python使用master worker管理模型开发服务端

今天聊聊,我在用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上….


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

4 Responses

  1. pcba_wang 2015年7月16日 / 上午9:52

    太棒了,不用担心被oom了

  2. 自由行 2015年7月14日 / 上午8:41

    有遇到过僵尸问题么

  3. 峰云就她了 2015年7月13日 / 上午10:15

    编辑器? 就是默认的呀。。。 wp默认的….

  4. ArtsCrafts 2015年7月13日 / 上午2:43

    想问下楼主用的什么编辑器,主题很舒服想问下楼主用的什么编辑器。主题看起来很舒服

发表评论

电子邮件地址不会被公开。 必填项已用*标注