图18 线程池执行模型
这种方式在流量较低的情况下看不出什么问题,流量变高会导致需要的线程数量成倍增加。
例如:一次请求 A 需要调用 BCD 3个接口,那100个并发需要的线程数就是100 3*100=400(第一个100是A对应的主线程,后面的3*100是 BCD 需要的100个线程)。
3)解决方案
线程池并发调用改成 NIO 异步调用,如下图所示。
和之前对比,100 个并发,需要的线程数也是 100(这个地方不考虑 NIO 本身的线程,这个是全局的,并且是相对固定很少的线程数)。
图19 NIO异步调用执行模型
4)效果
超时问题没再出现,CPU Load 平均下降50%,之前 Load 经常超过2(CPU 核数为2),改造之后 Load 降到 0.5 左右。
图20 CPU Load优化后效果
7.启动阶段预热
启动阶段预热可以提前建立链接,减少流量接入时的链接建立,从而降低超时的发生。
1)分析
应用拉入后出现大量超时,并且 CPU Load 高 CPU 利用率正常,说明有很多等待线程,这种是拉入后有大量请求在等待被处理。
之前我们生产遇到过是在等待 Redis 建立链接,建链的过程是同步的,应用刚拉入请求量瞬间涌入就会导致大量请求在等待 Redis 建链完成。
2)解决方案
启动阶段预热提前建立链接,或者是配置 Redis 的最小空闲连接数。其他资源准备也可以通过启动阶段预热完成,比如 DB 链接、本地缓存加载等。
8.优化 JIT
JIT(Just-In-Time)编译可以提高程序的运行效率,灰度接入流量将字节码编译成本地机器码,避免对接口性能的影响。
1)JIT 介绍
JIT 是 Just-In-Time 的缩写,意为即时编译。JIT 是一种在程序运行时将字节码编译为本地机器码的技术,可以提高程序的执行效率。
在 Java 中,程序首先被编译为字节码,然后由 JVM 解释执行。但是,解释执行的效率较低,因为每次执行都需要解释一遍字节码。为了提高程序的执行效率,JIT 技术被引入到 Java 中。JIT 会在程序运行时,将频繁执行的代码块编译为本地机器码,然后再执行机器码,这样可以大大提高程序的执行效率。
2)分析
JIT 技术可以根据程序的实际运行情况,动态地优化代码,使得程序的性能更好。但是JIT 编译过程需要一定的时间,因此在程序刚开始运行时,可能会出现一些性能瓶颈。
如下图应用拉入后 JIT 时间很久,那可以确认是 JIT 导致超时。
图21 JIT执行时间
3)解决方案
优化 JIT 一个比较好的方案是开启服务预热(预热功能携程 RPC 框架是支持的)。
原理是让应用拉入后不是立马接入100% 流量,而是随着时间移动来逐渐增加流量,最终接入100% 流量,这种会让小部分流量将热点代码提前编译好。
4)效果
开启服务预热后,如下图所示,应用流量是逐渐增加的,可以看到响应时间随着时间越来越低,这就达到了预热的效果。