业务:用户发起购买请求-->将数据放入购买队列-->经过 N 个队列-->将购买结果告诉用户
场景:目前在一台机器上部署 3 个服务 server.py,当后台服务拿到原始数据后,一直是在同一个 Redis 的 N 个队列中轮流处理,处理完成才存入 DB,所以如果直接杀掉进程重启服务不仅会导致客户端无法请求,还会丢失部分数据。
方案:利用 nginx 的热重启与负载均衡,然后对后台服务进行拆分:一个后台服务对应着一个 Redis,这样后台服务之间就不会有数据影响,数据统计等定时任务单独作为一个服务。这样就有以下几个服务:
服务 A-->Redis a
服务 B-->Redis b
服务 C-->Redis c
数据统计等定时任务,服务 D
各位同学,场景如上,你们还有什么更好的方法或者建议么,欢迎大家来探讨一下。
1
zhengxiaowai 2017-05-31 11:13:32 +08:00
这里用 redis 不好,应该要选择其他的可以持久化的 MQ,比如 RabbitMQ。
挂掉以后,重启先检查当前队列,有没有历史数据。有的话处理,没有的话丢下一步。 |
2
sylecn 2017-05-31 11:16:41 +08:00 via Android
用几个 redis 是次要的。redis 自身又不重启。
核心在于你的 worker ( server.py )要支持 graceful shutdown。在重启 /更新时必须先停止接受新任务,处理完当前任务,然后再结束进程。 |
3
reus 2017-05-31 11:22:18 +08:00
用户的任何操作都必须落盘,不论用不用队列。你这里用 redis 是不对的,如果处理时队列挂了,你哪里恢复数据去?怎样重启流程?
|
4
reus 2017-05-31 11:25:12 +08:00
@sylecn redis 当然有重启的可能,重启时未落盘的数据都丢了,流程就没法继续了。这里不该用 redis。redis 放的只应该是可以随时丢掉的数据,业务数据不能放 redis。
|
5
enenaaa 2017-05-31 11:59:00 +08:00
为啥要杀死进程?正常不是应该保存好状态数据后才退出么。 如果是无法避免的异常退出,我觉得还是先解决 bug 要紧。
|
6
freestyle 2017-05-31 14:42:29 +08:00 via iPhone
监听 signal,收到 kill 信号后处理完任务再 sys.exit
|
7
zjsxwc 2017-05-31 15:39:25 +08:00
redis 队列试用于实时性要求高,但允许数据丢失的情形,比如抢票,秒杀这种也就几秒钟有用处的情形。
一般有硬盘 io backup 的队列系统读写都会慢不少,我用 beanstalk 读取一个 job 就要好几秒,这显然不能满足延时低低场景,但好处是宕机了再次启动 job 还在那里。 实际应用当然是看环境来取舍了。 |
8
qq450255457 OP @zhengxiaowai 少了个描述,每次处理完队列后,会更新到数据库信息,再加入到新的队列中。在处理队列信息时,如果重启服务,这些数据不还是丢失了么?
|
9
qq450255457 OP @reus 如何优雅的重启,这是个关键的问题,如何实现优雅的重启呢? nginx 一直会接受新的链接,A 服务不处理,B 服务也会处理,因为他们用的是同一个 Redis,都可以取到数据。所以我就想着分开 redis,通过 nginx 热更新不给 B 服务分配链接。这样子一个一个地重启服务。
确实,如果 Redis 挂了,存放在 Redis 中的队列数据就没了。之后的数据会存放在 python 自定义的队列中。 |
10
qq450255457 OP |
11
qq450255457 OP @enenaaa 新的链接一直有过来,你想怎么保存好状态数据?存放 SQL ?
|
12
qq450255457 OP @freestyle 处理完任务?由于后台服务要一直保持开启,然后总会有新的链接过来,这链接我放哪去?
|
13
qq450255457 OP @zjsxwc 事实上我每处理完一个队列都会更新相关数据库信息,所以数据还在,但重启后,python 服务不会再继续处理之前的数据~
|
14
qq450255457 OP 每次处理完队列后,会更新到数据库信息,再加入到新的队列中。不过在处理队列的过程中,如果重启服务,数据还是会丢失的。
|
15
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 过来. |
16
sylecn 2017-05-31 16:18:40 +08:00 via Android
合理的 shutdown 流程:正在关闭 /重启的进程,收到 SIGTERM 先停止 redis subscribe,继续处理完已经收到的事件,然后结束进程。
如果你停止时是 kill -9,那就没什么可设计的了。肯定会丢至少一个请求。 |
17
type 2017-05-31 17:52:28 +08:00
SLB
|
18
qq450255457 OP @roricon 这个 graceful restart 有何好建议吗?
|
19
qq450255457 OP @sylecn 停止 redis subscribe ?我的是线程循环地从指定的队列中取数据哦,跟这个有关系?
|
20
lightening 2017-05-31 19:30:54 +08:00
同一楼意见,用 RabbitMQ。这是典型的应用场景。RabbitMQ 用 3 个进程 subscribe 一个 queue,queue 设置成要求
ack,一次取一个任务。完成后写入数据库,并发送 ack。如果 worker 进程在发送 ack 前挂了,RabbitMQ 会自动把失败的任务分配给其他活着的 worker。只有收到 ack 后,RabbitMQ 才会放心的认为任务完成,彻底清理掉。 |
21
sylecn 2017-05-31 20:53:20 +08:00 via Android
@qq450255457 那就是要在 shutdown 的时候停止循环啊。不然如果一直有新请求你就一直没法关闭了。
队列都是这样处理。redis 和 rabbitmq 都得这样。你得在某个时刻停止从队列取消息,才可能做到重启进程不丢消息。 |
22
SlipStupig 2017-06-01 07:45:13 +08:00
目前我就在用 redis,目前出现过数据全部丢失的情况,redis 3.0+可以用 aof+rdb 持久化保持,用了之后数据几乎没出现过丢失,但是如果你是单机模式千万不要用,性能下降的厉害,如果是 cluster 可以用 slave 节点做 aof,应该是能保证节点数据不丢失,关于自动重启,redis 可以做到,3.2+支持 supervised
|