服务优雅下线,没你想的那么简单?

2022-01-16 From 博客园 By 架构摆渡人

服务部署,是一个避免不了的问题。按正常迭代的速度一般两周会发一个版本,此时就需要部署新的代码。发布方式,我相信主流的都是用滚动发布,因为这样的成本是最低的,机器数量是固定的,一台台机器轮流发布。

但是我们总会在发布过程中碰到一些报错信息,那是因为请求还没结束,某些组件已经强制停止了,比如我们的数据源,比如异步任务还没处理完。

那么如何解决这个问题呢?那就是服务优雅下线,估计大家都听过这个词,但我不知道有多少做到了随时发布都不影响功能的正常使用。

优雅下线涉及点

外部请求必须处理

服务时时刻刻都在处理请求,一旦收到要停止的命令,那么必须等待当前的请求执行完毕才能去关闭一些资源,否则就会出现各种异常。

除了等待,还需要让外部的请求不要再过来,要告诉别人,我要下班了,不要来找我了,去找其他人吧。否则你永远都下不了班,是一样的道理。

异步任务必须处理

这里的异步任务通常指我们放入线程池中进行处理任务,如果强制进行程序的停止,那么线程池里的任务就会丢掉,所以除了同步被外部调用的逻辑处理完,这种异步的逻辑也是要处理完的。

这里再提一点,就是如果异步任务丢失会对业务造成影响的这种场景,建议还是不要放到线程池里面进行处理,如果要放,那么必须有持久化,程序重启后可以继续执行。

消息必须消费完

消息也是异步任务的一种类型,我们的目标肯定也是需要让消息消费完才行。但是消息跟线程池里的任务最大的差别就在于:消息是有持久化的,并且有重试功能。

就算消息没消费完,程序强制停止,这条消息没有ACK,然后就会重试到另一台机器的实例上继续执行,前提是你的这个执行逻辑不能产生脏数据,一定要通过事务保证数据的一致性。

优雅下线解决方案

注销服务实例

下线最重要的一件事情就是注销自己的实例,这样才不会有后续的请求过来。注销实例主要是跟注册中心交互,将自己的实例从注册中心下线掉就行了。下线后服务消费者会重新从注册中心拉取最新的实例列表,也就不会将请求路由过来。

如果要下线的这个服务不是一个内部服务,而是网关呢?网关是流量的入口,客户端的请求过来的,客户端是自然不知道网关有多少实例,所以在网关前面都有一个负载均衡器,比如常用的Nginx。

那就需要将这个下线的网关实例从Nginx中进行下线操作,这样后续的流量才不会被转发过来,跟内部服务是一样的道理。

Nginx如果有独立的模块去对接注册中心的话,那么还是把注册中心的给下线掉,Nginx就能感知到下线动作。如果没有对接,而是固定的配置信息,那么就需要改Nginx的配置,然后重新加载即可。

注销MQ消费实例

通过下线注册中心里面的实例,外部流量就不会请求过来。此时还需要将MQ的实例进行下线操作,告诉MQ的服务端,不要再给我推消息了或者是客户端不再拉取消息。

实现思路

  1. 写一个停止流量的接口,在接口中将本身实例从注册中心,MQ进行下线操作。
  2. 写一个检测流量是否结束的接口,在接口中判断当前是否还有正在工作的线程,有没有正在处理的消息,有没有正在执行的异步任务等等。
  3. 当完全没有流量的时候,发布平台直接对当前进程进行kill操作,此时所有任务都已执行完并且没有新流量进来,无损操作。
  4. 执行发布流程。
    这里其实涉及到一个点,就是假如3分钟了,还是有任务处理,那么是否要强制中断?

这里其实可以这么做,就是我们的服务本身的实例一旦下线,正常的话几秒钟后就应该没有任务了,因为对外的接口基本上都是毫秒级响应。主要就怕异步任务,比如线程池里堆积了好多任务等待执行,所以大家需要去梳理下,如果有这种场景就调整,不要往线程池里堆积任务,这样才能保证在下流量的时候能够尽快执行完成。

总结

其实优雅下线的核心在于流量的切换,就是我要下线的这个服务必须把所有外部的流量都切走,然后再把没处理完的事情处理完,完成后就可以直接重新发布了。

如果你们上了容器的话,容器管理平台应该是能够提供优雅下线的方式,像K8s里面应该就有优雅停止Pod的方式,不过我对K8s不太熟,记得是有的,其实原理也很简单,先启动一个Pod,完成之后将流量切过去就行了,这种方式更简单,充分利用了容器的优势。

本文来源:博客园,转载请注明出处!

来源地址:https://www.cnblogs.com/jiagoubaiduren/p/15808961.html

发表感想

© 2016 - 2022 chengxuzhixin.com All Rights Reserved.

浙ICP备2021034854号-1    浙公网安备 33011002016107号