Redis为单线程同步,为何多gunicorn worker下Flask接口返回不稳定?
问题根本原因
你遇到的是非原子操作导致的竞态条件,和Redis本身单线程特性不冲突:
- Redis单线程仅保证单个Redis命令的执行是原子的,你当前的逻辑是「读取
seen键值 -> 本地判断参数是否存在 -> 写入新的seen键值」三个拆分操作,跨多个Redis命令的组合逻辑没有原子性保障 - 多gunicorn worker是独立进程,会出现多个进程同时读取到同一个旧的
seen列表,各自判断参数不存在后,先后将新的列表写回Redis,后写入的进程会覆盖先写入的进程的修改,导致部分参数没有被正确记录,最终表现为接口返回结果不确定。
另外你代码中的redis.put是笔误,redis-py的StrictRedis实例写入键值的方法为redis.set。同时用pickle序列化存储数据存在安全风险,若Redis数据被篡改,反序列化会触发任意代码执行。
修复方案
最优方案:使用Redis原生集合结构(推荐)
直接用Redis的集合(Set)数据类型存储已访问的参数,利用集合自带的原子判断、原子添加命令,完全避免竞态,同时不需要额外序列化操作:
@flask_app.route('/do_something/<string:foo>') def do_something(foo: str): try: # 原子添加元素,返回1表示添加成功,返回0表示元素已存在于集合中 add_result = redis.sadd('seen_set', foo) if add_result == 0: return 'Foo already seen', 500 # 执行业务逻辑 return 'Done' except ValueError: return 'Error during processing!', 500
这个方案单命令就完成了判断+添加的逻辑,完全原子,性能最高,也不需要提前初始化集合,键不存在时sadd会自动创建集合。
备选方案:保留原列表结构,用WATCH+事务保障原子性
如果一定要沿用原有的列表存储逻辑,可以通过Redis的WATCH命令监视seen键,只要键在事务执行前被其他进程修改,事务就会自动终止,避免覆盖修改:
@flask_app.route('/do_something/<string:foo>') def do_something(foo: str): with redis.pipeline() as pipe: while True: try: # 监视seen键,修改时事务中断 pipe.watch('seen') seen = pickle.loads(pipe.get('seen')) if foo in seen: pipe.unwatch() return 'Foo already seen', 500 seen.append(foo) # 开启事务执行写入 pipe.multi() pipe.set('seen', pickle.dumps(seen)) pipe.execute() # 执行业务逻辑 return 'Done' except redis.WatchError: # 键被其他进程修改,重试 continue except ValueError: return 'Error during processing!', 500
内容的提问来源于stack exchange,提问作者abelian
相关产品推荐
相关产品推荐

