← 返回题目列表

Flask 多 worker 部署时为什么不能依赖全局变量保存状态?

高频 中等 第 3 / 27 题 更新于 2026/07/31
Flask多进程全局变量Gunicorn

简化版

Flask 生产部署常用 Gunicorn/uWSGI 多进程 worker。每个 worker 有自己的内存空间,全局变量不会在 worker 之间共享;请求打到不同 worker 时会看到不同状态。因此不能用全局变量保存用户会话、计数器、任务状态等业务数据,应使用数据库、Redis 或外部存储。

详细版

开发环境单进程里,全局变量看起来能保存状态:

counter = 0

@app.get("/count")
def count():
    global counter
    counter += 1
    return {"counter": counter}

生产如果有 4 个 worker,每个 worker 都有自己的 counter。用户连续请求可能分别命中不同 worker,看到 1、1、2、1 这类结果。面试重点是:全局变量只在当前进程内有效,不是跨请求可靠状态存储。

完整版教学

一、为什么开发环境容易误导人

开发服务器常是单进程单实例,全局变量看起来“能用”。但生产为了利用多核和提高稳定性,通常会启动多个 worker 进程。

Gunicorn master
  worker 1: counter=10
  worker 2: counter=3
  worker 3: counter=8

这些 worker 之间内存不共享。一个请求改了 worker 1 的全局变量,worker 2 完全不知道。

二、进程内存隔离是什么

操作系统进程有独立地址空间。Python 全局变量存在进程内存里,不会自动同步到其他进程。多线程共享内存,多进程不共享内存。

线程: 同进程,共享全局变量
进程: 独立内存,全局变量各一份

Flask 多 worker 本质上是多进程模型,所以全局状态各自一份。

三、负载均衡会放大问题

Nginx、Gunicorn master 或平台负载均衡会把请求分配给不同 worker。用户不一定总打到同一个进程。即使有粘性会话,也不应该依赖内存状态,因为 worker 重启就丢。

请求1 -> worker A -> cart=[book]
请求2 -> worker B -> cart=[]

这就是为什么购物车、登录态、任务进度不能放普通全局变量。

四、哪些状态可以放全局变量

只读配置、加载后不变的模型、线程安全的客户端对象可以谨慎放全局,作为进程内缓存。但业务可变状态不应放全局。

状态是否适合全局
常量配置可以
数据库连接池对象视扩展管理方式
用户 session不适合
订单状态不适合
本地只读模型可以但注意内存

全局变量可以做进程内只读缓存,不能做跨 worker 事实来源。

五、正确存储位置怎么选

用户会话可用 Flask session、Redis session 或数据库;计数器用 Redis atomic incr 或数据库;任务状态用 Celery backend、Redis、数据库;共享缓存用 Redis/Memcached。

跨请求状态 -> Redis/DB
跨 worker 状态 -> Redis/DB
跨机器状态 -> Redis/DB/对象存储

如果状态丢了会影响业务,就不要只放进程内存。

六、线程安全也要考虑

即使单 worker 多线程,全局可变变量也会有竞态条件。counter += 1 不是业务层面的安全并发更新。多线程要锁,多进程要外部原子存储。

线程安全问题: 同一进程内并发修改
多进程一致性: 不同进程状态不同步

这两个问题经常同时存在。

七、部署配置怎么影响结论

Gunicorn 的 workersthreadspreload_app 都会影响全局对象的行为。preload_app 会在 master 进程预加载应用,再 fork worker;这能节省部分内存,但 fork 后可变状态仍然是各 worker 自己维护。

preload -> fork -> copy-on-write
修改全局变量 -> 当前 worker 私有副本

所以不要因为 preload 看起来“先加载一次”,就误以为全局变量跨 worker 共享。

八、面试回答怎么落到方案

可以把答案分成三层:进程内只读对象可以全局复用;可丢失缓存可以全局但要能容忍不一致;业务真实状态必须进 Redis、数据库、队列或对象存储。

只读配置: global OK
临时缓存: global maybe
业务状态: external storage

这样回答能体现你不是简单否定全局变量,而是在按状态性质分类。

九、常见误区与追问

  • 误区:Flask 全局变量所有请求共享,所以能保存状态。 只在当前进程内共享,多 worker 不一致。
  • 误区:开发环境能用,生产也能用。 部署模型不同,生产多进程会暴露问题。
  • 误区:加锁就能解决多 worker 状态。 进程间内存不共享,线程锁只管当前进程。
  • 追问:用户登录态放哪里? Cookie session、Redis session 或数据库。
  • 追问:计数器怎么做? Redis INCR 或数据库原子更新。
  • 追问:本地缓存能不能用? 可以做非关键进程内缓存,要能容忍不一致和失效。
  • 追问:worker 重启会怎样? 进程内全局状态全部丢失。

十、加强记忆

Flask 全局状态记成“每个 worker 一份”。开发单进程能跑,不代表生产多进程正确;跨请求、跨 worker、跨机器的状态要放 Redis、数据库或外部存储。全局变量只适合只读配置或可丢失缓存,不能当业务事实来源。