一. 优雅启动
- 什么是启动预热
启动预热就是让刚启动的服务,不间接承当全副的流量,而是让它随着工夫的挪动缓缓减少调用次数,最终让流量弛缓运行一段时间后达到失常程度。
如何实现
首先对于调用方来说,咱们要晓得服务提供方的启动工夫,这里有两种获取办法:
一种是服务提供方在启动的时候,被动将启动的工夫发送给注册核心;
另一种就是注册核心来检测, 将服务提供方的申请注册工夫作为启动工夫。这两者工夫会有一些差别, 但并没有关系, 因为整个预热过程的工夫是一个粗略值,即便多个机器节点之间存在 1 分钟的误差也不会影响,并且在实在环境中机器都会开启 NTP 工夫同步性能,来保障所有机器工夫的一致性。调用方通过服务发现,除了能够拿到 IP 列表,还能够拿到对应的启动工夫。依据基于权重的负载平衡策略, 动静调整权重,随着工夫的推移缓缓减少到服务提供方的调用次数。
通过这种机制, 对服务提供方进行降权,缩小被负载平衡抉择的概率,防止让利用在启动之初就处于高负载状态,从而实现服务提供方在启动后有一个预热的过程。
在Dubbo框架中也引入了"warmup"个性,外围源码是在com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance.java中:
protected int getWeight(Invoker<?> invoker, Invocation invocation) { // 先失去Provider的权重 int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); if (weight > 0) { // 失去provider的启动工夫戳 long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L); if (timestamp > 0L) { // provider曾经运行工夫 int uptime = (int) (System.currentTimeMillis() - timestamp); // 失去warmup的值,默认为10分钟 int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP); // provider运行工夫少于预热工夫,那么须要从新计算权重weight(即须要降权) if (uptime > 0 && uptime < warmup) { weight = calculateWarmupWeight(uptime, warmup, weight); } } } return weight;}static int calculateWarmupWeight(int uptime, int warmup, int weight) { // 随着provider的启动工夫越来越长,缓缓晋升权重weight int ww = (int) ( (float) uptime / ( (float) warmup / (float) weight ) ); return ww < 1 ? 1 : (ww > weight ? weight : ww);}
Dubbo2.7.3版本, 参考源码“org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance”
依据calculateWarmupWeight()办法实现可知,随着provider的启动工夫越来越长,缓缓晋升权重weight,且权重最小值为1,具体执行策略:
1)如果provider运行了1分钟,那么weight为10,即只有最终须要承当的10%流量;
2)如果provider运行了2分钟,那么weight为20,即只有最终须要承当的20%流量;
3)如果provider运行了5分钟,那么weight为50,即只有最终须要承当的50%流量;
二. 优雅敞开
为什么须要优雅敞开
对于调用方来说,服务敞开的时候可能会存在以下几种状况:
- 调用方发送申请时,指标服务曾经下线。对于调用方来说,是能够立刻感知的,并且在其衰弱列表外面会把这个节点挪掉,也就不会纳入负载平衡选中。
- 调用方发申请时,指标服务正在敞开中,但调用方并不知道它正处于敞开状态,而且两者之间的连贯也没有断开,所以这个节点还会存在衰弱列表外面,所以这个节点仍肯定概率会被调用, 从而导致调用失败问题。
如何实现优雅敞开
大家可能存有疑难,RPC 外面有服务注册与发现性能, 注册核心的作用就是用来治理服务的状态, 当服务敞开时, 会先告诉注册核心进行下线, 而后通过注册核心移除节点信息,这样不就能够保障服务不被调用吗?
那咱们来看下敞开的流程:
整个敞开过程中依赖了两次 RPC 调用,一次是服务提供方告诉注册核心下线操作,一次是注册核心告诉服务调用方下线节点操作。并且注册核心告诉服务调用方都是异步的,并不能保障齐全实时性,通过服务发现并不能做到利用的无损敞开。
有没有好的解决方案呢?
服务提供方曾经进入敞开流程,那么很多对象曾经被销毁了,这个时候咱们能够设置一个申请“挡板”,挡板的作用就是通知调用方,服务提供方曾经开始进入敞开流程了,不能再解决其余申请了。
这就好比咱们去超市结账,在交接班或者上班的时候, 收银员会放一个提示牌在柜台, 提醒“该通道已敞开”,不能进行结账, 这个时候客户只能转移到其余可用的柜台上进行结账。
解决流程:
当服务提供方正在敞开,如果还收到了新的业务申请,服务提供方间接返回一个特定的异样给调用方。这个异样就是通知调用方“我正在敞开,不能解决这个申请”,而后调用方收到这个异样响应后,RPC 框架把这个节点从衰弱列表挪出,并把其余申请主动重试到其余节点,因为这个申请是没有被服务提供方解决过,所以能够平安地重试到其余节点,这样就能够实现对业务简直无损的解决。如果要更为欠缺, 咱们还能够加上被动告诉机制,这样既能够保障实时性,也能够防止客户端呈现重试状况。
如何捕捉敞开事件呢?
操作系统的过程的敞开,如果不是强制完结,过程会接管到一个完结信号,Java应用程序,在接管到完结信号时, 会调用Runtime.addShutdownHook 办法触发敞开钩子。 咱们在 RPC服务启动的时候,提前注册敞开钩子,在外面增加处理程序,先开启挡板, 而后告诉调用方服务已下线。当接管到新来的申请时,挡板会进行拦挡,抛出特定异样。为了尽可能地实现正在解决的申请, 咱们能够退出计数器机制,把残余申请纳入计数器当中, 每解决完一个申请, 就缩小一个计数, 将所有残余申请解决实现之后, 再真正完结服务。
在Dubbo框架中, 在以下场景中会触发优雅敞开:
JVM被动敞开(
System.exit(int)
;JVM因为资源问题退出(
OOM
);应用程序承受到过程失常完结信号:
SIGTERM
或SIGINT
信号。优雅停机是默认开启的,停机等待时间为10秒。能够通过配置
dubbo.service.shutdown.wait
来批改等待时间。基于ShutdownHook形式的优雅停机无奈确保所有敞开流程肯定执行实现,所以 Dubbo 推出了多段敞开的形式来保障服务齐全无损。在敞开利用前,首先通过 QOS(在线运维命令) 的offline指令下线所有服务,而后期待肯定工夫确保曾经达到申请全副处理完毕,因为服务曾经在注册核心下线,以后利用不会有新的申请。这时再执行真正的敞开(SIGTERM 或SIGINT)流程,就能保障服务无损。
Dubbo优雅敞开的源码:
DubboShutdownHook.register办法
注册敞开钩子:
/** * 注册敞开钩子,在服务敞开时触发执行 */public void register() { if (!registered.get() && registered.compareAndSet(false, true)) { Runtime.getRuntime().addShutdownHook(getDubboShutdownHook()); }}
DubboShutdownHook.doDestroy办法
销毁所有相干资源:
/** * 敞开登记所有资源, 包含注册器和协定处理器。 */public void doDestroy() { if (!destroyed.compareAndSet(false, true)) { return; } // 销毁所有注册器,包含Zookeeper、etcd、Consul等等。 AbstractRegistryFactory.destroyAll(); // 销毁所有协定处理器,包含Dubbo、Hessian、Http、Jsong等。 destroyProtocols();}
本文由mirson创作分享, 感激大家的反对, 心愿对大家有所播种!
入群申请,请加WX号:woodblock99