Dubbo优雅启动
1. 启动有什么问题
我们知道,应用在运行了一段时间后,执行速度会比刚启动的时候要快。这是因为在 Java 里面,在运行过程中,JVM 虚拟机会把高频的代码编译成机器码,被加载过的类也会被缓存到 JVM 缓存中,再次使用的时候不会触发临时加载,这样就使得“热点”代码的执行不用每次都通过解释,从而提升执行速度。
但是这些“临时数据”,都在我们应用重启后就消失了。重启后的这些“红利”没有了之后,如果让我们刚启动的应用就承担像停机前一样的流量,这会使应用在启动之初就处于高负载状态,从而导致调用方过来的请求可能出现大面积超时,进而对线上业务产生损害行为。
所以,在应用启动的时候,需要先让应用只处理少部分的流量,这样低功率运行一段时间后,再逐渐提升至最佳状态。
2. 优雅启动的方法
启动预热
那什么叫启动预热呢?
简单来说,就是让刚启动的服务提供方应用不承担全部的流量,而是让它被调用的次数随着时间的移动慢慢增加,最终让流量缓和地增加到跟已经运行一段时间后的水平一样。
那么Dubbo中是如何实现启动预加热的呢?
首先看一下 RoundRobinLoadBalance.doSelect 方法。此方法是从invoker 列表中根据权重选择出一个invoker.
其次看一下getWeight()方法,此方法计算invoker的权重。
具体的计算方式如下:
延迟暴露
我们应用启动的时候都是通过 main 入口,然后顺序加载各种相关依赖的类。以 Spring 应用启动为例,在加载的过程中,Spring 容器会顺序加载 Spring Bean,如果某个 Bean 是 RPC 服务的话,我们不光要把它注册到 Spring-BeanFactory 里面去,还要把这个 Bean 对应的接口注册到注册中心。注册中心在收到新上线的服务提供方地址的时候,会把这个地址推送到调用方应用内存中;当调用方收到这个服务提供方地址的时候,就会去建立连接发请求。
但这时候是不是存在服务提供方可能并没有启动完成的情况?因为服务提供方应用可能还在加载其它的 Bean。对于调用方来说,只要获取到了服务提供方的 IP,就有可能发起 RPC 调用,但如果这时候服务提供方没有启动完成的话,就会导致调用失败,从而使业务受损。
在解决问题前,我们先看下出现上述问题的根本原因。这是因为服务提供方应用在没有启动完成的时候,调用方的请求就过来了,而调用方请求过来的原因是,服务提供方应用在启动过程中把解析到的 RPC 服务注册到了注册中心,这就导致在后续加载没有完成的情况下服务提供方的地址就被服务调用方感知到了。这样的话,其实我们就可以把接口注册到注册中心的时间挪到应用启动完成后。
具体的做法就是在应用启动加载、解析 Bean 的时候,如果遇到了 RPC 服务的 Bean,只先把这个 Bean 注册到 Spring-BeanFactory 里面去,而并不把这个 Bean 对应的接口注册到注册中心,只有等应用启动完成后,才把接口注册到注册中心用于服务发现,从而实现让服务调用方延迟获取到服务提供方地址。
Dubbo 2.6.5 以前的版本,如果需要延迟到 Spring 初始化完成后,再暴露服务。那么需要进行如下的配置:
<dubbo:service delay="-1" />
延迟 5 秒暴露服务
<dubbo:service delay="5000" />
Dubbo 2.6.5以及以后的版本,所有服务都将在 Spring 初始化完成后进行暴露,如果你不需要延迟暴露服务,无需配置 delay。
延迟 5 秒暴露服务
<dubbo:service delay="5000" />
我们来看一下其中的源码实现。看一下 ServiceConfig.export()方法。
也就是如果是延时export,就使用时间调度器延时执行服务export逻辑。
QOS 命令上线
Dubbo 还为服务提供了另一个配置项:
<dubbo:service register="false" />
该配置项配置后,服务将不会发布到注册中心,可能很多 Dubbo 用户不会注意到这个配置,它的作用恰恰是 QOS 指令使用的。
当需要发布的时候,我们可以通过online 命令手动将服务发。
滚动升级
- 分时分批启动,就和灰度发布一样;
- 在请求低峰把,在热点的应用肯定是有使用低峰的;
3. 总结
启动预热与延迟暴露,它们并不是 RPC 的专属功能,我们在开发其它系统时,也可以利用这两点来减少冷启动对业务的影响。
4. 鸣谢
RPC实战与核心原理