程序热启动方案讨论 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
qq450255457
V2EX    Python

程序热启动方案讨论

  •  1
     
  •   qq450255457 2017-05-31 10:28:09 +08:00 4987 次点击
    这是一个创建于 3136 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    服务 A-->Redis a

    服务 B-->Redis b

    服务 C-->Redis c

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

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

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

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

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

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

    队列都是这样处理。redis 和 rabbitmq 都得这样。你得在某个时刻停止从队列取消息,才可能做到重启进程不丢消息。
    SlipStupig
        22
    SlipStupig  
       2017-06-01 07:45:13 +08:00
    目前我就在用 redis,目前出现过数据全部丢失的情况,redis 3.0+可以用 aof+rdb 持久化保持,用了之后数据几乎没出现过丢失,但是如果你是单机模式千万不要用,性能下降的厉害,如果是 cluster 可以用 slave 节点做 aof,应该是能保证节点数据不丢失,关于自动重启,redis 可以做到,3.2+支持 supervised
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3168 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    /div> VERSION: 3.9.8.5 28ms UTC 04:36 PVG 12:36 LAX 20:36 JFK 23:36
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86