目录
前言
日常开发中设计接口,响应时间是衡量一个接口质量的重要指标。
接口响应时间这里粗糙地分为三种:
- 即时响应:毫秒级,小于500毫秒
- 快速响应:秒级,大于500毫秒且小于2秒
- 长时间操作:大于2秒,甚至是分钟级别的操作
一般接口都是需要快速响应的,在不考虑任何优化策略的情况下,如果整个业务逻辑走下来,响应时间大于了2秒,那么就应该考虑对这个接口进行性能优化了,以免影响了用户的体验。
优化思路
缓存
毫无疑问,使用缓存是对性能提升最明显的方法。
若是缓存中存在,直接返回结果,否则再走接口逻辑,最后将结果放入缓存中,以便下次查询时使用。
缓存还可以分为本地缓存和分布式缓存
本地缓存:
由于数据是在本地内存,它的访问速度是最快的,同时它的容量也受限于运行本地内存,以及在分布式系统中,本地缓存的数据很可能和其他节点的数据不一致。所以本地缓存一般用来存储一些系统级别上基本上不会改变的内容。
在 Java 实现中,最简单的本地缓存其实可以用一个 ConcurrentHashMap来实现,不过在实际开发中有更好的选择,可以选择 Caffeine来做本地缓存,性能更好。
分布式缓存:
分布式缓存相对本地缓存,多了一次网络交互,所以速度会慢一些,但是大小则不会受制于本地服务机器内存了,内存不够用了还可以通过水平扩容来解决,而且在分布式系统中, 数据的一致性能够得以保证。
常用的分布式缓存为 Redis和 MemCached,MemCached 更加轻量级一些,纯粹的缓存中间件,Redis 不仅仅可以作为缓存,它还支持更多的功能(分布式锁,分布式限流,地理位置应用,布隆过滤器等等)
异步
如果是单线程执行业务逻辑,那么可以考虑对业务逻辑进行拆分,将其中能够并行执行的部分分解出来,然后使用多线程的方式去同时运行,理论上能够将该部分的运行性能提升(提升的大小取决于能够同时运行的线程数)
Java 中可以使用线程池 ThreadPool来并发执行任务,进阶一点还可以使用 CompletableFuture来编排异步任务。
合并
合并其实是指将批量操作,将多个操作合并成一个去执行
例如数据库中的批量插入,同一个表的多条插入语句,其实可以优化成一个插入语句,这样可以减少数据库的交互,避免重复地创建数据库连接。Mybatis 中的 BatchExecutor就是这个思路,将相同的 sql 语句添加到同一个 Satement 对象中等待执行,可以有效地减少 PrepareStatement 地编译操作
Redis 的 Pipeline也是将多个请求合并,最后一起发送,这样可以将多次网络交互优化成一次网络交互,减少网络交互的时间,从而提升性能。
拆分
拆分其实是针对多线程编程中,对共享资源的一个拆分,避免因为竞争激烈,导致多线程并发执行性能反而比单线程还慢了
代码中如果使用到了锁,可以从两方面考虑
减少锁的持有时间:将不必要的操作尽可能地放到锁外面去执行,避免其他线程等待锁的释放时间过长
减少锁的粒度:参考JUC下并发类的设计
ConcurrentHashMap是一个线程安全的Map,虽然可以直接通过synchronized修饰 put 和 get 方法来得到一个线程安全的HashMap,但是这样显然十分影响性能。
在JDK 1.7中,ConcurrentHashMap采用了**分段锁(Segment Lock)**的机制来提高并发性能。这种设计将整个哈希表分割成多个段(Segment),每个段都维护着自己独立的锁。这样,当多个线程并发访问ConcurrentHashMap时,它们可以并行地访问不同的段,从而减少了锁的竞争。
在JDK 1.8中,ConcurrentHashMap的锁机制发生了重大变化,它放弃了JDK 1.7中的分段锁设计,转而采用了一种基于CAS(Compare-And-Swap)操作+synchronized锁的细粒度锁机制。
除了ConcurrentHashMap,在 JUC 包下还有许多 Adder 类(IntegerAdder,LongAdder等等),当线程竞争不激烈时,直接采用CAS来实现数量的原子递增,如果竞争激励,则使用数组来维护元素个数(将单个资源的竞争拆分成多个),先从数组中随机选择一个,再通过CAS实现原子递增,最后再一起汇总。
单例
单例其实也可以看作是缓存的一种实现方式,本质是避免重复创建对象,直接服用现有的对象,从而减少重复创建对象的时间,提升性能。
在 spring 框架下开发,依赖注入默认就是单例的
压缩
接口的响应时间除了接口本身执行业务逻辑的时间,还有网络传输的时间。在其他条件都不变的情况下,减少网络传输内容的大小,也可以提升接口的性能。
如果接口的返回数据字段很多,可以考虑压缩字段的大小,比如说将返回的json中的有实际意思的单词字段名直接改成简单的f1,f2,f3;这样在返回数据量大的情况下也可以减少网络传输的内容大小。