Django中能否用全局变量替代session存储无法序列化的复杂对象?
我开发了一款Django项目,需要在不同视图间传递一个结构极其复杂的对象,常规实现方式是使用session变量存储,示例代码如下:request.session['variable'] = complex_object
我清楚这是该场景下的标准实现方案,但我用到的这个复杂对象不支持JSON序列化,因此无法直接存入session。可行解法之一是为该对象编写专用序列化器,但该对象嵌套层级极深、结构极度复杂,编写序列化器并非工作量繁琐的问题,而是根本无法实现。
因此我尝试寻找更简便的临时方案,开始使用全局变量存储该对象。我清楚这种做法不符合最佳实践,但我急需可落地的解决方案。全局变量在开发环境下运行完全正常,但项目部署到生产环境后就变得极不稳定,时灵时不灵,因此该方案也不可行。
为更清晰说明我使用全局变量的实现逻辑,附上核心伪代码示例:
global p def a(request): global p p = 5 def b(request): global p print(p)
补充说明:使用pickle序列化该对象也无法实现,因为该对象属于SwigPyObject类型,这类对象不支持pickle序列化。
核心诉求:哪怕是临时的hack方案,只要能稳定跨视图传递该对象即可。
问题根因
生产环境下全局变量失效的核心原因,是Django服务默认采用多进程模式部署(Gunicorn、uWSGI等主流部署工具都会拉起多个独立worker进程处理请求)。每个worker是完全独立的Python运行时,拥有单独的全局内存空间:
- 当视图a的请求被调度到worker1上时,全局变量p只会被写入worker1的内存
- 当后续视图b的请求被负载均衡器调度到worker2、worker3上时,根本访问不到worker1内存里存储的p值
开发环境下Django默认是单进程单线程模式,所有请求都在同一个进程内处理,全局变量自然能正常读写。
可落地解决方案
1. 零代码改动临时hack
把生产环境的WSGI/ASGI服务的worker数强制设置为1,同时关闭多线程并发模式。所有请求都会被路由到同一个Python进程处理,你之前写的全局变量逻辑可以直接跑通,不需要修改任何业务代码。
- 适用场景:内部工具、日活极低的内部系统
- 明显缺陷:并发能力极差,多个用户同时访问会出现请求阻塞、响应超时
2. 独立长驻进程存对象
如果不想牺牲并发能力,可以单独拉起一个独立的长驻Python进程,专门负责持有所有SwigPyObject对象:
- 在这个独立进程里完成所有Swig对象的初始化、存储逻辑,不做任何序列化操作
- 给每个存入的Swig对象分配一个唯一字符串ID,通过本地端口暴露极简的读写接口(本地HTTP服务、ZeroMQ通信均可实现,代码量不超过百行)
- 业务侧跨视图传递时,只需要把对象的唯一ID存在session里,后续取对象时拿ID调用接口从独立进程里查就行
- 完全规避Swig对象无法序列化的问题,因为对象自始至终都存在于同一个内存空间里
3. 会话粘滞绑定worker
如果不想新增独立进程,可以通过部署配置实现会话粘滞:
- 配置WSGI服务器的负载均衡规则,确保同一个用户的所有请求永远被调度到同一个worker进程上处理
- 这种情况下,用户在视图a里写入全局变量的对象,后续该用户的视图b请求一定会路由到同一个worker,自然能读到对应的值
- 不需要修改业务代码,配置即可生效;缺点是如果worker重启、或者服务扩容,之前存储的对象会临时丢失,可以通过简单的异常捕获做兜底跳转
注意事项
- 不要尝试修改session序列化器去直接存储SwigPyObject,session的后端本质是跨进程持久化存储,只要涉及跨进程读写,不支持序列化的Swig对象就无法正常存取
- 全局/进程内存储的Swig对象不会被Python垃圾回收自动清理,长期运行会出现内存泄漏,需要定期重启对应持有对象的进程做内存释放
- 这类内存存储的对象不要用于跨用户共享,多用户同时读写同一个全局变量会出现严重的数据错乱
内容的提问来源于stack exchange,提问作者TimoSan

