系统设计:消灭慢接口
1. 引子
某些接口的响应时间明显变慢,甚至响应超时。这部分接口对整个系统对整体吞吐率和可用性都会带来影响,当然也会影响用户体验。
对核心接口与流量访问高的接口需要做定向优化,例如:
-
异步处理,或者加入并发处理,避免同步阻塞
-
如果频繁对数据库进行访问考虑,加入缓存
-
批量访问,避免for循环调用数据库带来的网络开销
-
避免接口一次返回过多的数据
错误接口部分这种没什么可说的,属于硬性规定,消灭它,如果有接口的错误率高于0.1%或者频繁错误日志打印那一定属于程序层面的问题。
2. 通用
观察接口吞吐量、耗时、错误率。获取方式可以通过各种中间件、埋点。
3. 慢接口优化
3.1 下游问题/网络抖动
下游慢接口导致。
3.2 编码问题
- 是否存在无效的字段填充?即,某些字段是流程不需要的,查询时有额外开销
- 是否存在放大调用?
- 是否可以优化成批量查询、批量填充?
- 是否存在慢SQL?
3.3 异步处理常见方式
首先看需要考虑使用的场景:
- 编程接口易用性
- 执行环境:单JVM还是集群?
- 性能和稳定性,是否持久化:如果机器突然故障/重启可能会丢失处理进度。如果希望能恢复或重做,需要支持幂等
- 如何取得异步执行的结果。
列举一些常见的异步化方式
类型 | 接口 | 执行环境 | 是否持久化 | 性能和稳定性 | 备注 |
---|---|---|---|---|---|
Java线程模型 | 原生支持 | 单JVM | 自行控制 | 单机 | |
Java并发工具类(JUC) | 原生支持 | 单JVM | 自行控制 | 单机 | |
Spring线程池 | API简单,可用注解 | 单JVM | 自行控制 | 单机 | 使用注解不指定线程池可能会混用 |
Eventbus(Guava) | API简单,事件模型 | 单JVM | 中断无法恢复,不支持集群调度 | 单机 | 需要注意事件类跨bundle依赖的问题 |
redis队列 | 需要接入Redis,产生外部依赖,并且要编写事件投递和消费的接入代码 | 单JVM | 中断无法恢复,不支持集群调度 | 高性能 | 可能单点故障,需要进行高可用设计 |
mircotask | 功能丰富,包括监控等,有一定接入成本 | 集群 | 待补充 | 待补充 | |
使用MQ实现异步事件模型 | 事件模型,需要接入MQ中间件,产生外部依赖,需要自行编写事件处理框架 | 可集群部署,可跨应用处理 | 可能需要持久化 | 待补充 | |
使用定时任务中间件实现异步事件模型 | 事件模型,需要接入定时任务中间件,产生外部依赖,需要自行编写事件处理框架 | 可集群部署,可跨应用处理 | 需要持久化 | 执行速度可控 |
3.4 缓存
”所见一切皆缓存。“
解释:对于任意一个事物,当观察者去观察它时,因为信号传播需要时间,观察者接收到的信号永远是它过去发出的。而在这个信号在传播的过程中,被观察的事物可能发生了变化。观察者接收到的信号可以看做它过去的快照,观察者依据这个信号做出的一切判断,都可以认为是将快照缓存了下来。
3.4.1 缓存选型
- 近端缓存
- 远端缓存
缓存选型 | 实例 | 应用场景 | 接入成本 | 限制 |
---|---|---|---|---|
JVM缓存 | HashMap、BloomFilter、WeakReference、SoftReference | 广泛 | 容易实现 | 单机需要预热,JVM内存上限制约 |
分布式缓存 | Redis、Memcache | 广泛 | 引入额外依赖 | 需要考虑可靠性和降级策略 |
浏览器缓存 | 使用客户端资源,节约服务器资源 | 有限制 | 只能解决部分体验问题,对后端开发者不可控 | |
CDN缓存 | - | 有限制 | 对于大对象访问速度优化明显,有额外云基建花费 |
3.4.2 缓存常见问题
(by chatgpt)
一致性问题:缓存与数据库中数据不一致的问题,特别是高并发情况下数据频繁更新的场景。需要采用合适的缓存更新策略和缓存失效机制来保证数据一致性。
穿透问题:指大量请求直接访问数据库,导致缓存无法起到应有的作用。缓存穿透的原因可能是缓存中不存在需要访问的数据,需要采用布隆过滤器等机制来防止缓存穿透。
雪崩问题:缓存中大量的数据同时失效,导致请求直接访问数据库,从而造成数据库压力过大。需要采用分布式锁、限流等机制来防止缓存雪崩。
内存泄漏问题:如果缓存中的数据一直不被访问或者缓存没有被清理,可能会导致内存泄漏问题。需要采用合适的缓存淘汰策略来避免内存泄漏问题。
容量问题:需要根据实际业务需求和硬件资源来设置合适的缓存容量。
3.4.3 不适合缓存的场景
(chatgpt生成)
- 数据实时性要求高:如果数据实时性要求高,需要及时更新,那么就不能使用缓存。例如在线支付系统、股票交易系统等。
- 缓存更新成本高:有一些数据的更新频率非常高,而且每次更新都需要付出较高的代价,这种情况下使用缓存反而会降低性能,因为缓存需要及时更新,更新成本高会导致缓存命中率降低。例如视频直播系统、游戏排名系统等。
- 业务复杂度高:有一些业务涉及到多个系统之间的交互,业务复杂度很高,加上缓存还需要考虑缓存的一致性和更新策略,这样反而会增加系统复杂度。例如分布式事务系统、复杂的金融交易系统等。
- 访问量低:对于访问量不大的应用,使用缓存并不能显著提升性能,反而增加了额外的系统复杂度和开发成本。例如内部管理系统、小型门户网站等。
在设计阶段,一定要提前考虑如何使用缓存。
3.5 避免一次性返回过多的数据
不良影响:
构造返回请求时JVM内存占用升高
接口响应时间过长导致RT升高
网络带宽占用
可能会超出浏览器、服务器配置的限制(HTTP协议本身没有限制大小)
4. 消灭错误接口
错误率统计口径:接口返回值不是2XX。
因此只需要在应用内部处理好异常,对外返回HttpStatus=200即可,Result类里的code、suceess按照实际情况返回,不计入错误率。
特殊情况:某些SEO规范下,查不到数据接口要处理成返回404。
作者:五岳
出处:http://www.cnblogs.com/wuyuegb2312
对于标题未标注为“转载”的文章均为原创,其版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。