结合Nginx实现服务平滑重启

Nginx作为负载均衡,当请求增大,单个web进程负载不够时,可以通过水平扩展web进程数量,前端用Nginx作为负载均衡,将请求按照负载策略(轮询、随机,ip hash等)分配到各个web进程中。在一个web进程可以满足负载的前提下,当重启进程会导致服务不可用,这时也可以起两个进程(尽管是在一台物理机上),当重启服务时可以逐个启动web进程,这样就能保证服务平滑重启。

Nginx反向代理负载均衡配置,

upstream test-api{

server 172.17.0.1:9081 max_fails=1 fail_timeout=45s;
server 172.17.0.1:9082 max_fails=1 fail_timeout=45s;
}

负载策略:默认是轮询,其他策略包括weight,ip_hash,fair,url_hash。

虽然server列表是静态配置的,但Nginx在转发请求时依赖的server是动态的,当某个server失败max_fails次后,Nginx将此server从列表中剔除那个server,等待fail_timeout时间后,再尝试将其加回到server列表中,参与负载。

这套配置能满足基本的负载要求,但不满足服务平滑重启,原因在于fail_timeout值不方便确定,理论上来讲,将fail_timeout设为server重启所需要的时间就好,但后端web进程因为你本身体量或运行方式不同(纯Tomcat ,内嵌Tomcat,Docker容器)启动需要时间不确定。

如果fail_timeout小于server重启时间,会导致无意义的尝试,部分请求会被牺牲

如果fail_timeout大于server重启时间,server的服务时间不能得到充分利用,更严重的,当第一个server重启完成之后,立即重启第二个server,这时在Nginx看来,两个server都不可用,请求会reset。所以尽可能及时地将服务可用状态同步给负载均衡层。

解决这个问题,有两个大方向,

一、Nginx主动探测server可用性

sever提供健康检查接口,Nginx通过健康接口定时(比如每3秒)检查改服务是否可用,如果不可用摘除之,一定时间后再次尝试,如果连续几次可用,则将其加回到server类表中。

实现方案

  1. 找第三方实现,加入到Nginx的模块中(未试验)
  2. 利用Tengine,其自带健康检查模块
二、server主动上报,类似微服务的注册中心

当server收到kill 信号时,先向Nginx上报将自己摘除的信息,当server启动完成之后,向Nginx上报,将自己加入server列表中

实现方案

  1. 在Nginx和server中间添加一层成熟的服务注册服务,比如consul。nginx将请求转发给consul,consul将请求均衡地转发给server
  2. server在启停时动态修改upstream配置,然后重启Nginx。

两种方向比较,第一种缺陷,需要负载层不断地检查,一是会带来网络上的开销,二是检查时间间隔不好确定,服务的可用状态可能会在检查时间间隔中变更,还是不够及时。

所以着重看第二个方向,对于第一种方案,改造成本低,但是因为多了一次转发,增大了请求处理流程。对于第二种方案,如果能做到Nginx动态读取配置,那就完美了。Nginx因为采用多进程模式,因此有能力实现平滑重启的功能,kill -HUP

在我的实践中,就是采用的第二种方案,但没有让server参与这个流程,而是自己编写的脚本,实现了启停server,探测server可用性,动态修改Nginx配置,重启Nignx,这样可以实现不侵入server。脚本大概实现逻辑,当重启一个server时,先更新upstream配置(比如摘掉9081,sed -i '/9081/s/^/#&/ xxx.conf),重启Nginx。在服务启动过程中,定时请求server健康接口,当发现服务可用后,再改回upstream配置(比如去掉9081的注释,sed -i '/9081/s/^#//' xxx.conf),重启Nginx。

通过测试,利用curl循环请求接口,执行重启两个server流程,能实现服务平滑重启,客户端无感知的效果。

你可能感兴趣的:(结合Nginx实现服务平滑重启)