查看: 497|回复: 0

Node接口同步还是异步选择:事件循环阻塞与I/O场景判断

[复制链接]
发表于 6 小时前 | 显示全部楼层 |阅读模式
在Node后端开发中,接口该写同步还是异步,讨论的往往不是语法风格,而是事件循环下主线程会不会被占住。Node主线程一旦被同步逻辑卡住,其他请求就要一起等。同样一段代码放在单机脚本里可能没事,放到线上接口并进入并发场景,就可能出现接口卡顿、RT变高、前面的请求未处理完后请求已排队。因此分界线不是“哪种写法更现代”,而是这段逻辑会不会把当前执行线占死。

先看文件读取例子。同步版本:
  1. const fs = require('node:fs');
  2. app.get('/profile', (req, res) => {
  3.   const text = fs.readFileSync('./profile.json', 'utf8');
  4.   res.send(JSON.parse(text));
  5. });
复制代码

功能本身没问题,但只要接口有人调用,readFileSync就会占住主线程。文件没读完,这次请求不往下走,其他请求也很难舒服进入。异步版本:
  1. const fs = require('node:fs/promises');
  2. app.get('/profile', async (req, res) => {
  3.   const text = await fs.readFile('./profile.json', 'utf8');
  4.   res.send(JSON.parse(text));
  5. });
复制代码

关键不是多了async几个字符,而是文件读取不会一直堵主线程。判断同步异步,应盯住阻塞点,而不是写法外观。

同步不是不能用。服务启动阶段读取本地配置、加载静态字典、初始化模板,通常只做一次,发生在服务真正接请求之前,用同步很正常:
  1. const fs = require('node:fs');
  2. const config = JSON.parse(
  3.   fs.readFileSync('./config.json', 'utf8')
  4. );
复制代码

这种地方硬绕成异步,收益未必大,代码反而更散。单机脚本、数据迁移脚本、批量处理文件的小工具、构建脚本、内部工具也适合同步优先,因为目标是把事情按顺序做完,不关心还能不能同时服务其他请求。轻量的纯内存逻辑,如字段整理、对象映射、少量判断,本来就该同步:
  1. function toUserCard(user) {
  2.   return {
  3.     id: user.id,
  4.     nickname: user.nickname,
  5.     isVip: user.level >= 3,
  6.   };
  7. }
复制代码

这种函数没有I/O和网络等待,硬写成异步没有实际意义。边界要清楚:纯内存逻辑不等于永远安全。请求链路里的大循环、大JSON处理、大量排序聚合,就算不访问数据库,也会拖住主线程。此时问题不是同步能不能用,而是这段计算本来就不该这么放。

正式线上服务里,下面这些地方应优先异步:数据库查库、写库、事务;Redis、MQ、第三方HTTP调用;文件读写;用户接口里的耗时操作。只要要等网络或外部资源结果,就应走异步。数据库示例:
  1. app.get('/orders', async (req, res) => {
  2.   const data = await orderService.list(req.user.id);
  3.   res.json(data);
  4. });
复制代码

这里若坚持同步写法,本质是拿接口吞吐换一时省事。文件I/O对Node同样是I/O,放在请求主路径上就有能力拖慢主线程。用户接口中的耗时操作,可以先问一句:这段逻辑是不是在用户请求过程中执行,而且会不会花时间;答案是会,就优先往异步、非阻塞靠。

容易带偏人的不是同步,而是“异步绝对正确”。误区一是接口代码必须全异步。接口应避免阻塞,但不等于每一层逻辑都必须异步;参数清洗、结果拼装、轻量校验本来就是同步动作,套一层async只是把短路绕远。误区二是同步写法一定更差。在一次性脚本、启动阶段、低请求量本地任务里,同步不一定差,有时更省心。Node要警惕的是同步阻塞出现在并发请求链路里。误区三是async/await写满就代表工程更规范。没有异步源却假装异步,会增加调用方和维护负担:
  1. async function buildUserView(user) {
  2.   return {
  3.     id: user.id,
  4.     nickname: user.nickname,
  5.   };
  6. }
复制代码

这不是错,只是没必要。

实际项目里做判断,可先过四个问题:1.有没有等待外部资源,数据库、Redis、文件、HTTP只要涉及等待外部返回,优先异步;2.它在不在请求主路径上,在用户接口执行链路里就要对阻塞更敏感;3.它一旦慢下来,会不会连带拖住别的请求,不能只看自己能不能跑完,还要看跑的时候会不会把别人堵住;4.它属于启动期还是运行期,启动期任务可放宽,运行期任务要保守。很多争论是把这两个阶段混成了一件事。

场景速查可这样记:服务启动时读取本地配置,同步即可;单机脚本、构建脚本、内部工具,同步优先;轻量对象映射、字段整理、简单计算,保持同步;数据库CRUD,必须异步;Redis、MQ、第三方接口调用,必须异步;文件上传、下载、读写,必须异步;面向用户的线上接口主路径,优先非阻塞设计。这不是死规定,但能挡掉大部分误判。

结论可以收成一句:Node里没有“同步永远落后”,也没有“异步天然正确”。同步适合一次性、短链路、轻逻辑;异步适合I/O、等待态、并发请求场景。真正要盯住的是这段代码会不会影响整条请求链路的流动性。先判断这段代码会不会堵住别人,再决定它该不该写成同步。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-27 16:30 , Processed in 0.022592 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部