pybottle搭配gunicorn+gevent部署时全局变量无法跨worker共享如何解决
问题原因
你碰到的计数跳变不是greenlet有独立副本导致的,核心原因是你配置了workers=50,Gunicorn会启动50个独立的工作进程,每个进程都有独立的内存地址空间,会各自初始化一份COUNT变量。同一个进程内的所有greenlet是共享进程内的全局变量的,你日志中相同内存地址的Greenlet对应同一个进程,COUNT能连续递增,出现跳变是因为请求被调度到了不同的worker进程。
解决方案
用多进程共享内存存储变量
可以用Python标准库multiprocessing提供的共享对象实现跨进程变量共享,操作时需要加锁避免竞态问题:from gevent import monkey monkey.patch_all() import bottle from multiprocessing import Value, Lock import sys app = bottle.Bottle() # 初始化共享整型变量,初始值为0,配套锁保证操作原子性 COUNT = Value('i', 0) COUNT_LOCK = Lock() @app.route('/inc', method='GET') def count(): with COUNT_LOCK: COUNT.value += 1 return str(COUNT.value) def main(): port = int(sys.argv[1]) app.run( server="gunicorn", host="0.0.0.0", port=port, workers=50, worker_class="gevent", debug=False, reloader=False, ) if __name__ == '__main__': main(sys.argv[1:])该方案无需引入额外组件,适合简单场景,缺点是进程共享内存容量有限,服务重启数据会丢失,且跨服务器部署时不生效。
用外部存储中间件持久化存储(生产环境推荐)
接入Redis这类支持原子操作的内存数据库存储计数,天然支持跨进程、跨服务器共享,还能避免竞态问题,服务重启数据也不会丢失:from gevent import monkey monkey.patch_all() import bottle import redis import sys app = bottle.Bottle() # 初始化Redis连接 r = redis.Redis(host='127.0.0.1', port=6379, db=0, decode_responses=True) # 初始化计数(第一次运行执行) r.setnx('count', 0) @app.route('/inc', method='GET') def count(): # Redis的INCR命令是原子操作,不需要额外加锁 new_count = r.incr('count') return str(new_count) def main(): port = int(sys.argv[1]) app.run( server="gunicorn", host="0.0.0.0", port=port, workers=50, worker_class="gevent", debug=False, reloader=False, ) if __name__ == '__main__': main(sys.argv[1:])改为单worker多协程模式
如果不需要利用多核CPU提升并发能力,可将workers参数改为1,所有请求都会在同一个进程内的不同greenlet中处理,全局变量天然共享,无需额外改造。缺点是无法利用多核资源,高并发场景下性能会弱于多worker模式。
内容的提问来源于stack exchange,提问作者ealeon
相关产品推荐
相关产品推荐

