Express异步请求处理的并发与数据一致性问题
Express路由并发与数据库一致性问题解答
一、顺序执行数据库调用的场景
对应的路由代码:
app.get('/all', async (req, res) => { // foo、bar、baz为数据库调用,因依赖关系顺序执行 const fooRes = await foo(); const barRes = await bar(fooRes); const bazRes = await baz(barRes); res.send({data: [fooRes,barRes,bazRes]}) }); app.post('/foo', async (req, res) => { // 更新foo数据 }); app.post('/bar', async (req, res) => { // 更新bar数据 }); app.post('/baz', async (req, res) => { // 更新baz数据 });
针对问题的解答:
- 其他用户完全可以调用/foo进行数据更新。Express基于事件循环实现异步处理,当/all路由中的
await baz(barRes)暂停当前请求执行时,事件循环会立即处理其他进来的请求(比如/foo的POST请求),不会阻塞整个服务。 - 会存在返回过时数据的风险。例如:用户A的/all请求已获取
fooRes,此时用户B调用/foo更新了foo的数据,但用户A的/all后续流程仍使用旧的fooRes,最终返回的foo数据就是更新前的过时值。如果业务要求/all返回的是同一时间点的一致数据快照,这种顺序异步调用的方式无法保证数据一致性。
二、Promise.all并行执行的场景
对应的/all路由代码:
app.get('/all', async (req, res) => { const data = await Promise.all([ foo(), bar(), baz() ]); res.send({ data }) }); // 上述/foo、/bar、/baz更新路由保持不变
针对问题的解答:
- 不能认为用户1的
baz()调用一定先于用户2的/baz更新执行。Express接收请求的顺序是确定的,但从Express将请求转发到数据库的过程中,存在诸多不确定因素:比如用户1的baz()查询因数据库内部队列、网络延迟等原因,实际到达数据库的时间晚于用户2的/baz更新请求;或者数据库本身采用异步调度处理请求,执行顺序由数据库决定,而非Express接收请求的顺序。 - 完全有可能出现用户1的/all请求返回更新后的baz数据。只要用户2的/baz更新请求先于用户1的
baz()查询到达数据库并完成执行,用户1的查询就会读取到更新后的值——哪怕用户1的/all请求更早到达Express。
内容的提问来源于stack exchange,提问作者hangry horse
相关产品推荐
相关产品推荐

