Code前端首页关于Code前端联系我们

DJANGO uWSGI服务实现优雅重载

terry 2年前 (2023-09-28) 阅读数 99 #未命名

后台

电子主API服务采用uWSGI+Django框架。从历史上看,它始终通过 svc 守护进程运行。每次重新启动只不过是通过 svc -k / svc -i 通知服务器重新启动,这基本上意味着通过向服务器发送 SIGKILL/SIGINT 信号来杀死旧进程,然后守护进程再次启动新进程。

问题

这种重启方式会导致Web服务器每次重启都会有一小段时间(几秒到几十秒)不可用。这直接导致异常的来源有两个: 1、旧进程被直接杀死。 ,处理未完成请求的过程也直接中断; 2、旧服务器被杀掉,新服务器开始初始化准备之前,完全无法处理任何新的请求,这直接体现在大量的niginx502响应上。

对于异常1,由于SIGKILL信号直接杀死进程,所以是不可避免的。但是对于SIGINT信号,通过添加信号处理函数,理论上可以等待服务器处理完所有未完成的请求后再终止自身,但是在收到SIGINT信号之后,不应该再接收到新的请求。该方法无法解决异常2的问题。

对于异常2,要解决重启准备期间服务器无法响应新请求的问题,唯一的办法就是提前阻塞重启节点的流量,并在服务器准备好重新启动后恢复流量。通过提前阻塞一定时间的流量,等待历史请求处理完成再重启,也可以解决异常1的问题。这是典型的维持多进程服务高可用的思路。难点在于需要自动化这个过程,即: a. nginx 阻塞节点流量; b.一定时间后重启节点服务; c. 重启就绪后恢复节点流量。对于使用集成分布式框架并开放了流量控制和节点重启平台功能的大公司来说,这是一个选择。但对于小公司来说,自动化比较困难,手工操作不仅麻烦,而且容易。错误会发生,而且随着节点数量的增加,工作量会更加难以接受。? 提到了几种重载方案,简单总结如下:

方法流程优点缺点
服务器svc当前使用情况

直接在后台触发相关信号通知real_run script

使用方便每次重启所有进程(包括master),重启完成后会作为一个全新的进程♼直接完成,无需等待现有请求完成,

之前的所有新请求new 进程就绪后不会被处理,相当于服务宕机一段时间(秒)——依靠nginx实现故障转移

标准(默认/无聊)优雅重载(又名SIGHUP)

直接发送SIGHUP信号

master进程本身并不重启,它等待处理现有的请求。

在新的worker准备好之前,所有新的请求都会进入等待队列

使用方便

不会存在不一致的状态

基本上请求后重置所有进程状态等待满会报错直接

新请求可能需要等待很长时间

工作人员在惰性应用程序模式下重新加载将w写入主FIFO

等待正在运行的工作人员,然后启动每个工作人员。

避免重新启动整个实例。与默认方法一样,新请求必须排队等待
链加载(懒惰的应用程序)❀在处理现有请求后再次将c写入Master FI并重新启动下一个请求待新工人一一准备好后才进行工人的更换,以减少机器的短期负荷只能处理重新启动的worker代码更新,无法更改uwsgi选项的配置

需要多个worker配置才能获得更好的结果

Zerg模式配置ixsg池服务器或unzerg池绑定

首先生成新的worker,准备好后杀死旧的worker(详细信息请参见下面的Zerg Dance - 自动化此过程的实现)

基本上可以认为是0停止重新加载

允许master配置不同的选项来重新启动接管通过配置3个Master FIFO + uWSGI高级钩子新建进程,暂停旧进程

再次真正实现零停机。需要使用先进的uWSGI和Unix技术,且配置比较复杂

考虑到,链restart方法很容易配置并且可以在许多应用中使用。对于工人来说,可以完全避免异常1和异常2问题的发生。考虑到实际改变uWSGI配置的频率很低,有时按照老方法有损地重启master进程也是可以接受的,所以采用了chaining Restart来实现uWSGI配置的优雅重启。其实只需要在原来的.xml配置文件中添加/tmp/uwsgi_api.fifo即可(相当于.ini文件,命令行参数和master一样适用—— fifo),这意味着该命令是通过 /tmp/uwsgi_api.fifo 管道传输的。当需要重新启动时,运行 echo c > /tmp/uwsgi_api.fifo。

旧配置:

<uwsgi>
    <socket>0.0.0.0:3000</socket>
    <listen>14400</listen>
    <master>true</master>
    <processes>4</processes>
    <threads>12</threads>
    <module>wsgi</module>
    <profiler>true</profiler>
    <memory-report>true</memory-report>
    <limit-as>6048</limit-as>
    <buffer-size>65536</buffer-size>
    <thunder-lock>true</thunder-lock>
    <harakiri>30</harakiri>
    <lazy-apps>true</lazy-apps>
</uwsgi>

新配置:

<uwsgi>
    <socket>0.0.0.0:3000</socket>
    <listen>14400</listen>
    <master>true</master>
    <master-fifo>/tmp/uwsgi_api.fifo</master-fifo>
    <processes>4</processes>
    <threads>12</threads>
    <module>wsgi</module>
    <profiler>true</profiler>
    <memory-report>true</memory-report>
    <limit-as>6048</limit-as>
    <buffer-size>65536</buffer-size>
    <thunder-lock>true</thunder-lock>
    <harakiri>30</harakiri>
    <lazy-apps>true</lazy-apps>
</uwsgi>

PS:上网搜索,发现有人分享了uWSGI平滑重启方法。几篇文章的来源好像都是同一个——uwsgi Graceful Reload。还用到的是chain reload,是通过在.ini配置文件中添加:touch-chain-reload=XXX/settings.py来实现的,即每当触摸某个代码文件时自动触发。重启和链式重启的本质是一样的,只不过这里我是通过管道发送重启命令,而前者是通过监控代码文件的状态来实现的。我个人认为通过命名管道触发重启更可控。这可以将重新启动操作本身与代码状态隔离开来,这是两个不应该相关的条件。再者,使用touch方式,当代码在线发生时,Update——git pull新代码、cp覆盖新代码甚至编辑修改代码(应该尽量避免)——无论是有意还是无意,都会触发reload,这不一定是操作者想要的,并且是通过管道进行的,所以很明显这个操作需要重启服务器。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门