下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

深入讲解Python程序中不同的重启机制

作者:课课家教育     来源: http://www.kokojia.com点击数:1212发布时间: 2017-05-19 11:05:36

标签: PythonLinux网络数据库

     大家对Python程序都比较熟悉了吧,但是有的可能不知道Python程序中不同的重启机制,那么今天课课家,就来和大家一起探讨一下。有需要的小伙伴,可以参考一下。文章里面有一些细节的知识,还望大家认真的阅读哦!

分析典型案例:

  Celery 分布式异步任务框架

  Gunicorn Web容器

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

  知识点:

  atexit

  os.execv

  模块共享变量

  信号处理

  sleep原理:select

  文件描述符共享

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

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

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

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

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

  对于Celery:

  def _reload_current_worker(): platforms.close_open_fds([ sys.__stdin__, sys.__stdout__, sys.__stderr__, ]) os.execv(sys.executable, [sys.executable] + sys.argv) def install_worker_restart_handler(worker, sig='SIGHUP'): def restart_worker_sig_handler(*args): """Signal handler restarting the current python program.""" import atexit atexit.register(_reload_current_worker) from celery.worker import state state.should_stop = EX_OK platforms.signals[sig] = restart_worker_sig_handler

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

  注册atexit函数

深入讲解Python程序中不同的重启机制_Python_Linux_网络_数据库_课课家

  设置全局变量

考虑到这个执行顺序,应该就能明白Celery 是Master和Worker都退出了,崭新呈现看过APUE的小伙伴,应该比较熟悉 atexit 了,这里也不多说.

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

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

  Python执行

  Python在执行时,首先会将.py文件中的源代码编译成Python的bytecode(字节码),然后再由PythonVirtualMachine(Python虚拟机)来执行这些编译好的bytecode。这种机制的基本思想跟java,.NET是一致的。然而,PythonVirtualMachine与Java或.NET的VirtualMachine不同的是,Python的VirtualMachine是一种更高级的VirtualMachine。这里的高级并不是通常意义上的高级,不是说Python的VirtualMachine比Java或.NET的功能更强大,而是说和Java或.NET相比,Python的VirtualMachine距离真实机器的距离更远。或者可以这么说,Python的VirtualMachine是一种抽象层次更高的VirtualMachine。

  基于C的Python编译出的字节码文件,通常是.pyc格式。

  除此之外,Python还可以以交互模式运行,比如主流操作系统Unix/Linux、Mac、Windows都可以直接在命令模式下直接运行Python交互环境。直接下达操作指令即可实现交互操作。

  在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的名字了,打印的时候会打出来:

  class Arbiter(object): def __init__(self, app): #一部分略 self.master_name = "Master" def handle_hup(self): """\\ HUP handling. - Reload configuration - Start the new worker processes with a new configuration - Gracefully shutdown the old worker processes """ self.log.info("Hang up: %s", self.master_name) self.reload()

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

  读取配置

  开启新worker

  优雅关闭旧Worker

深入讲解Python程序中不同的重启机制_Python_Linux_网络_数据库_课课家

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

  master只负责两件事情:

  拿着被Fork的worker的PID,以供关闭和处理

  1秒醒来一次,看看有没有worker超时了需要被干掉

  while True: sig = self.SIG_QUEUE.pop(0) if len(self.SIG_QUEUE) else None if sig is None: self.sleep() self.murder_workers() self.manage_workers() continue else: #处理信号

  在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也没看见有人用socketREUSE。。作为一个懒惰的人感觉很蛋疼。。。

     结束语:以上就是今天所讲的内容,大家都明白Python程序中不同的重启机制了吧!如果各位小伙伴还想了解更多关于这方面知识内容,随时可以登陆课课家教育平台哦!这里面有各方面的知识内容,来到这里一定会有所收获哟~

赞(17)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程