Node.js服务端将request存为全局变量有何风险?并行请求如何处理?
全局/单例存储HTTP请求对象的风险说明
Node.js确实采用单线程执行JavaScript同步代码,但基于事件循环的异步IO模型决定了HTTP请求不会从头到尾独占线程执行:只要请求处理逻辑中出现任何异步操作(数据库查询、文件读写、远程接口调用等),线程就会暂时挂起当前请求的后续逻辑,转而处理新进来的请求,你示例中的全局单例request对象会被新请求直接覆盖,等之前的异步逻辑恢复执行时,拿到的已经是其他请求的对象,必然会出现数据混乱。
你给出的示例代码仅在全同步处理请求的场景下能正常运行,只要加入异步操作问题立刻复现,我们可以对lib.js做简单修改验证:
// lib.js修改后代码 const { loadReq } = require('./shared'); const doSomethingWithReqPassingItAsParameter = async (req) => { // 模拟异步数据库查询 await new Promise(resolve => setTimeout(resolve, 100)); console.log('req as parameter', req.url); }; const doSomethingWithReqPassingItAsGlobal = async () => { // 模拟异步数据库查询 await new Promise(resolve => setTimeout(resolve, 100)); console.log('req as global', loadReq().url); }; module.exports = { doSomethingWithReqPassingItAsParameter, doSomethingWithReqPassingItAsGlobal, };
此时同时向8080端口发起/test1和/test2两个并发请求,会发现全局方式两次打印的url都是后发起的请求路径,而传参方式打印的路径是各自对应的请求路径。
这种实现的具体后果包括:
- 请求数据串扰:这是最严重的风险,并发场景下A请求的参数、用户身份等敏感信息会被B请求读取,轻则业务逻辑出错,重则出现越权访问、用户隐私泄露的安全事故。
- 问题排查难度极高:串请求的bug仅在并发场景下偶现,没有明确的复现路径,也无法追溯全局request对象的赋值来源,排查成本极高。
- 代码可维护性差:所有依赖全局request的函数都和单例对象强耦合,无法单独编写单元测试,也无法在非HTTP请求的场景(比如定时任务、命令行脚本)复用这些函数。
- 内存泄漏风险:全局变量持有的request对象如果没有手动置空,会一直存活在内存中,它关联的响应对象、请求体、请求头等大体积数据都无法被垃圾回收器回收,请求量上升后很快会出现内存溢出。
替代方案
如果不想层层传递request参数,可以使用Node.js官方提供的AsyncLocalStorage能力,它基于异步上下文隔离存储数据,同一请求链路的所有同步、异步逻辑都能访问到属于当前请求的request对象,完全不会出现并发串扰的问题,可以完美替代全局单例方案。
内容的提问来源于stack exchange,提问作者eugenekr
相关产品推荐
相关产品推荐

