程序热启动方案讨论

2017-05-31 10:28:09 +08:00
 qq450255457

业务:用户发起购买请求-->将数据放入购买队列-->经过 N 个队列-->将购买结果告诉用户

场景:目前在一台机器上部署 3 个服务 server.py,当后台服务拿到原始数据后,一直是在同一个 Redis 的 N 个队列中轮流处理,处理完成才存入 DB,所以如果直接杀掉进程重启服务不仅会导致客户端无法请求,还会丢失部分数据。

方案:利用 nginx 的热重启与负载均衡,然后对后台服务进行拆分:一个后台服务对应着一个 Redis,这样后台服务之间就不会有数据影响,数据统计等定时任务单独作为一个服务。这样就有以下几个服务:

服务 A-->Redis a

服务 B-->Redis b

服务 C-->Redis c

数据统计等定时任务,服务 D

各位同学,场景如上,你们还有什么更好的方法或者建议么,欢迎大家来探讨一下。

4460 次点击
所在节点    Python
22 条回复
zhengxiaowai
2017-05-31 11:13:32 +08:00
这里用 redis 不好,应该要选择其他的可以持久化的 MQ,比如 RabbitMQ。
挂掉以后,重启先检查当前队列,有没有历史数据。有的话处理,没有的话丢下一步。
sylecn
2017-05-31 11:16:41 +08:00
用几个 redis 是次要的。redis 自身又不重启。
核心在于你的 worker ( server.py )要支持 graceful shutdown。在重启 /更新时必须先停止接受新任务,处理完当前任务,然后再结束进程。
reus
2017-05-31 11:22:18 +08:00
用户的任何操作都必须落盘,不论用不用队列。你这里用 redis 是不对的,如果处理时队列挂了,你哪里恢复数据去?怎样重启流程?
reus
2017-05-31 11:25:12 +08:00
@sylecn redis 当然有重启的可能,重启时未落盘的数据都丢了,流程就没法继续了。这里不该用 redis。redis 放的只应该是可以随时丢掉的数据,业务数据不能放 redis。
enenaaa
2017-05-31 11:59:00 +08:00
为啥要杀死进程?正常不是应该保存好状态数据后才退出么。 如果是无法避免的异常退出,我觉得还是先解决 bug 要紧。
freestyle
2017-05-31 14:42:29 +08:00
监听 signal,收到 kill 信号后处理完任务再 sys.exit
zjsxwc
2017-05-31 15:39:25 +08:00
redis 队列试用于实时性要求高,但允许数据丢失的情形,比如抢票,秒杀这种也就几秒钟有用处的情形。

一般有硬盘 io backup 的队列系统读写都会慢不少,我用 beanstalk 读取一个 job 就要好几秒,这显然不能满足延时低低场景,但好处是宕机了再次启动 job 还在那里。

实际应用当然是看环境来取舍了。
qq450255457
2017-05-31 15:43:57 +08:00
@zhengxiaowai 少了个描述,每次处理完队列后,会更新到数据库信息,再加入到新的队列中。在处理队列信息时,如果重启服务,这些数据不还是丢失了么?
qq450255457
2017-05-31 15:48:29 +08:00
@reus 如何优雅的重启,这是个关键的问题,如何实现优雅的重启呢? nginx 一直会接受新的链接,A 服务不处理,B 服务也会处理,因为他们用的是同一个 Redis,都可以取到数据。所以我就想着分开 redis,通过 nginx 热更新不给 B 服务分配链接。这样子一个一个地重启服务。
确实,如果 Redis 挂了,存放在 Redis 中的队列数据就没了。之后的数据会存放在 python 自定义的队列中。
qq450255457
2017-05-31 15:50:12 +08:00
qq450255457
2017-05-31 15:52:01 +08:00
@enenaaa 新的链接一直有过来,你想怎么保存好状态数据?存放 SQL ?
qq450255457
2017-05-31 15:53:29 +08:00
@freestyle 处理完任务?由于后台服务要一直保持开启,然后总会有新的链接过来,这链接我放哪去?
qq450255457
2017-05-31 15:55:50 +08:00
@zjsxwc 事实上我每处理完一个队列都会更新相关数据库信息,所以数据还在,但重启后,python 服务不会再继续处理之前的数据~
qq450255457
2017-05-31 15:59:46 +08:00
每次处理完队列后,会更新到数据库信息,再加入到新的队列中。不过在处理队列的过程中,如果重启服务,数据还是会丢失的。
roricon
2017-05-31 16:14:24 +08:00
首先你要实现楼上各位大大提到的 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 过来.
sylecn
2017-05-31 16:18:40 +08:00
合理的 shutdown 流程:正在关闭 /重启的进程,收到 SIGTERM 先停止 redis subscribe,继续处理完已经收到的事件,然后结束进程。

如果你停止时是 kill -9,那就没什么可设计的了。肯定会丢至少一个请求。
type
2017-05-31 17:52:28 +08:00
SLB
qq450255457
2017-05-31 18:58:43 +08:00
@roricon 这个 graceful restart 有何好建议吗?
qq450255457
2017-05-31 19:01:48 +08:00
@sylecn 停止 redis subscribe ?我的是线程循环地从指定的队列中取数据哦,跟这个有关系?
lightening
2017-05-31 19:30:54 +08:00
同一楼意见,用 RabbitMQ。这是典型的应用场景。RabbitMQ 用 3 个进程 subscribe 一个 queue,queue 设置成要求
ack,一次取一个任务。完成后写入数据库,并发送 ack。如果 worker 进程在发送 ack 前挂了,RabbitMQ 会自动把失败的任务分配给其他活着的 worker。只有收到 ack 后,RabbitMQ 才会放心的认为任务完成,彻底清理掉。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/364850

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX