今天聊聊,我在用python开发服务端时,用到的master worker进程管理模型…. 上次在外面做分享的时候,不少人对这个Master worker模式很感兴趣… 我想说的是,python写服务端虽然性能没有C,哪怕新潮的Golang强劲,但是他的扩展丰富,相关的模块也很丰富…. So,我们这边大量的业务都采用python构建分布式系统,比如中心节点调度、RPC服务、对外供数API、公网自动化爬虫….. 下次有时间,给大家讲讲我们的自动化公网爬虫的逻辑,是个很奇特的东西,可以做很广的横向扩展……
上面的这个socket架构很像nginx的模式,他们会预先启动多个进程,并且监听在一个端口上… 不要吃惊,在linux下是支持多个进程同时监听在一个端口上的… 至于如何调度,那就要靠抢了…. 内核自己会做相关的io调度…
master-worker模型是什么?不管是apahce、nginx、php-fpm 都在使用这个模型.
master-worker是基于”pre-fork worker”模型加强版,这就意味着有一个中心主控master进程,由它来管理一组worker进程。这个主控进程并不知晓任何客户端,所有的请求和响应都完全是由多个worker进程来处理。那么master进行干嘛? 他主要是动态的管理已经启动的进程,比如动态的添加减少进程…如果你的逻辑有redis、mongodb、mysql的连接池,很有可能因为链接失败的异常,导致你的逻辑异常退出…还有内存异常的情况. master worker很好的控制这种情况…
那么继续来解释下master-worker的woker是怎么实现的,就是刚才说的pre-fork
这里标注下该文章的原文链接… http://xiaorui.cc/?p=1735
pre-fork服务器会通过预先开启大量的进程,等待并处理接到的请求,当然肯定不是那种一个request一个进程的模式,因为资源消耗是在太大了…
那么由于采用了预先prefork方式来开启进程,所以能够以更快的速度应付多用户请求。另外,pre-fork服务器在遇到极大的高峰负载时仍能保持良好的性能状态。这是因为不管什么时候,只要预先设定的所有进程都已被用来处理请求时,服务器仍可追加额外的进程。
缺点是,当遇到高峰负载时,由于要启动新的服务器进程,不可避免地会带来响应的延迟。 当然也有人说了,为毛不用线程…. 问的好,其实nginx就是多进程的模式,只是他背靠着epoll模型,所以他不存在太多的堵塞问题… 如果真的用类似,gunicorn uwsgi这样的wsgi,会发现你启动8个进程,但是因为种种问题,产生了io堵塞,那么第九个访问者肯定阻塞在socket就绪队列上…. 如果你没有epoll,那么还是推荐你用多进程加线程或者是协程的模式…. 不扯了…..
主控master进程
主控master进程,就是一个简单的循环,用来不断侦听不同进程信号并作出不同的动作,仅此而已。它通过一些信号,诸如TTIN, TTOU, 和CHLD等等, 管理着那些正在运行的worker进程。
TTIN 和 TTOU信号是告诉主控master进程增加或减少正在运行的worker数量。
CHLD信号是在一个子进程已经中止之后,由主控master进程重启这个失效的worker进程。
话说gunicorn也是这么个模型… 我们看几段代码,在gunicorn/arbiter.py中
class Arbiter(object): """ Arbiter maintain the workers processes alive. It launches or kills them if needed. It also manages application reloading via SIGHUP/USR2. """ # A flag indicating if a worker failed to # to boot. If a worker process exist with # this error code, the arbiter will terminate. WORKER_BOOT_ERROR = 3 START_CTX = {} LISTENER = None WORKERS = {} PIPE = [] # I love dynamic languages SIG_QUEUE = [] SIGNALS = map( lambda x: getattr(signal, "SIG%s" % x), "HUP QUIT INT TERM TTIN TTOU USR1 USR2 WINCH".split() ) SIG_NAMES = dict( (getattr(signal, name), name[3:].lower()) for name in dir(signal) if name[:3] == "SIG" and name[3] != "_" ) def __init__(self, app): os.environ["SERVER_SOFTWARE"] = SERVER_SOFTWARE self.setup(app) self.pidfile = None self.worker_age = 0 self.reexec_pid = 0 self.master_name = "Master"
在上面代码中的第23行中,列举了相应的多个信号:
HUP,重启所有的配置和所有的worker进程
QUIT,正常关闭,它会等待所有worker进程处理完各自的东西后关闭
INT/TERM,立即关闭,强行中止所有的处理
TTIN,增加一个worker进程
TTOU,减少一个worker进程
USR1,重新打开由master和worker所有的日志处理
USR2,重新运行master和worker
WINCH,正常关闭所有worker进程,保持主控master进程的运行
下面是针对不同信号的代码:
def handle_chld(self, sig, frame): "SIGCHLD handling" self.wakeup() 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() def handle_quit(self): "SIGQUIT handling" raise StopIteration def handle_int(self): "SIGINT handling" self.stop(False) raise StopIteration def handle_term(self): "SIGTERM handling" self.stop(False) raise StopIteration def handle_ttin(self): """\ SIGTTIN handling. Increases the number of workers by one. """ self.num_workers += 1 self.manage_workers() def handle_ttou(self): """\ SIGTTOU handling. Decreases the number of workers by one. """ if self.num_workers <= 1: return self.num_workers -= 1 self.manage_workers() def handle_usr1(self): """\ SIGUSR1 handling. Kill all workers by sending them a SIGUSR1 """ self.kill_workers(signal.SIGUSR1) self.log.reopen_files() def handle_usr2(self): """\ SIGUSR2 handling. Creates a new master/worker set as a slave of the current master without affecting old workers. Use this to do live deployment with the ability to backout a change. """ self.reexec() def handle_winch(self): "SIGWINCH handling" if os.getppid() == 1 or os.getpgrp() != os.getpid(): self.log.info("graceful stop of workers") self.num_workers = 0 self.kill_workers(signal.SIGQUIT) else: self.log.info("SIGWINCH ignored. Not daemonized")
这就是效果.
root 15076 0.0 0.0 37388 7912 ? S Jul11 0:07 | \_ master: extractor root 15756 8.5 0.5 396384 188012 ? R Jul12 8:55 | | \_ worker: extractor root 17555 9.8 0.5 388768 179772 ? S Jul12 10:14 | | \_ worker: extractor root 24508 0.1 0.1 262844 61660 ? S Jul12 0:03 | | \_ worker: extractor root 29469 2.6 0.5 380156 171052 ? R Jul12 1:36 | | \_ worker: extractor root 7986 0.1 0.1 268396 63108 ? S Jul12 0:03 | | \_ worker: extractor root 7999 14.1 0.5 393452 183664 ? R Jul12 6:09 | | \_ worker: extractor root 25418 0.1 0.1 266660 61460 ? S Jul12 0:03 | | \_ worker: extractor root 29435 0.1 0.1 259764 54832 ? S Jul12 0:03 | | \_ worker: extractor root 4000 6.2 0.5 373508 165268 ? S Jul12 2:00 | | \_ worker: extractor root 23509 0.2 0.1 256776 55724 ? S 00:04 0:03 | | \_ worker: extractor root 2557 0.3 0.1 265668 58792 ? S 00:08 0:03 | | \_ worker: extractor root 12307 1.8 0.3 316224 109304 ? R 00:12 0:18 | | \_ worker: extractor root 15691 0.3 0.1 267792 61004 ? S 00:14 0:03 | | \_ worker: extractor root 17329 0.4 0.1 263180 60552 ? S 00:14 0:04 | | \_ worker: extractor root 29317 0.5 0.1 261692 59020 ? S 00:19 0:03 | | \_ worker: extractor root 7174 0.9 0.1 266872 61944 ? S 00:22 0:03 | | \_ worker: extractor
root 2027 0.0 0.0 45800 7248 ? S Apr29 10:30 | \_ master: engine root 1454 0.3 0.1 292884 91480 ? Sl 00:16 0:02 | | \_ worker: engine root 1457 0.2 0.1 292892 91476 ? Sl 00:16 0:02 | | \_ worker: engine root 1458 0.2 0.1 292892 91488 ? Sl 00:16 0:02 | | \_ worker: engine root 1465 0.2 0.1 292888 91484 ? Sl 00:16 0:02 | | \_ worker: engine root 1468 0.2 0.1 292888 91472 ? Sl 00:16 0:02 | | \_ worker: engine root 1473 0.2 0.1 292888 91484 ? Sl 00:16 0:02 | | \_ worker: engine root 1477 0.2 0.1 292888 91480 ? Sl 00:16 0:02 | | \_ worker: engine root 1483 0.2 0.1 292896 91488 ? Sl 00:16 0:02 | | \_ worker: engine root 1484 0.2 0.1 292892 91480 ? Sl 00:16 0:02 | | \_ worker: engine root 1496 0.2 0.1 294956 91532 ? Sl 00:16 0:02 | | \_ worker: engine root 1502 0.2 0.1 292896 91484 ? Sl 00:16 0:02 | | \_ worker: engine root 1510 0.2 0.1 292892 91488 ? Sl 00:16 0:02 | | \_ worker: engine root 1515 0.2 0.1 292896 91488 ? Sl 00:16 0:01 | | \_ worker: engine root 1527 0.2 0.1 292892 91484 ? Sl 00:16 0:01 | | \_ worker: engine root 1529 0.2 0.1 292892 91476 ? Sl 00:16 0:02 | | \_ worker: engine root 1538 0.2 0.1 292896 91480 ? Sl 00:16 0:02 | | \_ worker: engine
启动多少个workers?
不要试图开几百个进程,你预期多少个客户端就启用多少个worker。gunicorn只需要启用4–12个workers,就足以每秒钟处理几百甚至上千个请求了…
话说,python实现服务端相对来说容易些,有不少好的封装… … 但是设计到高性能的方面,还是需要借鉴各类分布式的服务端架构… 总会有收获的….
大家可以借鉴gunirorn的思路,自己实现一个master woker的python进程管理接口… 这两天我会把项目中,我自己封装的一个进程管理模型提交到pypi上….
太棒了,不用担心被oom了
有遇到过僵尸问题么
编辑器? 就是默认的呀。。。 wp默认的….
想问下楼主用的什么编辑器,主题很舒服想问下楼主用的什么编辑器。主题看起来很舒服