如何优雅地重启 django 在 nginx 后面运行 fcgi?

Posted

技术标签:

【中文标题】如何优雅地重启 django 在 nginx 后面运行 fcgi?【英文标题】:How to gracefully restart django running fcgi behind nginx? 【发布时间】:2010-09-26 14:04:28 【问题描述】:

我正在使用 fcgi 连接的 nginx 后面运行一个 django 实例(通过使用 manage.py runfcgi 命令)。由于代码已加载到内存中,我无法在不杀死并重新启动 django fcgi 进程的情况下重新加载新代码,从而中断实时网站。重启本身非常快。但是通过首先杀死 fcgi 进程,一些用户的操作会被打断,这是不好的。 我想知道如何重新加载新代码而不会造成任何中断。建议将不胜感激!

【问题讨论】:

【参考方案1】:

我会在新端口上启动一个新的 fcgi 进程,更改 nginx 配置以使用新端口,让 nginx 重新加载配置(这本身很优雅),然后最终停止旧进程(您可以使用 netstat 查找当与旧端口的最后一个连接关闭时退出)。

或者,您可以更改 fcgi 实现以派生一个新进程,关闭子进程中除 fcgi 服务器套接字之外的所有套接字,关闭父进程中的 fcgi 服务器套接字,在子进程中执行一个新的 django 进程(使其使用fcgi 服务器套接字),并在所有 fcgi 连接关闭后终止父进程。 IOW,为 runfcgi 实现优雅重启。

【讨论】:

如果你把一个新的fcgi放在一个新的端口上,nginx不会把已经登录的用户转发到新进程吗?这将与冷重启 fcgi 进程相同 确实会将所有用户转发到新进程;这没有害处。冷重启的问题是正在运行的进程被杀死,所以 in-progress HTTP 请求失败。这就是 OP 担心的情况(IIUC)【参考方案2】:

所以我继续执行 Martin 的建议。这是我想出的 bash 脚本。

pid_file=/path/to/pidfile
port_file=/path/to/port_file
old_pid=`cat $pid_file`

if [[ -f $port_file ]]; then
    last_port=`cat $port_file`
    port_to_use=$(($last_port + 1))
else
    port_to_use=8000
fi

# Reset so me don't go up forever
if [[ $port_to_use -gt 8999 ]]; then
    port_to_use=8000
fi

sed -i "s/$old_port/$port_to_use/g" /path/to/nginx.conf

python manage.py runfcgi host=127.0.0.1 port=$port_to_use maxchildren=5 maxspare=5 minspare=2 method=prefork pidfile=$pid_file

echo $port_to_use > $port_file

kill -HUP `cat /var/run/nginx.pid`

echo "Sleeping for 5 seconds"
sleep 5s

echo "Killing old processes on $last_port, pid $old_pid"
kill $old_pid

【讨论】:

哇,简单但非常有用。非常感谢分享这个! 我认为脚本的sed 部分有错字,我猜$old_port 应该是$last_port【参考方案3】:

我在寻找此问题的解决方案时遇到了此页面。其他一切都失败了,所以我查看了源代码:)

解决方案似乎要简单得多。 Django fcgi 服务器使用 Flup,它以正确的方式处理 HUP 信号:它优雅地关闭。所以你所要做的就是:

    向 fcgi 服务器发送 HUP 信号(runserver 的 pidfile= 参数会派上用场)

    稍等(flup 允许子进程处理 10 秒,所以再等几个;15 看起来不错)

    向 fcgi 服务器发送 KILL 信号,以防万一被阻止

    再次启动服务器

就是这样。

【讨论】:

如果您在upstart 下设置您的django fcgi 服务器,这将非常有效。 initctl reload <job> 将发送 HUP,您的作业定义中的 respawn 指令将处理重新启动。没有麻烦,没有大惊小怪。【参考方案4】:

您可以使用生成而不是 FastCGI

http://www.eflorenzano.com/blog/post/spawning-django/

【讨论】:

【参考方案5】:

我们终于找到了正确的解决方案!

http://rambleon.usebox.net/post/3279121000/how-to-gracefully-restart-django-running-fastcgi

首先向flup发送一个HUP信号来表示重启。然后 Flup 将对它的所有子代执行此操作:

    关闭将停止非活动子级的套接字 发送一个 INT 信号 等待 10 秒 发送 KILL 信号

当所有的孩子都走了,它会开始新的。

这几乎在所有时间都有效,除非当flup 执行步骤2 时孩子正在处理请求,那么您的服务器将因KeyboardInterrupt 而死,给用户一个500 错误。

解决方案是安装一个 SIGINT 处理程序 - 有关详细信息,请参阅上面的页面。即使只是忽略 SIGINT 也会让您的进程有 10 秒的时间退出,这对于大多数请求来说已经足够了。

【讨论】:

正如该帖子中的 cmets 所述,这仅适用于 Flup 1.0.3。此外,我无法使用 prefork 进行此操作,只能使用线程。 是否有不使用flup 1.0.3的问题或特殊原因?我在 prefork 模式下使用它,它工作正常。

以上是关于如何优雅地重启 django 在 nginx 后面运行 fcgi?的主要内容,如果未能解决你的问题,请参考以下文章

如何优雅地重启芹菜工人?

如何在不延迟任务的情况下优雅地重启 Celery

nginx 相关命令

nginx命令大全

nginx命令大全

说说 Django 如何优雅地对接 Mongodb