安装相关包
Flask==0.1
gunicorn==0.2
Jinja2==2.11.3
MarkupSafe==1.1.1
Werkzeug==0.6.1
测试代码
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
注:以下所说子进程一般代指worker工作进程
gunicorn采用了pre-fork模式的设计, 主进程+多个子进程,主进程负责管理子进程,子进程负责HTTP请求的处理. 子进程的处理HTTP对父进程是不可见的.
在程序运行, 主进程会一直循环运行, 在循环之前就已经创建好了子进程, 循环期间检测外部信号和检查子进程健康情况, 从而作出对应处理. 主进程会监听信号,例如, 当收到终止信号, 会将一个个子进程杀死. 杀死子进程分为优雅停止和直接杀死. 优雅停止子进程能够让当前请求处理完再结束, 新的在等待(sock.accept)的请求将不会开始.
子进程会不断从监听的sock连接中,获取client客户端, 当得到一个client, 就会一直读取http数据流数据,直到一个完整的http request报文数据获取到,就会构造出wsgi协议需要的数据, 调用wsgi application(Flask等), 然后就得到了http response, 再通过client sock将response数据send出去.
gunicorn中的同步worker是只能每个请求过来,处理完成就关闭(关闭的是client而不是listen),所以不管http keep-alive是否是1.1, 都无法生效(使用异步worker支持).
子进程是否共用一个tcp监听连接?
是的(子进程从主进程复制了一份), 主进程创建了TCP连接, 并且在子进程创建的时候传递给了子进程
worker = Worker(i, self.pid, self.LISTENER, self.modname,
self.timeout)
杀死主进程的流程
通常主进程会阻塞在sleep中, gunicorn通过管道实现了休眠等待信号的逻辑, 当按下ctrl+c
会走到except select.error, e:
的逻辑, EINTR: 终止信号影响了select.select
def sleep(self):
try:
ready = select.select([self.PIPE[0]], [], [], 100.0)
if not ready[0]:
return
while os.read(self.PIPE[0], 1):
pass
except select.error, e:
if e[0] not in [errno.EAGAIN, errno.EINTR]:
raise
except OSError, e:
if e.errno not in [errno.EAGAIN, errno.EINTR]:
raise
except KeyboardInterrupt:
print 3
sys.exit()
当跳出sleep, 就会执行对应信号的处理逻辑, 如果是停止服务, 就会去遍历当前所有子进程, 通过子进程的id去kill掉
def kill_workers(self, sig):
for pid in self.WORKERS.keys():
self.kill_worker(pid, sig)
def kill_worker(self, pid, sig):
worker = self.WORKERS.pop(pid)
try:
os.kill(pid, sig)
kpid, stat = os.waitpid(pid, os.WNOHANG)
if kpid:
self.log.warning("Problem killing process: %s" % pid)
except OSError, e:
if e.errno == errno.ESRCH:
try:
worker.tmp.close()
except:
pass
主进程如何检查子进程的健康情况
子进程通过不断一个修改一个自己的临时文件的inode信息, 主进程在循环中不断检查这个inode信息的stat.st_ctime(i节点最后更改时间)来判断子进程是否正常.
解析HTTP报文是gunicorn做还是Flask做?
HTTP报文详细的由Flask解析, 通过wsgi的wsgi_input流数据解析. gunicorn解析了一些基础信息
environ = {
"wsgi.url_scheme": 'http',
"wsgi.input": wsgi_input,
"wsgi.errors": sys.stderr,
"wsgi.version": (1, 0),
"wsgi.multithread": False,
"wsgi.multiprocess": True,
"wsgi.run_once": False,
"SCRIPT_NAME": "",
"SERVER_SOFTWARE": self.SERVER_VERSION,
"REQUEST_METHOD": self.parser.method,
"PATH_INFO": unquote(self.parser.path),
"QUERY_STRING": self.parser.query_string,
"RAW_URI": self.parser.raw_path,
"CONTENT_TYPE": self.parser.headers_dict.get('Content-Type', ''),
"CONTENT_LENGTH": str(wsgi_input.len),
"REMOTE_ADDR": self.client_address[0],
"REMOTE_PORT": self.client_address[1],
"SERVER_NAME": self.server_address[0],
"SERVER_PORT": self.server_address[1],
"SERVER_PROTOCOL": self.parser.raw_version
}
多个子进程一起去accept客户端client会怎样?
在旧的内核中会产生多个子进程同时唤醒(惊群问题), 新的内核只有一个能得到client.
假设A B C三子进程同时在select.select
, 那么只会有其中一个返回, 例如是B, 现在的等待队列之后A C(因为B在处理报文了)
网友评论