Python程序中不同的重启机制

简介:

分析典型案例:

  1. Celery 分布式异步任务框架
  2. Gunicorn Web容器

之所以挑这两个,不仅仅是应用广泛,而且两个的进程模型比较类似,都是Master、Worker的形式,在热重启上思路和做法又基本不同,比较有参考意义

知识点:

  • atexit
  • os.execv
  • 模块共享变量
  • 信号处理
  • sleep原理:select
  • 文件描述符共享

这几个知识点不难,区别只在于Celery和Gunicorn的应用方式。如果脑海中有这样的知识点,这篇文章也就是开阔下视野而已。。。

Celery和Gunicorn都会在接收到HUP信号时,进行热重启操作

Celery的重启:关旧Worker,然后execv重新启动整个进程

Gunicorn的重启:建立新Worker,再关旧Worker,Master不动

下面具体的看下它们的操作和核心代码。

对于Celery:


 
 
  1. def _reload_current_worker(): 
  2.     platforms.close_open_fds([ 
  3.         sys.__stdin__, sys.__stdout__, sys.__stderr__, 
  4.     ]) 
  5.     os.execv(sys.executable, [sys.executable] + sys.argv) 
  6.   
  7.   
  8.    
  9. def install_worker_restart_handler(worker, sig='SIGHUP'): 
  10.     def restart_worker_sig_handler(*args): 
  11.         """Signal handler restarting the current python program.""" 
  12.         import atexit 
  13.         atexit.register(_reload_current_worker) 
  14.         from celery.worker import state 
  15.         state.should_stop = EX_OK 
  16.     platforms.signals[sig] = restart_worker_sig_handler 

HUP上挂的restart_worker_sig_handler 就做了两件事:

  1. 注册atexit函数
  2. 设置全局变量

考虑到这个执行顺序,应该就能明白Celery 是Master和Worker都退出了,崭新呈现。。

看过APUE的小伙伴,应该比较熟悉 atexit 了,这里也不多说。os.execv还挺有意思,根据Python文档,这个函数会执行一个新的函数用于替换掉 当前进程 ,在Unix里,新的进程直接把可执行程序读进进程,保留同样的PID。

在Python os标准库中,这是一整套基本一毛一样的函数,也许应该叫做函数族了:

  • os. execl ( path , arg0 , arg1 , … )
  • os. execle ( path , arg0 , arg1 , … , env )
  • os. execlp ( file , arg0 , arg1 , … )
  • os. execlpe ( file , arg0 , arg1 , … , env )
  • os. execv ( path , args )
  • os. execve ( path , args , env )
  • os. execvp ( file , args )
  • os. execvpe ( file , args , env )

以exec开头,后缀中的l和v两种,代表命令行参数是否是变长的(前者不可变),p代表是否使用PATH定位程序文件,e自然就是在新进程中是否使用ENV环境变量了

然后给worker的state.should_stop变量设置成False。。。 模块共享变量 这个是 Python FAQ里提到的一种方便的跨模块消息传递的方式,运用了Python module的单例。我们都知道Python只有一个进程,所以单例的变量到处共享

而should_stop这个变量也是简单粗暴,worker在执行任务的循环中判断这个变量,即执行异步任务->查看变量->获得异步任务->继续执行 的循环中,如果True就抛出一个【应该关闭】异常,worker由此退出。

这里面有一个不大不小的坑是:信号的发送对于外部的工具,例如kill,是非阻塞的,所以当HUP信号被发出后,worker可能并没有完成重启(等待正在执行的旧任务完成 才退出和新建),因此如果整个系统中只使用HUP信号挨个灰度各个机器,那么很有可能出现全部worker离线的情况

接下来我们看看Gunicorn的重启机制:

