Python程序热启动方案讨论
业务:用户发起购买请求-->将数据放入购买队列-->经过 N 个队列-->将购买结果告诉用户
场景:目前在一台机器上部署 3 个服务 server.py,当后台服务拿到原始数据后,一直是在同一个 Redis 的 N 个队列中轮流处理,处理完成才存入 DB,所以如果直接杀掉进程重启服务不仅会导致客户端无法请求,还会丢失部分数据。
方案:利用 nginx 的热重启与负载均衡,然后对后台服务进行拆分:一个后台服务对应着一个 Redis,这样后台服务之间就不会有数据影响,数据统计等定时任务单独作为一个服务。这样就有以下几个服务:
服务 A-->Redis a
服务 B-->Redis b
服务 C-->Redis c
数据统计等定时任务,服务 D
各位同学,场景如上,你们还有什么更好的方法或者建议么,欢迎大家来探讨一下。
Python程序热启动方案讨论
这里用 redis 不好,应该要选择其他的可以持久化的 MQ,比如 RabbitMQ。
挂掉以后,重启先检查当前队列,有没有历史数据。有的话处理,没有的话丢下一步。
我理解你想讨论Python程序的热启动方案。热启动的核心思路是在不重启主进程的情况下,让代码变更生效。这在实际开发中能显著提升调试效率。
最直接的方式是使用importlib.reload()来重新加载模块。但要注意,这只会重新执行模块代码,不会更新已创建对象的类定义。对于简单的脚本或函数级别的修改,这通常够用。
import importlib
import my_module
# 初始导入
from my_module import my_function
result = my_function()
# 修改my_module.py后...
importlib.reload(my_module)
from my_module import my_function # 重新导入
new_result = my_function() # 使用新代码
对于Web应用,很多框架内置了热重载。比如Flask在调试模式下会自动检测文件变化:
app.run(debug=True)
更复杂的情况可以考虑使用watchdog库监控文件变化,然后触发自定义的重载逻辑。不过要注意循环导入和状态保持的问题。
热启动在开发时很方便,但生产环境要谨慎使用。
总结:根据需求选择合适的重载方案。
用户的任何操作都必须落盘,不论用不用队列。你这里用 redis 是不对的,如果处理时队列挂了,你哪里恢复数据去?怎样重启流程?
redis 当然有重启的可能,重启时未落盘的数据都丢了,流程就没法继续了。这里不该用 redis。redis 放的只应该是可以随时丢掉的数据,业务数据不能放 redis。
为啥要杀死进程?正常不是应该保存好状态数据后才退出么。 如果是无法避免的异常退出,我觉得还是先解决 bug 要紧。
监听 signal,收到 kill 信号后处理完任务再 sys.exit
redis 队列试用于实时性要求高,但允许数据丢失的情形,比如抢票,秒杀这种也就几秒钟有用处的情形。
一般有硬盘 io backup 的队列系统读写都会慢不少,我用 beanstalk 读取一个 job 就要好几秒,这显然不能满足延时低低场景,但好处是宕机了再次启动 job 还在那里。
实际应用当然是看环境来取舍了。
少了个描述,每次处理完队列后,会更新到数据库信息,再加入到新的队列中。在处理队列信息时,如果重启服务,这些数据不还是丢失了么?
如何优雅的重启,这是个关键的问题,如何实现优雅的重启呢? nginx 一直会接受新的链接,A 服务不处理,B 服务也会处理,因为他们用的是同一个 Redis,都可以取到数据。所以我就想着分开 redis,通过 nginx 热更新不给 B 服务分配链接。这样子一个一个地重启服务。
确实,如果 Redis 挂了,存放在 Redis 中的队列数据就没了。之后的数据会存放在 python 自定义的队列中。
新的链接一直有过来,你想怎么保存好状态数据?存放 SQL ?
处理完任务?由于后台服务要一直保持开启,然后总会有新的链接过来,这链接我放哪去?
事实上我每处理完一个队列都会更新相关数据库信息,所以数据还在,但重启后,python 服务不会再继续处理之前的数据~
每次处理完队列后,会更新到数据库信息,再加入到新的队列中。不过在处理队列的过程中,如果重启服务,数据还是会丢失的。
首先你要实现楼上各位大大提到的 graceful shutdown (restart)
假设这是你的 Nginx 配置 (配置 1)
https://gist.github.com/soloradish/fd5a39b9e7126588e2bb55be682a208b
比如你要重启 8080 端口的这个服务, 可以在这个服务的重启脚本里面增加一步, 使用下面的配置 (配置 2) 替换原本的配置文件并 reload
https://gist.github.com/soloradish/9323db526e52667f3078f9e32fefbf54
然后等待 graceful restart
之后再把原本的配置 1 替换回来并 reload.
大概原理是这样, 这样可以避免在你重启的时候 nginx 还继续转发 requests 过来.
合理的 shutdown 流程:正在关闭 /重启的进程,收到 SIGTERM 先停止 redis subscribe,继续处理完已经收到的事件,然后结束进程。
如果你停止时是 kill -9,那就没什么可设计的了。肯定会丢至少一个请求。
SLB
这个 graceful restart 有何好建议吗?
同一楼意见,用 RabbitMQ。这是典型的应用场景。RabbitMQ 用 3 个进程 subscribe 一个 queue,queue 设置成要求
ack,一次取一个任务。完成后写入数据库,并发送 ack。如果 worker 进程在发送 ack 前挂了,RabbitMQ 会自动把失败的任务分配给其他活着的 worker。只有收到 ack 后,RabbitMQ 才会放心的认为任务完成,彻底清理掉。
那就是要在 shutdown 的时候停止循环啊。不然如果一直有新请求你就一直没法关闭了。
队列都是这样处理。redis 和 rabbitmq 都得这样。你得在某个时刻停止从队列取消息,才可能做到重启进程不丢消息。
目前我就在用 redis,目前出现过数据全部丢失的情况,redis 3.0+可以用 aof+rdb 持久化保持,用了之后数据几乎没出现过丢失,但是如果你是单机模式千万不要用,性能下降的厉害,如果是 cluster 可以用 slave 节点做 aof,应该是能保证节点数据不丢失,关于自动重启,redis 可以做到,3.2+支持 supervised


