最近看到一个字节员工的吐槽:以前以为大厂都是人均卷王,真正进去以后才发现,所谓996,可能是5小时干活、3小时装忙、2小时带薪拉屎。
![]()
这个说法当然带着调侃,但放到研发系统里,其实有一个很像的问题: 程序看起来一直在工作,CPU、线程、连接池都很忙,真正有效吞吐却没有多少。
我之前排查过一个Node.js服务,监控上看QPS不低,接口RT却越来越高。第一眼很容易认为是机器性能不够,后来顺着调用链查下去,发现大量时间根本没有用来处理业务,而是在等待。
当时接口大致是这样的:
asyncfunctioncreateOrder(order) {
const user = await getUser(order.userId);
const stock = await getStock(order.skuId);
await updateOrder(order, user, stock);
await notifyWarehouse(order);
await notifyUser(order);return { success: true };
}
代码没有语法问题,线上也没有异常日志。但压测一上来,RT从几十毫秒逐渐升到几百毫秒,峰值甚至超过2秒。
一开始我怀疑数据库,直接看慢SQL。结果数据库CPU、慢查询数量都正常;Redis命中率也正常。再看Node进程,CPU只有40%左右,内存也没有明显上涨。
这时候问题就比较奇怪了: 机器没有打满,数据库没有打满,接口为什么还在排队?
继续看调用链,发现一次订单创建实际上串行等待了四个外部操作:
createOrder
├─ getUser 35ms
├─ getStock 42ms
├─ updateOrder 28ms
├─ notifyWarehouse 180ms
└─ notifyUser 120ms
其中通知仓库和通知用户根本不参与订单最终结果,却被放进了主链路。
于是第一版优化非常直接,把没有依赖关系的操作并发起来:
asyncfunctioncreateOrder(order) {
const [user, stock] = awaitPromise.all([
getUser(order.userId),
getStock(order.skuId)
]);
await updateOrder(order, user, stock);
// 非核心通知不阻塞订单响应
notifyWarehouse(order).catch(err =>
logger.error({ err, orderId: order.id }, 'warehouse notify failed')
);
notifyUser(order).catch(err =>
logger.error({ err, orderId: order.id }, 'user notify failed')
);return { success: true };
}
这里还有一个坑:不能看到 Promise.all 就全部并发。比如库存扣减、订单状态更新这种存在数据依赖的操作,仍然必须保证顺序和事务边界。
另外, catch 也不能简单理解成“错误处理完成”。通知失败以后,如果业务要求最终送达,就必须有可靠重试机制。否则只是把异常从接口日志里挪走了。
后来我们继续检查日志,发现真正影响RT的还有一个地方:
2026-10-16 14:03:21 request start order=98123
2026-10-16 14:03:21 call warehouse
2026-10-16 14:03:21 warehouse timeout
2026-10-16 14:03:22 retry warehouse
2026-10-16 14:03:23 retry warehouse
2026-10-16 14:03:24 request end cost=3018ms
原来的重试代码是:
for (let i = 0; i < 3; i++) {
try {
returnawait notifyWarehouse(order);
} catch (e) {
await sleep(1000);
}
}
这类代码在线上特别容易制造一种假象:服务进程一直“很忙”,请求也一直没有结束,但真正完成的业务工作并没有增加。
更麻烦的是,当下游开始变慢时,上游会产生更多重试;重试又进一步增加下游压力,最后形成类似这样的调用链:
正常请求
↓
下游变慢
↓
请求超时
↓
立即/固定间隔重试
↓
下游压力增加
↓
更多请求超时
↓
继续重试
后来我们把这类非核心通知从同步请求链路里拆出来,通过消息队列异步处理,并给消费者增加幂等和有限重试。订单接口只负责完成核心状态落库,通知则由后台任务负责最终处理。
最终监控里最明显的变化不是CPU下降,而是接口RT和下游调用次数同时下降。原来一次业务请求可能触发多次通知调用,优化后主链路只承担真正影响业务结果的操作。
所以回到这个“5小时搬砖、3小时装忙”的吐槽,放到Node.js服务里也一样:进程一直运行、Promise一直等待、请求一直重试,并不等于系统真的产生了有效吞吐。排查性能问题时,别只看CPU和机器负载, 把一次请求到底花时间在哪里拆出来,往往比盯着监控大盘更有用。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.