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 · 4454 次点击
    这是一个创建于 2758 天前的主题,其中的信息可能已经有所发展或是发生改变。

    业务:用户发起购买请求-->将数据放入购买队列-->经过 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   ·   实用小工具   ·   2420 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 45ms · UTC 16:02 · PVG 00:02 · LAX 08:02 · JFK 11:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.