系统设计:消灭慢接口

1. 引子

某些接口的响应时间明显变慢,甚至响应超时。这部分接口对整个系统对整体吞吐率和可用性都会带来影响,当然也会影响用户体验。

对核心接口与流量访问高的接口需要做定向优化,例如:

  1. 异步处理,或者加入并发处理,避免同步阻塞

  2. 如果频繁对数据库进行访问考虑,加入缓存

  3. 批量访问,避免for循环调用数据库带来的网络开销

  4. 避免接口一次返回过多的数据

错误接口部分这种没什么可说的,属于硬性规定,消灭它,如果有接口的错误率高于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。