设计¶
这部分是采用gunicorn的英文文档,同时加入自己的理解,源代码,以及参考资料等等
服务器模型¶
gunicorn是基于”pre-fork worker”模型,这就意味着有一个中心主控master进程,由它来管理一组worker进程。这个主控进程并不知晓任何客户端,所有的请求和响应都完全是由多个worker进程来处理
解释pre-fork¶
pre-fork服务器和fork服务器相似,通过一个单独的进程来处理每条请求。
不同的是,pre-fork服务器会通过预先开启大量的进程,等待并处理接到的请求。
由于采用了这种方式来开启进程,服务器并不需要等待新的进程启动而消耗时间,因而能够以更快的速度应付多用户请求。
另外,pre-fork服务器在遇到极大的高峰负载时仍能保持良好的性能状态。这是因为不管什么时候,只要预先设定的所有进程都已被用来处理请求时,服务器仍可追加额外的进程。
缺点是,当遇到高峰负载时,由于要启动新的服务器进程,不可避免地会带来响应的延迟。
主控master进程¶
主控master进程,就是一个简单的循环,用来不断侦听不同进程信号并作出不同的动作,仅此而已。它通过一些信号,诸如TTIN, TTOU, 和CHLD等等, 管理着那些正在运行的worker进程。
TTIN 和 TTOU信号是告诉主控master进程增加或减少正在运行的worker数量。
CHLD信号是在一个子进程已经中止之后,由主控master进程重启这个失效的worker进程。
我们看几段代码,在gunicorn/arbiter.py中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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进程的运行
下面是针对不同信号的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 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")
|
同步workers¶
大多数情况下,采用的worker类型是同步方式,也就是说一次仅处理一个请求。这种模型方式是最简单的,因为期间发生的任何错误最多只影响到一个请求。
尽管下面我们描述的内容,也是一次处理一个请求,但实际上在编写应用的时候,是加一些条件的。(??)
异步workers¶
可用的异步workers,主要是基于greenlets软件包(通过eventlet和gevent)。greenlet是用python来实现的协程方式(cooperative multi-threading)。通常情况下,我们编写的应用代码不需要作出什么改变,就能使用上这些异步workers的特性的。
tornado workers¶
还有一个可以用上的worker,是tornado worker,它是用在那些采用tornado框架的程序上。尽管tornado worker也可以用于wsgi程序上,但是这不是一个推荐的做法。
选择worker进程类型¶
默认的同步worker在cpu和带宽方面会消耗资源的。这就意味着你的应用不可能无节制地做任何事。例如,互联网上的一个请求,必须要遵守这个准则。
这个资源绑定的条件,也就是为什么我们会在gunicorn默认配置前做一个缓冲的代理。如果你把同步worker,直接暴露在internet上,一个dos(Denial of Service)攻击就会给服务器不停地制造大流量无用数据,而是服务器无法正常提供服务。Slowloris,就是这样一个有趣的例子,专门用来做这事的。
有些情况可以考虑采用异步worker:
- 需要长时间阻塞调用的应用,比如外部的web service
- 直接给internet提供服务
- 流请求和响应(是类似flv流么?)
- 长轮询
- Web sockets(WebSocket是HTML5规格中的一个非常重要的新特性,它的存在可以允许用户在浏览器中实现双向通信,实现数据的及时推送)
- Comet(基于 HTTP 长连接的“服务器推”技术,是一种新的 Web 应用架构。基于这种架构开发的应用中,服务器端会主动以异步的方式向客户端程序推送数据,而不需要客户端显式的发出请求。Comet 架构非常适合事件驱动的 Web 应用,以及对交互性和实时性要求很强的应用)
启动多少个workers?¶
不要试图做这样的事,你预期多少个客户端就启用多少个worker。gunicorn只需要启用4–12个workers,就足以每秒钟处理几百甚至上千个请求了。
在处理请求时,gunicorn依靠操作系统来提供负载均衡。通常我们推荐的worker数量是:(2 x $num_cores) + 1,这个公式很简单,它是基于给定的核心处理器数量,在其他worker处理请求时,每个worker将从socket那进行读写操作。
很显然,你的硬件环境和应用将影响到worker数量。我们推荐先采用上述公式来安排,在应用启动之后,然后再通过TTIN和TTOU这两个信号来调整worker数量。
记住:太多的worker,肯定会在某一个时刻,让你的整个系统急剧降低性能。(只能意译了)