信号实质上挂在在Arbiter上,Arbiter相当于master,守护和管理worker的,管理各种信号,事实上它init的时候就给自己起好Master的名字了,打印的时候会打出来:


 
 
  1. class Arbiter(object): 
  2.     def __init__(self, app): 
  3.         #一部分略 
  4.         self.master_name = "Master"    
  5.     def handle_hup(self): 
  6.         """\ 
  7.         HUP handling. 
  8.         - Reload configuration 
  9.         - Start the new worker processes with a new configuration 
  10.         - Gracefully shutdown the old worker processes 
  11.         ""
  12.         self.log.info("Hang up: %s", self.master_name) 
  13.         self.reload() 

这里的函数文档里写了处理HUP信号的过程了,简单的三行:

  1. 读取配置
  2. 开启新worker
  3. 优雅关闭旧Worker

reload函数的实现本身没什么复杂的,因为Gunicorn 是个Web容器,所以master里面是没有业务逻辑的,worker都是master fork出来的,fork是可以带着文件描述符(自然也包括socket)过去的。这也是Gunicorn可以随意增减worker的根源

master只负责两件事情:

  1. 拿着被Fork的worker的PID,以供关闭和处理
  2. 1秒醒来一次,看看有没有worker超时了需要被干掉

 
 
  1. while True
  2.     sig = self.SIG_QUEUE.pop(0) if len(self.SIG_QUEUE) else None 
  3.     if sig is None: 
  4.         self.sleep() 
  5.         self.murder_workers() 
  6.         self.manage_workers() 
  7.         continue 
  8.     else
  9.         #处理信号 

在sleep函数中,使用了select.select+timeout实现,和time.sleep的原理是一样的,但不同之处在于select监听了自己创建的一个PIPE,以供wakeup立即唤醒

总结

以上就介绍了Celery和Gunicorn的重启机制差异。

从这两者的设计来看,可以理解他们这样实现的差异。

Celery是个分布式、异步的任务队列,任务信息以及排队信息实质上是持久化在外部的MQ中的,例如RabbitMQ和redis,其中的持久化方式,这应该另外写一篇《高级消息队列协议AMQP介绍》,就不在这里说了,对于Celery的Master和Worker来说,可以说是完全没有状态的。由Celery的部署方式也可以知道,近似于一个服务发现的框架,下线的Worker不会对整个分布式系统带来任何影响。唯一的例外可能是Beat组件,作为Celery定时任务的节拍器,要做少许改造以适配分布式的架构,并且实现Send Once功能。

Gunicorn作为Python的Web容器之一,会接收用户的请求,虽然我们通常都会使用nginx放在Gunicorn前方做反向代理,通常也可以使用nginx来做upstream offline、online的热重启,但那就不是一个层次的事情了

这里回头来再吐槽Golang

项目中使用到了Golang的一个Web框架,Golang在1.8中也已经支持Http.Server的热关闭了,但是一是因为刚出不就(竟然现在才出),二是因为Golang的进程模型和Python大相近庭,go协程嘛,目前还没有看到那个Web框架中真正实现Gunicorn类似的热重启。

Golang 在fcgi的操作应该就类似Python之于wsgi了。。我感觉我是选择错了一个web框架

也没看见有人用syscall.Exec来用一下execve系统调用,Golang也没看见有人用socket REUSE。。作为一个懒惰的人感觉很蛋疼。。。

先让nginx大法做这件事情好了。


本文作者:佚名

来源:51CTO

相关文章
|
5月前
|
机器学习/深度学习 数据挖掘 Python
Python编程入门——从零开始构建你的第一个程序
【10月更文挑战第39天】本文将带你走进Python的世界,通过简单易懂的语言和实际的代码示例,让你快速掌握Python的基础语法。无论你是编程新手还是想学习新语言的老手,这篇文章都能为你提供有价值的信息。我们将从变量、数据类型、控制结构等基本概念入手,逐步过渡到函数、模块等高级特性,最后通过一个综合示例来巩固所学知识。让我们一起开启Python编程之旅吧!
101 1
|
1月前
|
Python
[oeasy]python074_ai辅助编程_水果程序_fruits_apple_banana_加法_python之禅
本文回顾了从模块导入变量和函数的方法,并通过一个求和程序实例,讲解了Python中输入处理、类型转换及异常处理的应用。重点分析了“明了胜于晦涩”(Explicit is better than implicit)的Python之禅理念,强调代码应清晰明确。最后总结了加法运算程序的实现过程,并预告后续内容将深入探讨变量类型的隐式与显式问题。附有相关资源链接供进一步学习。
38 4
|
3月前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
282 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
3月前
|
存储 NoSQL 数据库连接
在Python程序中实现LevelDB的海量key的分批次扫描
通过本文的步骤,您可以在Python程序中实现对LevelDB海量key的分批次扫描。这样不仅能够有效地管理大规模数据,还可以避免一次性加载过多数据到内存中,提高程序的性能和稳定性。希望这篇指南能为您的开发工作提供实用的帮助。
104 28
|
3月前
|
缓存 JSON 数据处理
Python进阶:深入理解import机制与importlib的妙用
本文深入解析了Python的`import`机制及其背后的原理,涵盖基本用法、模块缓存、导入搜索路径和导入钩子等内容。通过理解这些机制,开发者可以优化模块加载速度并确保代码的一致性。文章还介绍了`importlib`的强大功能,如动态模块导入、实现插件系统及重新加载模块,展示了如何利用这些特性编写更加灵活和高效的代码。掌握这些知识有助于提升编程技能,充分利用Python的强大功能。
92 4
|
5月前
|
存储 Python
Python编程入门:打造你的第一个程序
【10月更文挑战第39天】在数字时代的浪潮中,掌握编程技能如同掌握了一门新时代的语言。本文将引导你步入Python编程的奇妙世界,从零基础出发,一步步构建你的第一个程序。我们将探索编程的基本概念,通过简单示例理解变量、数据类型和控制结构,最终实现一个简单的猜数字游戏。这不仅是一段代码的旅程,更是逻辑思维和问题解决能力的锻炼之旅。准备好了吗?让我们开始吧!
|
4月前
|
安全 API C语言
Python程序的安全逆向(关于我的OPENAI的APIkey是如何被盗的)
本文介绍了如何使用C语言编写一个简单的文件加解密程序,并讨论了如何为编译后的软件添加图标。此外,文章还探讨了Python的.pyc、.pyd等文件的原理,以及如何生成和使用.pyd文件来增强代码的安全性。通过视频和教程,作者详细讲解了生成.pyd文件的过程,并分享了逆向分析.pyd文件的方法。最后,文章提到可以通过定制Python解释器来进一步保护源代码。
121 6
|
4月前
|
IDE 程序员 开发工具
Python编程入门:打造你的第一个程序
迈出编程的第一步,就像在未知的海洋中航行。本文是你启航的指南针,带你了解Python这门语言的魅力所在,并手把手教你构建第一个属于自己的程序。从安装环境到编写代码,我们将一步步走过这段旅程。准备好了吗?让我们开始吧!
|
5月前
|
开发者 Python
使用Python实现自动化邮件通知:当长时程序运行结束时
本文介绍了如何使用Python实现自动化邮件通知功能,当长时间运行的程序完成后自动发送邮件通知。主要内容包括:项目背景、设置SMTP服务、编写邮件发送函数、连接SMTP服务器、发送邮件及异常处理等步骤。通过这些步骤,可以有效提高工作效率,避免长时间等待程序结果。
196 9
|
5月前
|
Python
在Python中,`try...except`语句用于捕获和处理程序运行时的异常
在Python中,`try...except`语句用于捕获和处理程序运行时的异常
127 5

热门文章

最新文章

下一篇
oss创建bucket