gunicorn和uwsgi lazy-apps模式
Python项目部署架构
对于Python项目部署,常采用Nginx + Uwsgi Server + Python App的架构,Nnginx负责反向代理和负载均衡以及静态文件的直接访问,Gunicorn和Uwsgi作为网关服务用来解析Http请求,后面的Flask/Django/Sanic只是个Application。
Uwsgi和Gunicorn是部署python项目常用的WSGI服务器,采用的是Master-Worker进程模型,即Master进程只负责管理监控Worker进程,请求实际由Worker进程处理。
Python项目连接池的安全使用
对于项目中经常使用到的(数据库)连接池等场景,在一些中大型的Python项目中可能需要特别关注。由于连接池等对象既不是进程安全的也不是线程安全的,所以在初始化配置和使用连接池的时候需要明确项目的运行架构,正确安全的配置和使用。
对于Master-Worker模型,Worker如何加载应用App,可能会严重影响我们对连接池等对象的使用。
Uwsgi和Gunicorn加载App的两种方式
一个是由Master加载完app后,Worker进行Fork操作,将App复制到其进程空间中。
另一种是采用Lazy-App的方式,Master进程不预先加载App,而是由每个Worker分别加载。
对于第一种情况,可能会导致项目中的一些代码和对象因跨进程出现问题,比如初始化了一个Mysql连接池对象,所有Worker进程共享该连接池,通过该连接池获取的连接在访问数据库的时候可能会出现异常情况。
正确的方式是采用Lazy-App的方式,使每个Worker加载App,每个进程单独管理连接池,同时在通过连接池获取连接的时候加上线程锁,保证每个进程的每个线程获取到的连接都是安全的。
在Uwsgi和Gunicorn中正确加载App
对于Uwsgi,可以在uwsgi.ini中添加参数
lazy-apps=true
,这样每个Worker进程会分别加载App,所有进程都有属于自己连接池对象。而对于Gunicorn,默认采用的是Lazy-App方式,但可以通过
--preload
参数关闭Lazy-App模式实现预加载。
连接持有的粒度
对于web系统,从连接池获取连接可以有两种方式
一是在请求进入时获取连接,然后整个请求处理过程都持有该连接,请求结束后释放。
二是在具体访问数据库的时候才获取连接,执行sql之前获取连接,sql执行完立即释放,即每次执行sql都获取连接。
第一种方式适用于整个请求需要在一个事务中处理的情况,但相比第二种方式每个请求占用数据库连接时间较长,对于有大量并发请求的场景,容易出现耗尽连接池,连接不够用的情况。第二种方式持有连接的粒度比较细,能承受更大的并发,建议在没有事务性要求的请求中采用第二种方式获取连接。