中级常见状态是:
我知道这个问题大概要用 Redis / MQ / ORM / 日志 / 限流。
高级需要做到:
我知道为什么用、怎么用、什么时候不能用、出了问题怎么排查、代价是什么。
资深进一步要求:
我能从系统演进、团队协作、故障隔离、业务边界、成本和长期维护角度做取舍。
分级标记说明
| 标记 | 含义 |
|---|---|
| 🟩 中级答案 | 能独立完成常规业务,知道常见方案 |
| 🟦 高级答案 | 能解释机制、落地方案、处理边界问题 |
| 🟪 资深答案 | 能做系统级权衡,考虑故障、扩展、成本和长期维护 |
| ⚠️ 容易踩坑 | 面试或真实项目中容易暴露短板的地方 |
| ✅ 复习重点 | 这题真正要掌握的核心 |
1. Node.js 为什么适合 I/O 密集型服务?为什么不适合直接做 CPU 密集型任务?
题目
Node.js 为什么适合 I/O 密集型服务?为什么不适合直接在主线程做 CPU 密集型任务?
概念图
#mermaid-svg-RMgADTPjsBN9D3Gr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RMgADTPjsBN9D3Gr .error-icon{fill:#552222;}#mermaid-svg-RMgADTPjsBN9D3Gr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RMgADTPjsBN9D3Gr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RMgADTPjsBN9D3Gr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RMgADTPjsBN9D3Gr .marker.cross{stroke:#333333;}#mermaid-svg-RMgADTPjsBN9D3Gr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RMgADTPjsBN9D3Gr p{margin:0;}#mermaid-svg-RMgADTPjsBN9D3Gr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RMgADTPjsBN9D3Gr .cluster-label text{fill:#333;}#mermaid-svg-RMgADTPjsBN9D3Gr .cluster-label span{color:#333;}#mermaid-svg-RMgADTPjsBN9D3Gr .cluster-label span p{background-color:transparent;}#mermaid-svg-RMgADTPjsBN9D3Gr .label text,#mermaid-svg-RMgADTPjsBN9D3Gr span{fill:#333;color:#333;}#mermaid-svg-RMgADTPjsBN9D3Gr .node rect,#mermaid-svg-RMgADTPjsBN9D3Gr .node circle,#mermaid-svg-RMgADTPjsBN9D3Gr .node ellipse,#mermaid-svg-RMgADTPjsBN9D3Gr .node polygon,#mermaid-svg-RMgADTPjsBN9D3Gr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RMgADTPjsBN9D3Gr .rough-node .label text,#mermaid-svg-RMgADTPjsBN9D3Gr .node .label text,#mermaid-svg-RMgADTPjsBN9D3Gr .image-shape .label,#mermaid-svg-RMgADTPjsBN9D3Gr .icon-shape .label{text-anchor:middle;}#mermaid-svg-RMgADTPjsBN9D3Gr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RMgADTPjsBN9D3Gr .rough-node .label,#mermaid-svg-RMgADTPjsBN9D3Gr .node .label,#mermaid-svg-RMgADTPjsBN9D3Gr .image-shape .label,#mermaid-svg-RMgADTPjsBN9D3Gr .icon-shape .label{text-align:center;}#mermaid-svg-RMgADTPjsBN9D3Gr .node.clickable{cursor:pointer;}#mermaid-svg-RMgADTPjsBN9D3Gr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RMgADTPjsBN9D3Gr .arrowheadPath{fill:#333333;}#mermaid-svg-RMgADTPjsBN9D3Gr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RMgADTPjsBN9D3Gr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RMgADTPjsBN9D3Gr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RMgADTPjsBN9D3Gr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RMgADTPjsBN9D3Gr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RMgADTPjsBN9D3Gr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RMgADTPjsBN9D3Gr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RMgADTPjsBN9D3Gr .cluster text{fill:#333;}#mermaid-svg-RMgADTPjsBN9D3Gr .cluster span{color:#333;}#mermaid-svg-RMgADTPjsBN9D3Gr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RMgADTPjsBN9D3Gr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RMgADTPjsBN9D3Gr rect.text{fill:none;stroke-width:0;}#mermaid-svg-RMgADTPjsBN9D3Gr .icon-shape,#mermaid-svg-RMgADTPjsBN9D3Gr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RMgADTPjsBN9D3Gr .icon-shape p,#mermaid-svg-RMgADTPjsBN9D3Gr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RMgADTPjsBN9D3Gr .icon-shape .label rect,#mermaid-svg-RMgADTPjsBN9D3Gr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RMgADTPjsBN9D3Gr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RMgADTPjsBN9D3Gr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RMgADTPjsBN9D3Gr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否,CPU 计算
HTTP 请求进入 Node 服务
JS 主线程
遇到 I/O?
交给系统/libuv/驱动处理
事件完成后回调进入事件循环
长时间占用主线程
事件循环被阻塞
其他请求延迟升高
🟩 中级答案
Node 适合 I/O 密集型服务,是因为它使用异步非阻塞 I/O 和事件循环。数据库、网络、文件等 I/O 操作不会一直占用 JS 主线程。
不适合直接做 CPU 密集型任务,是因为业务 JS 通常运行在主线程。如果计算任务太重,会阻塞事件循环,导致其他请求无法及时处理。
🟦 高级答案
Node 的优势不是"计算快",而是"等待 I/O 的时候不浪费主线程"。当请求遇到数据库、Redis、HTTP 调用、文件读写等 I/O 操作时,Node 可以把等待交出去,主线程继续处理其他请求。
CPU 密集型任务不同。比如大 JSON 解析、大量加密计算、图片处理、复杂循环计算,这些都会长时间占用 JS 主线程。一旦主线程被占满,事件循环无法及时执行回调,请求就会堆积。
解决方式包括:
worker_threadscluster- 多进程部署
- 任务队列
- 把计算任务拆到专门的服务
🟪 资深答案
资深回答会进一步区分:
- Node 的 JS 执行通常是单主线程;
- Node 进程内部并不是只有一个线程;
- libuv 有线程池;
- I/O 并发不等于 CPU 并行;
- CPU 密集型任务要通过 Worker、进程池或外部计算服务隔离。
线上判断时,不是靠感觉,而是看:
- event loop lag;
- CPU 使用率;
- P95/P99 延迟;
- GC 情况;
- 请求堆积;
- active handles。
⚠️ 容易踩坑
"Node 是单线程,所以什么都只能一个线程做"这个说法不准确。
更准确是:业务 JS 主要在主线程执行,但 Node 进程内部和部署形态都可以利用多线程/多进程。
✅ 复习重点
Node 适合 I/O 密集型,不是因为它魔法般更快,而是因为它减少了等待 I/O 时的线程浪费。
2. 下面代码输出顺序是什么?为什么?
题目
js
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
Promise.resolve().then(() => {
console.log('C');
});
process.nextTick(() => {
console.log('D');
});
console.log('E');
概念图
#mermaid-svg-XcvFB7WA16MDCy57{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XcvFB7WA16MDCy57 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XcvFB7WA16MDCy57 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XcvFB7WA16MDCy57 .error-icon{fill:#552222;}#mermaid-svg-XcvFB7WA16MDCy57 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XcvFB7WA16MDCy57 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XcvFB7WA16MDCy57 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XcvFB7WA16MDCy57 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XcvFB7WA16MDCy57 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XcvFB7WA16MDCy57 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XcvFB7WA16MDCy57 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XcvFB7WA16MDCy57 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XcvFB7WA16MDCy57 .marker.cross{stroke:#333333;}#mermaid-svg-XcvFB7WA16MDCy57 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XcvFB7WA16MDCy57 p{margin:0;}#mermaid-svg-XcvFB7WA16MDCy57 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XcvFB7WA16MDCy57 .cluster-label text{fill:#333;}#mermaid-svg-XcvFB7WA16MDCy57 .cluster-label span{color:#333;}#mermaid-svg-XcvFB7WA16MDCy57 .cluster-label span p{background-color:transparent;}#mermaid-svg-XcvFB7WA16MDCy57 .label text,#mermaid-svg-XcvFB7WA16MDCy57 span{fill:#333;color:#333;}#mermaid-svg-XcvFB7WA16MDCy57 .node rect,#mermaid-svg-XcvFB7WA16MDCy57 .node circle,#mermaid-svg-XcvFB7WA16MDCy57 .node ellipse,#mermaid-svg-XcvFB7WA16MDCy57 .node polygon,#mermaid-svg-XcvFB7WA16MDCy57 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XcvFB7WA16MDCy57 .rough-node .label text,#mermaid-svg-XcvFB7WA16MDCy57 .node .label text,#mermaid-svg-XcvFB7WA16MDCy57 .image-shape .label,#mermaid-svg-XcvFB7WA16MDCy57 .icon-shape .label{text-anchor:middle;}#mermaid-svg-XcvFB7WA16MDCy57 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XcvFB7WA16MDCy57 .rough-node .label,#mermaid-svg-XcvFB7WA16MDCy57 .node .label,#mermaid-svg-XcvFB7WA16MDCy57 .image-shape .label,#mermaid-svg-XcvFB7WA16MDCy57 .icon-shape .label{text-align:center;}#mermaid-svg-XcvFB7WA16MDCy57 .node.clickable{cursor:pointer;}#mermaid-svg-XcvFB7WA16MDCy57 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XcvFB7WA16MDCy57 .arrowheadPath{fill:#333333;}#mermaid-svg-XcvFB7WA16MDCy57 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XcvFB7WA16MDCy57 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XcvFB7WA16MDCy57 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XcvFB7WA16MDCy57 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XcvFB7WA16MDCy57 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XcvFB7WA16MDCy57 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XcvFB7WA16MDCy57 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XcvFB7WA16MDCy57 .cluster text{fill:#333;}#mermaid-svg-XcvFB7WA16MDCy57 .cluster span{color:#333;}#mermaid-svg-XcvFB7WA16MDCy57 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XcvFB7WA16MDCy57 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XcvFB7WA16MDCy57 rect.text{fill:none;stroke-width:0;}#mermaid-svg-XcvFB7WA16MDCy57 .icon-shape,#mermaid-svg-XcvFB7WA16MDCy57 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XcvFB7WA16MDCy57 .icon-shape p,#mermaid-svg-XcvFB7WA16MDCy57 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XcvFB7WA16MDCy57 .icon-shape .label rect,#mermaid-svg-XcvFB7WA16MDCy57 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XcvFB7WA16MDCy57 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XcvFB7WA16MDCy57 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XcvFB7WA16MDCy57 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 同步代码
A
E
process.nextTick 队列
D
Promise microtask 队列
C
timers 阶段
B
🟩 中级答案
输出顺序是:
txt
A
E
D
C
B
同步代码先执行,所以先输出 A 和 E。然后执行 process.nextTick,再执行 Promise 微任务,最后执行 setTimeout。
🟦 高级答案
这题考 Node 的事件循环和任务队列优先级。
执行顺序是:
- 同步代码进入调用栈,先打印
A; setTimeout回调进入 timers 阶段等待;- Promise
.then进入 microtask 队列; process.nextTick进入 nextTick 队列;- 同步代码继续执行,打印
E; - 当前调用栈清空后,Node 优先清空 nextTick 队列,打印
D; - 再执行 Promise 微任务,打印
C; - 最后进入 timers 阶段,打印
B。
🟪 资深答案
资深回答会提醒:
process.nextTick是 Node 特有机制,不等同于浏览器标准 microtask;nextTick优先级非常高;- 大量递归使用
process.nextTick可能导致事件循环饥饿; - 这类题要区分 Node 环境和浏览器环境。
⚠️ 容易踩坑
很多人会答成 A E C D B,这是把 Promise microtask 和 Node 的 process.nextTick 优先级混淆了。
✅ 复习重点
Node 中,同步代码之后,
process.nextTick通常优先于 Promise microtask。
3. require 和 import 有什么区别?
概念图
#mermaid-svg-3iPsXNeniDGDDLyY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3iPsXNeniDGDDLyY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3iPsXNeniDGDDLyY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3iPsXNeniDGDDLyY .error-icon{fill:#552222;}#mermaid-svg-3iPsXNeniDGDDLyY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3iPsXNeniDGDDLyY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3iPsXNeniDGDDLyY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3iPsXNeniDGDDLyY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3iPsXNeniDGDDLyY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3iPsXNeniDGDDLyY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3iPsXNeniDGDDLyY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3iPsXNeniDGDDLyY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3iPsXNeniDGDDLyY .marker.cross{stroke:#333333;}#mermaid-svg-3iPsXNeniDGDDLyY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3iPsXNeniDGDDLyY p{margin:0;}#mermaid-svg-3iPsXNeniDGDDLyY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3iPsXNeniDGDDLyY .cluster-label text{fill:#333;}#mermaid-svg-3iPsXNeniDGDDLyY .cluster-label span{color:#333;}#mermaid-svg-3iPsXNeniDGDDLyY .cluster-label span p{background-color:transparent;}#mermaid-svg-3iPsXNeniDGDDLyY .label text,#mermaid-svg-3iPsXNeniDGDDLyY span{fill:#333;color:#333;}#mermaid-svg-3iPsXNeniDGDDLyY .node rect,#mermaid-svg-3iPsXNeniDGDDLyY .node circle,#mermaid-svg-3iPsXNeniDGDDLyY .node ellipse,#mermaid-svg-3iPsXNeniDGDDLyY .node polygon,#mermaid-svg-3iPsXNeniDGDDLyY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3iPsXNeniDGDDLyY .rough-node .label text,#mermaid-svg-3iPsXNeniDGDDLyY .node .label text,#mermaid-svg-3iPsXNeniDGDDLyY .image-shape .label,#mermaid-svg-3iPsXNeniDGDDLyY .icon-shape .label{text-anchor:middle;}#mermaid-svg-3iPsXNeniDGDDLyY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3iPsXNeniDGDDLyY .rough-node .label,#mermaid-svg-3iPsXNeniDGDDLyY .node .label,#mermaid-svg-3iPsXNeniDGDDLyY .image-shape .label,#mermaid-svg-3iPsXNeniDGDDLyY .icon-shape .label{text-align:center;}#mermaid-svg-3iPsXNeniDGDDLyY .node.clickable{cursor:pointer;}#mermaid-svg-3iPsXNeniDGDDLyY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3iPsXNeniDGDDLyY .arrowheadPath{fill:#333333;}#mermaid-svg-3iPsXNeniDGDDLyY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3iPsXNeniDGDDLyY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3iPsXNeniDGDDLyY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3iPsXNeniDGDDLyY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3iPsXNeniDGDDLyY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3iPsXNeniDGDDLyY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3iPsXNeniDGDDLyY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3iPsXNeniDGDDLyY .cluster text{fill:#333;}#mermaid-svg-3iPsXNeniDGDDLyY .cluster span{color:#333;}#mermaid-svg-3iPsXNeniDGDDLyY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3iPsXNeniDGDDLyY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3iPsXNeniDGDDLyY rect.text{fill:none;stroke-width:0;}#mermaid-svg-3iPsXNeniDGDDLyY .icon-shape,#mermaid-svg-3iPsXNeniDGDDLyY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3iPsXNeniDGDDLyY .icon-shape p,#mermaid-svg-3iPsXNeniDGDDLyY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3iPsXNeniDGDDLyY .icon-shape .label rect,#mermaid-svg-3iPsXNeniDGDDLyY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3iPsXNeniDGDDLyY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3iPsXNeniDGDDLyY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3iPsXNeniDGDDLyY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CommonJS
require
module.exports
运行时加载
同步加载
ES Module
import
export
静态分析
live binding
top-level await
🟩 中级答案
require 属于 CommonJS,主要是运行时加载,使用 module.exports 导出。
import 属于 ES Module,语法更静态,使用 export 导出,更适合静态分析和 Tree Shaking。
🟦 高级答案
CommonJS 和 ES Module 的核心区别不只是语法不同,而是模块模型不同。
CommonJS:
- 运行时加载;
- 通常同步;
- 可以写在条件语句里;
- 导出是
module.exports; - 加载后有缓存。
ES Module:
- 静态加载;
- 支持静态分析;
- 支持 Tree Shaking;
- 导入是 live binding;
- 支持 top-level await;
- Node 中和 CJS 混用有边界。
🟪 资深答案
资深回答会考虑实际工程迁移:
.cjs/.mjs;package.json中"type": "module";- 老项目从 CJS 迁移到 ESM 的成本;
- Jest、ts-node、Babel、Webpack、Vite 等工具链兼容;
- 第三方库只支持 ESM 或只支持 CJS 时的处理方式。
⚠️ 容易踩坑
"ESM 支持 Tree Shaking,所以 Node 里一定更快"这个说法不严谨。
Tree Shaking 更多发生在构建工具场景,服务端 Node 项目不一定直接因此变快。
✅ 复习重点
CJS 是运行时模块系统,ESM 是静态模块系统。差异不只是写法,而是加载、绑定、分析、兼容方式都不同。
4. 下面代码有什么问题?
题目
js
async function getUser(id) {
try {
const user = await db.query(`select * from users where id = ${id}`);
return user;
} catch (err) {
console.log(err);
}
}
风险图
#mermaid-svg-6xJfPJkSrAApBzjN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6xJfPJkSrAApBzjN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6xJfPJkSrAApBzjN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6xJfPJkSrAApBzjN .error-icon{fill:#552222;}#mermaid-svg-6xJfPJkSrAApBzjN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6xJfPJkSrAApBzjN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6xJfPJkSrAApBzjN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6xJfPJkSrAApBzjN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6xJfPJkSrAApBzjN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6xJfPJkSrAApBzjN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6xJfPJkSrAApBzjN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6xJfPJkSrAApBzjN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6xJfPJkSrAApBzjN .marker.cross{stroke:#333333;}#mermaid-svg-6xJfPJkSrAApBzjN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6xJfPJkSrAApBzjN p{margin:0;}#mermaid-svg-6xJfPJkSrAApBzjN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6xJfPJkSrAApBzjN .cluster-label text{fill:#333;}#mermaid-svg-6xJfPJkSrAApBzjN .cluster-label span{color:#333;}#mermaid-svg-6xJfPJkSrAApBzjN .cluster-label span p{background-color:transparent;}#mermaid-svg-6xJfPJkSrAApBzjN .label text,#mermaid-svg-6xJfPJkSrAApBzjN span{fill:#333;color:#333;}#mermaid-svg-6xJfPJkSrAApBzjN .node rect,#mermaid-svg-6xJfPJkSrAApBzjN .node circle,#mermaid-svg-6xJfPJkSrAApBzjN .node ellipse,#mermaid-svg-6xJfPJkSrAApBzjN .node polygon,#mermaid-svg-6xJfPJkSrAApBzjN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6xJfPJkSrAApBzjN .rough-node .label text,#mermaid-svg-6xJfPJkSrAApBzjN .node .label text,#mermaid-svg-6xJfPJkSrAApBzjN .image-shape .label,#mermaid-svg-6xJfPJkSrAApBzjN .icon-shape .label{text-anchor:middle;}#mermaid-svg-6xJfPJkSrAApBzjN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6xJfPJkSrAApBzjN .rough-node .label,#mermaid-svg-6xJfPJkSrAApBzjN .node .label,#mermaid-svg-6xJfPJkSrAApBzjN .image-shape .label,#mermaid-svg-6xJfPJkSrAApBzjN .icon-shape .label{text-align:center;}#mermaid-svg-6xJfPJkSrAApBzjN .node.clickable{cursor:pointer;}#mermaid-svg-6xJfPJkSrAApBzjN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6xJfPJkSrAApBzjN .arrowheadPath{fill:#333333;}#mermaid-svg-6xJfPJkSrAApBzjN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6xJfPJkSrAApBzjN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6xJfPJkSrAApBzjN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6xJfPJkSrAApBzjN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6xJfPJkSrAApBzjN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6xJfPJkSrAApBzjN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6xJfPJkSrAApBzjN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6xJfPJkSrAApBzjN .cluster text{fill:#333;}#mermaid-svg-6xJfPJkSrAApBzjN .cluster span{color:#333;}#mermaid-svg-6xJfPJkSrAApBzjN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6xJfPJkSrAApBzjN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6xJfPJkSrAApBzjN rect.text{fill:none;stroke-width:0;}#mermaid-svg-6xJfPJkSrAApBzjN .icon-shape,#mermaid-svg-6xJfPJkSrAApBzjN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6xJfPJkSrAApBzjN .icon-shape p,#mermaid-svg-6xJfPJkSrAApBzjN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6xJfPJkSrAApBzjN .icon-shape .label rect,#mermaid-svg-6xJfPJkSrAApBzjN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6xJfPJkSrAApBzjN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6xJfPJkSrAApBzjN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6xJfPJkSrAApBzjN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户输入 id
字符串拼接 SQL
SQL 注入风险
select * 字段不可控
catch 捕获错误
只 console.log
错误被吞掉
调用方拿到 undefined
🟩 中级答案
这段代码有几个明显问题:
- SQL 字符串拼接,有 SQL 注入风险;
catch里只打印错误,没有继续抛出;select *不利于性能和字段控制;- 没有校验
id。
🟦 高级答案
核心问题是 SQL 注入:
js
const user = await db.query(`select * from users where id = ${id}`);
如果 id 来自用户输入,攻击者可以构造恶意 SQL。
更好的写法是参数化查询:
js
async function getUser(id) {
if (!Number.isInteger(Number(id))) {
throw new BadRequestError('Invalid user id');
}
const user = await db.query(
'select id, name, email from users where id = ?',
[id]
);
return user;
}
错误不应该在这里吞掉。DAO 或 service 层可以记录必要上下文,但通常要把错误抛给统一错误处理中间件。
🟪 资深答案
资深会进一步考虑:
- 数据库账号最小权限;
- 动态字段、排序字段、表名必须白名单;
- ORM 不等于绝对安全;
- raw SQL 必须参数化;
- 错误信息不能直接暴露给用户;
- 安全问题要靠代码规范、review、扫描、测试共同保证。
✅ 复习重点
用户输入永远不能直接拼进 SQL。参数化查询是底线。
5. 如何统一处理 Express/Koa 项目里的异常?
概念图
#mermaid-svg-P4YTWFZ5754SKIiv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P4YTWFZ5754SKIiv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P4YTWFZ5754SKIiv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P4YTWFZ5754SKIiv .error-icon{fill:#552222;}#mermaid-svg-P4YTWFZ5754SKIiv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P4YTWFZ5754SKIiv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P4YTWFZ5754SKIiv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P4YTWFZ5754SKIiv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P4YTWFZ5754SKIiv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P4YTWFZ5754SKIiv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P4YTWFZ5754SKIiv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P4YTWFZ5754SKIiv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P4YTWFZ5754SKIiv .marker.cross{stroke:#333333;}#mermaid-svg-P4YTWFZ5754SKIiv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P4YTWFZ5754SKIiv p{margin:0;}#mermaid-svg-P4YTWFZ5754SKIiv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-P4YTWFZ5754SKIiv .cluster-label text{fill:#333;}#mermaid-svg-P4YTWFZ5754SKIiv .cluster-label span{color:#333;}#mermaid-svg-P4YTWFZ5754SKIiv .cluster-label span p{background-color:transparent;}#mermaid-svg-P4YTWFZ5754SKIiv .label text,#mermaid-svg-P4YTWFZ5754SKIiv span{fill:#333;color:#333;}#mermaid-svg-P4YTWFZ5754SKIiv .node rect,#mermaid-svg-P4YTWFZ5754SKIiv .node circle,#mermaid-svg-P4YTWFZ5754SKIiv .node ellipse,#mermaid-svg-P4YTWFZ5754SKIiv .node polygon,#mermaid-svg-P4YTWFZ5754SKIiv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P4YTWFZ5754SKIiv .rough-node .label text,#mermaid-svg-P4YTWFZ5754SKIiv .node .label text,#mermaid-svg-P4YTWFZ5754SKIiv .image-shape .label,#mermaid-svg-P4YTWFZ5754SKIiv .icon-shape .label{text-anchor:middle;}#mermaid-svg-P4YTWFZ5754SKIiv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P4YTWFZ5754SKIiv .rough-node .label,#mermaid-svg-P4YTWFZ5754SKIiv .node .label,#mermaid-svg-P4YTWFZ5754SKIiv .image-shape .label,#mermaid-svg-P4YTWFZ5754SKIiv .icon-shape .label{text-align:center;}#mermaid-svg-P4YTWFZ5754SKIiv .node.clickable{cursor:pointer;}#mermaid-svg-P4YTWFZ5754SKIiv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P4YTWFZ5754SKIiv .arrowheadPath{fill:#333333;}#mermaid-svg-P4YTWFZ5754SKIiv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P4YTWFZ5754SKIiv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P4YTWFZ5754SKIiv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P4YTWFZ5754SKIiv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P4YTWFZ5754SKIiv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P4YTWFZ5754SKIiv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P4YTWFZ5754SKIiv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P4YTWFZ5754SKIiv .cluster text{fill:#333;}#mermaid-svg-P4YTWFZ5754SKIiv .cluster span{color:#333;}#mermaid-svg-P4YTWFZ5754SKIiv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P4YTWFZ5754SKIiv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P4YTWFZ5754SKIiv rect.text{fill:none;stroke-width:0;}#mermaid-svg-P4YTWFZ5754SKIiv .icon-shape,#mermaid-svg-P4YTWFZ5754SKIiv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P4YTWFZ5754SKIiv .icon-shape p,#mermaid-svg-P4YTWFZ5754SKIiv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P4YTWFZ5754SKIiv .icon-shape .label rect,#mermaid-svg-P4YTWFZ5754SKIiv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P4YTWFZ5754SKIiv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P4YTWFZ5754SKIiv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P4YTWFZ5754SKIiv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Route Handler
Service
throw Error
asyncHandler / Koa middleware
统一错误中间件
结构化日志
统一响应
监控告警
🟩 中级答案
使用全局错误处理中间件,统一处理接口中的异常,返回统一格式,比如:
json
{
"code": "INTERNAL_ERROR",
"message": "服务器错误"
}
🟦 高级答案
Express 中 async route 的错误需要显式传给错误中间件:
js
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/user/:id', asyncHandler(async (req, res) => {
const user = await userService.getUser(req.params.id);
res.json(user);
}));
统一错误处理中间件:
js
app.use((err, req, res, next) => {
logger.error({
err,
traceId: req.id,
method: req.method,
path: req.path,
userId: req.user?.id
});
res.status(err.statusCode || 500).json({
code: err.code || 'INTERNAL_ERROR',
message: err.expose ? err.message : '服务器错误',
requestId: req.id
});
});
🟪 资深答案
资深会设计完整错误体系:
- 参数错误;
- 权限错误;
- 认证错误;
- 业务错误;
- 依赖服务错误;
- 可重试错误;
- 不可重试错误;
- 系统未知错误。
还会考虑:
- 错误码规范;
- 多语言错误消息;
- requestId / traceId;
- 错误率告警;
- 错误聚合;
- 日志脱敏;
- 生产环境不暴露 stack。
⚠️ 容易踩坑
"try/catch 每个接口都包一遍"不是好方案。
它会让业务代码到处重复,而且容易出现吞错、不统一、漏日志的问题。
✅ 复习重点
异常处理的目标不是"别崩",而是统一响应、方便排查、支持告警、避免信息泄露。
6. Promise.all、Promise.allSettled、Promise.race 分别适合什么场景?
概念图
#mermaid-svg-vOTtIfj3TrxbkMPR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vOTtIfj3TrxbkMPR .error-icon{fill:#552222;}#mermaid-svg-vOTtIfj3TrxbkMPR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vOTtIfj3TrxbkMPR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vOTtIfj3TrxbkMPR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vOTtIfj3TrxbkMPR .marker.cross{stroke:#333333;}#mermaid-svg-vOTtIfj3TrxbkMPR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vOTtIfj3TrxbkMPR p{margin:0;}#mermaid-svg-vOTtIfj3TrxbkMPR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vOTtIfj3TrxbkMPR .cluster-label text{fill:#333;}#mermaid-svg-vOTtIfj3TrxbkMPR .cluster-label span{color:#333;}#mermaid-svg-vOTtIfj3TrxbkMPR .cluster-label span p{background-color:transparent;}#mermaid-svg-vOTtIfj3TrxbkMPR .label text,#mermaid-svg-vOTtIfj3TrxbkMPR span{fill:#333;color:#333;}#mermaid-svg-vOTtIfj3TrxbkMPR .node rect,#mermaid-svg-vOTtIfj3TrxbkMPR .node circle,#mermaid-svg-vOTtIfj3TrxbkMPR .node ellipse,#mermaid-svg-vOTtIfj3TrxbkMPR .node polygon,#mermaid-svg-vOTtIfj3TrxbkMPR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vOTtIfj3TrxbkMPR .rough-node .label text,#mermaid-svg-vOTtIfj3TrxbkMPR .node .label text,#mermaid-svg-vOTtIfj3TrxbkMPR .image-shape .label,#mermaid-svg-vOTtIfj3TrxbkMPR .icon-shape .label{text-anchor:middle;}#mermaid-svg-vOTtIfj3TrxbkMPR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vOTtIfj3TrxbkMPR .rough-node .label,#mermaid-svg-vOTtIfj3TrxbkMPR .node .label,#mermaid-svg-vOTtIfj3TrxbkMPR .image-shape .label,#mermaid-svg-vOTtIfj3TrxbkMPR .icon-shape .label{text-align:center;}#mermaid-svg-vOTtIfj3TrxbkMPR .node.clickable{cursor:pointer;}#mermaid-svg-vOTtIfj3TrxbkMPR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vOTtIfj3TrxbkMPR .arrowheadPath{fill:#333333;}#mermaid-svg-vOTtIfj3TrxbkMPR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vOTtIfj3TrxbkMPR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vOTtIfj3TrxbkMPR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vOTtIfj3TrxbkMPR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vOTtIfj3TrxbkMPR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vOTtIfj3TrxbkMPR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vOTtIfj3TrxbkMPR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vOTtIfj3TrxbkMPR .cluster text{fill:#333;}#mermaid-svg-vOTtIfj3TrxbkMPR .cluster span{color:#333;}#mermaid-svg-vOTtIfj3TrxbkMPR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vOTtIfj3TrxbkMPR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vOTtIfj3TrxbkMPR rect.text{fill:none;stroke-width:0;}#mermaid-svg-vOTtIfj3TrxbkMPR .icon-shape,#mermaid-svg-vOTtIfj3TrxbkMPR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vOTtIfj3TrxbkMPR .icon-shape p,#mermaid-svg-vOTtIfj3TrxbkMPR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vOTtIfj3TrxbkMPR .icon-shape .label rect,#mermaid-svg-vOTtIfj3TrxbkMPR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vOTtIfj3TrxbkMPR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vOTtIfj3TrxbkMPR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vOTtIfj3TrxbkMPR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 全部必须成功
都要结果,允许失败
谁先完成用谁
多个 Promise
依赖关系
Promise.all
Promise.allSettled
Promise.race
竞速 / 超时控制
🟩 中级答案
Promise.all 适合多个任务都必须成功的场景,任何一个失败整体失败。
Promise.allSettled 适合批量任务,需要知道每个任务成功或失败。
Promise.race 返回最快完成的任务,适合竞速或超时控制。
🟦 高级答案
Promise.all 适合强依赖任务,比如页面初始化同时获取用户信息、权限、配置,只要一个失败就整体失败。
Promise.allSettled 适合弱依赖任务,比如批量发送通知、批量埋点、批量处理文件,希望获得每个任务的最终状态。
Promise.race 常用于超时控制:
js
await Promise.race([
request(),
timeout(3000)
]);
但要注意,race 返回了超时,不代表底层请求真的被取消。真正取消请求需要配合 AbortController。
🟪 资深答案
资深会进一步考虑:
- 并发数限制;
- 失败重试;
- 部分失败补偿;
- 幂等;
- 请求取消;
- 任务队列;
- 依赖隔离;
- 防止一次性创建过多 Promise 打爆下游。
⚠️ 容易踩坑
Promise.all 失败后,其他已经发出的任务并不会自动取消。
如果这些任务有副作用,比如扣库存、发邮件,就必须额外设计幂等和补偿。
✅ 复习重点
Promise 并发不是只选 API,而是要根据强依赖、弱依赖、竞速、失败副作用来设计。
7. 下面这种写法有什么风险?
题目
js
users.forEach(async user => {
await sendEmail(user.email);
});
风险图
#mermaid-svg-VGENapbHo8Ff6oxd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VGENapbHo8Ff6oxd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VGENapbHo8Ff6oxd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VGENapbHo8Ff6oxd .error-icon{fill:#552222;}#mermaid-svg-VGENapbHo8Ff6oxd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VGENapbHo8Ff6oxd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VGENapbHo8Ff6oxd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VGENapbHo8Ff6oxd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VGENapbHo8Ff6oxd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VGENapbHo8Ff6oxd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VGENapbHo8Ff6oxd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VGENapbHo8Ff6oxd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VGENapbHo8Ff6oxd .marker.cross{stroke:#333333;}#mermaid-svg-VGENapbHo8Ff6oxd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VGENapbHo8Ff6oxd p{margin:0;}#mermaid-svg-VGENapbHo8Ff6oxd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VGENapbHo8Ff6oxd .cluster-label text{fill:#333;}#mermaid-svg-VGENapbHo8Ff6oxd .cluster-label span{color:#333;}#mermaid-svg-VGENapbHo8Ff6oxd .cluster-label span p{background-color:transparent;}#mermaid-svg-VGENapbHo8Ff6oxd .label text,#mermaid-svg-VGENapbHo8Ff6oxd span{fill:#333;color:#333;}#mermaid-svg-VGENapbHo8Ff6oxd .node rect,#mermaid-svg-VGENapbHo8Ff6oxd .node circle,#mermaid-svg-VGENapbHo8Ff6oxd .node ellipse,#mermaid-svg-VGENapbHo8Ff6oxd .node polygon,#mermaid-svg-VGENapbHo8Ff6oxd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VGENapbHo8Ff6oxd .rough-node .label text,#mermaid-svg-VGENapbHo8Ff6oxd .node .label text,#mermaid-svg-VGENapbHo8Ff6oxd .image-shape .label,#mermaid-svg-VGENapbHo8Ff6oxd .icon-shape .label{text-anchor:middle;}#mermaid-svg-VGENapbHo8Ff6oxd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VGENapbHo8Ff6oxd .rough-node .label,#mermaid-svg-VGENapbHo8Ff6oxd .node .label,#mermaid-svg-VGENapbHo8Ff6oxd .image-shape .label,#mermaid-svg-VGENapbHo8Ff6oxd .icon-shape .label{text-align:center;}#mermaid-svg-VGENapbHo8Ff6oxd .node.clickable{cursor:pointer;}#mermaid-svg-VGENapbHo8Ff6oxd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VGENapbHo8Ff6oxd .arrowheadPath{fill:#333333;}#mermaid-svg-VGENapbHo8Ff6oxd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VGENapbHo8Ff6oxd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VGENapbHo8Ff6oxd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VGENapbHo8Ff6oxd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VGENapbHo8Ff6oxd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VGENapbHo8Ff6oxd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VGENapbHo8Ff6oxd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VGENapbHo8Ff6oxd .cluster text{fill:#333;}#mermaid-svg-VGENapbHo8Ff6oxd .cluster span{color:#333;}#mermaid-svg-VGENapbHo8Ff6oxd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VGENapbHo8Ff6oxd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VGENapbHo8Ff6oxd rect.text{fill:none;stroke-width:0;}#mermaid-svg-VGENapbHo8Ff6oxd .icon-shape,#mermaid-svg-VGENapbHo8Ff6oxd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VGENapbHo8Ff6oxd .icon-shape p,#mermaid-svg-VGENapbHo8Ff6oxd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VGENapbHo8Ff6oxd .icon-shape .label rect,#mermaid-svg-VGENapbHo8Ff6oxd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VGENapbHo8Ff6oxd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VGENapbHo8Ff6oxd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VGENapbHo8Ff6oxd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} forEach 调用 async 回调
不会等待 Promise
外层无法 await
错误难以捕获
并发不可控
可能打爆邮件服务
🟩 中级答案
forEach 不会等待 async 回调完成,外层无法知道所有邮件什么时候发送完。错误也可能无法被正确捕获。
🟦 高级答案
如果要顺序执行:
js
for (const user of users) {
await sendEmail(user.email);
}
如果要并发执行:
js
await Promise.all(
users.map(user => sendEmail(user.email))
);
如果用户数量很大,应该限制并发:
js
const limit = pLimit(10);
await Promise.all(
users.map(user => limit(() => sendEmail(user.email)))
);
🟪 资深答案
真实业务中,批量发邮件不一定应该在接口请求内直接完成。更稳的方案是:
#mermaid-svg-6cFltFasKSpUy44i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6cFltFasKSpUy44i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6cFltFasKSpUy44i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6cFltFasKSpUy44i .error-icon{fill:#552222;}#mermaid-svg-6cFltFasKSpUy44i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6cFltFasKSpUy44i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6cFltFasKSpUy44i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6cFltFasKSpUy44i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6cFltFasKSpUy44i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6cFltFasKSpUy44i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6cFltFasKSpUy44i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6cFltFasKSpUy44i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6cFltFasKSpUy44i .marker.cross{stroke:#333333;}#mermaid-svg-6cFltFasKSpUy44i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6cFltFasKSpUy44i p{margin:0;}#mermaid-svg-6cFltFasKSpUy44i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6cFltFasKSpUy44i .cluster-label text{fill:#333;}#mermaid-svg-6cFltFasKSpUy44i .cluster-label span{color:#333;}#mermaid-svg-6cFltFasKSpUy44i .cluster-label span p{background-color:transparent;}#mermaid-svg-6cFltFasKSpUy44i .label text,#mermaid-svg-6cFltFasKSpUy44i span{fill:#333;color:#333;}#mermaid-svg-6cFltFasKSpUy44i .node rect,#mermaid-svg-6cFltFasKSpUy44i .node circle,#mermaid-svg-6cFltFasKSpUy44i .node ellipse,#mermaid-svg-6cFltFasKSpUy44i .node polygon,#mermaid-svg-6cFltFasKSpUy44i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6cFltFasKSpUy44i .rough-node .label text,#mermaid-svg-6cFltFasKSpUy44i .node .label text,#mermaid-svg-6cFltFasKSpUy44i .image-shape .label,#mermaid-svg-6cFltFasKSpUy44i .icon-shape .label{text-anchor:middle;}#mermaid-svg-6cFltFasKSpUy44i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6cFltFasKSpUy44i .rough-node .label,#mermaid-svg-6cFltFasKSpUy44i .node .label,#mermaid-svg-6cFltFasKSpUy44i .image-shape .label,#mermaid-svg-6cFltFasKSpUy44i .icon-shape .label{text-align:center;}#mermaid-svg-6cFltFasKSpUy44i .node.clickable{cursor:pointer;}#mermaid-svg-6cFltFasKSpUy44i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6cFltFasKSpUy44i .arrowheadPath{fill:#333333;}#mermaid-svg-6cFltFasKSpUy44i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6cFltFasKSpUy44i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6cFltFasKSpUy44i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6cFltFasKSpUy44i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6cFltFasKSpUy44i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6cFltFasKSpUy44i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6cFltFasKSpUy44i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6cFltFasKSpUy44i .cluster text{fill:#333;}#mermaid-svg-6cFltFasKSpUy44i .cluster span{color:#333;}#mermaid-svg-6cFltFasKSpUy44i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6cFltFasKSpUy44i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6cFltFasKSpUy44i rect.text{fill:none;stroke-width:0;}#mermaid-svg-6cFltFasKSpUy44i .icon-shape,#mermaid-svg-6cFltFasKSpUy44i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6cFltFasKSpUy44i .icon-shape p,#mermaid-svg-6cFltFasKSpUy44i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6cFltFasKSpUy44i .icon-shape .label rect,#mermaid-svg-6cFltFasKSpUy44i .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6cFltFasKSpUy44i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6cFltFasKSpUy44i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6cFltFasKSpUy44i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接口提交发送任务
写入消息队列
Worker 限速消费
邮件服务
失败重试
死信队列
还要考虑:
- 邮件服务限流;
- 失败重试;
- 幂等,避免重复发送;
- 发送状态记录;
- 退订和黑名单;
- 批量任务进度。
✅ 复习重点
批量异步任务要明确:顺序、并发、限流、失败处理、是否需要队列。
8. 一个接口偶尔会超时,你会从哪些方面排查?
排查图
#mermaid-svg-L4TftZZ5zaHh52OG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-L4TftZZ5zaHh52OG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-L4TftZZ5zaHh52OG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-L4TftZZ5zaHh52OG .error-icon{fill:#552222;}#mermaid-svg-L4TftZZ5zaHh52OG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-L4TftZZ5zaHh52OG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-L4TftZZ5zaHh52OG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-L4TftZZ5zaHh52OG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-L4TftZZ5zaHh52OG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-L4TftZZ5zaHh52OG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-L4TftZZ5zaHh52OG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-L4TftZZ5zaHh52OG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-L4TftZZ5zaHh52OG .marker.cross{stroke:#333333;}#mermaid-svg-L4TftZZ5zaHh52OG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-L4TftZZ5zaHh52OG p{margin:0;}#mermaid-svg-L4TftZZ5zaHh52OG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-L4TftZZ5zaHh52OG .cluster-label text{fill:#333;}#mermaid-svg-L4TftZZ5zaHh52OG .cluster-label span{color:#333;}#mermaid-svg-L4TftZZ5zaHh52OG .cluster-label span p{background-color:transparent;}#mermaid-svg-L4TftZZ5zaHh52OG .label text,#mermaid-svg-L4TftZZ5zaHh52OG span{fill:#333;color:#333;}#mermaid-svg-L4TftZZ5zaHh52OG .node rect,#mermaid-svg-L4TftZZ5zaHh52OG .node circle,#mermaid-svg-L4TftZZ5zaHh52OG .node ellipse,#mermaid-svg-L4TftZZ5zaHh52OG .node polygon,#mermaid-svg-L4TftZZ5zaHh52OG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-L4TftZZ5zaHh52OG .rough-node .label text,#mermaid-svg-L4TftZZ5zaHh52OG .node .label text,#mermaid-svg-L4TftZZ5zaHh52OG .image-shape .label,#mermaid-svg-L4TftZZ5zaHh52OG .icon-shape .label{text-anchor:middle;}#mermaid-svg-L4TftZZ5zaHh52OG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-L4TftZZ5zaHh52OG .rough-node .label,#mermaid-svg-L4TftZZ5zaHh52OG .node .label,#mermaid-svg-L4TftZZ5zaHh52OG .image-shape .label,#mermaid-svg-L4TftZZ5zaHh52OG .icon-shape .label{text-align:center;}#mermaid-svg-L4TftZZ5zaHh52OG .node.clickable{cursor:pointer;}#mermaid-svg-L4TftZZ5zaHh52OG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-L4TftZZ5zaHh52OG .arrowheadPath{fill:#333333;}#mermaid-svg-L4TftZZ5zaHh52OG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-L4TftZZ5zaHh52OG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-L4TftZZ5zaHh52OG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-L4TftZZ5zaHh52OG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-L4TftZZ5zaHh52OG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-L4TftZZ5zaHh52OG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-L4TftZZ5zaHh52OG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-L4TftZZ5zaHh52OG .cluster text{fill:#333;}#mermaid-svg-L4TftZZ5zaHh52OG .cluster span{color:#333;}#mermaid-svg-L4TftZZ5zaHh52OG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-L4TftZZ5zaHh52OG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-L4TftZZ5zaHh52OG rect.text{fill:none;stroke-width:0;}#mermaid-svg-L4TftZZ5zaHh52OG .icon-shape,#mermaid-svg-L4TftZZ5zaHh52OG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-L4TftZZ5zaHh52OG .icon-shape p,#mermaid-svg-L4TftZZ5zaHh52OG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-L4TftZZ5zaHh52OG .icon-shape .label rect,#mermaid-svg-L4TftZZ5zaHh52OG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-L4TftZZ5zaHh52OG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-L4TftZZ5zaHh52OG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-L4TftZZ5zaHh52OG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接口偶尔超时
确认超时发生在哪一层
前端/网关
Node 服务
数据库
Redis
第三方服务
event loop lag / CPU / GC
慢 SQL / 锁等待 / 连接池
热 key / 大 key / 高延迟
依赖波动 / DNS / 网络
🟩 中级答案
可以从数据库、业务代码、缓存、网络、第三方接口排查。重点看 SQL 是否慢、索引是否失效、业务是否阻塞线程、网络是否波动。
🟦 高级答案
先定义"超时"发生在哪一层:
- 浏览器超时;
- 网关超时;
- Node 服务超时;
- 数据库查询超时;
- Redis 超时;
- 第三方服务超时。
然后通过 traceId 串起链路,看耗时集中在哪一段。重点看:
- P95 / P99 延迟;
- 慢 SQL;
- DB 连接池;
- Redis 延迟;
- event loop lag;
- CPU;
- GC;
- 请求参数;
- 用户、租户、地区、版本;
- 是否集中在流量高峰。
🟪 资深答案
资深排查会分成两类动作:
短期止血:
- 限流;
- 降级;
- 熔断;
- 回滚;
- 扩容;
- 临时加缓存;
- 调整超时阈值。
长期治理:
- 优化慢 SQL;
- 拆分重逻辑;
- 引入异步化;
- 增加链路追踪;
- 增加 dashboard;
- 建立 SLO;
- 做容量压测;
- 补充复盘机制。
✅ 复习重点
偶发超时不要靠猜。要用 trace、指标、日志把链路切开,看时间到底花在哪。
9. Node 服务里如何防止 SQL 注入?
防护图
#mermaid-svg-OrLHOrWHQ3TEAa1N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OrLHOrWHQ3TEAa1N .error-icon{fill:#552222;}#mermaid-svg-OrLHOrWHQ3TEAa1N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OrLHOrWHQ3TEAa1N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .marker.cross{stroke:#333333;}#mermaid-svg-OrLHOrWHQ3TEAa1N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OrLHOrWHQ3TEAa1N p{margin:0;}#mermaid-svg-OrLHOrWHQ3TEAa1N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .cluster-label text{fill:#333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .cluster-label span{color:#333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .cluster-label span p{background-color:transparent;}#mermaid-svg-OrLHOrWHQ3TEAa1N .label text,#mermaid-svg-OrLHOrWHQ3TEAa1N span{fill:#333;color:#333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .node rect,#mermaid-svg-OrLHOrWHQ3TEAa1N .node circle,#mermaid-svg-OrLHOrWHQ3TEAa1N .node ellipse,#mermaid-svg-OrLHOrWHQ3TEAa1N .node polygon,#mermaid-svg-OrLHOrWHQ3TEAa1N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OrLHOrWHQ3TEAa1N .rough-node .label text,#mermaid-svg-OrLHOrWHQ3TEAa1N .node .label text,#mermaid-svg-OrLHOrWHQ3TEAa1N .image-shape .label,#mermaid-svg-OrLHOrWHQ3TEAa1N .icon-shape .label{text-anchor:middle;}#mermaid-svg-OrLHOrWHQ3TEAa1N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OrLHOrWHQ3TEAa1N .rough-node .label,#mermaid-svg-OrLHOrWHQ3TEAa1N .node .label,#mermaid-svg-OrLHOrWHQ3TEAa1N .image-shape .label,#mermaid-svg-OrLHOrWHQ3TEAa1N .icon-shape .label{text-align:center;}#mermaid-svg-OrLHOrWHQ3TEAa1N .node.clickable{cursor:pointer;}#mermaid-svg-OrLHOrWHQ3TEAa1N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .arrowheadPath{fill:#333333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OrLHOrWHQ3TEAa1N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OrLHOrWHQ3TEAa1N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OrLHOrWHQ3TEAa1N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OrLHOrWHQ3TEAa1N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OrLHOrWHQ3TEAa1N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OrLHOrWHQ3TEAa1N .cluster text{fill:#333;}#mermaid-svg-OrLHOrWHQ3TEAa1N .cluster span{color:#333;}#mermaid-svg-OrLHOrWHQ3TEAa1N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OrLHOrWHQ3TEAa1N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OrLHOrWHQ3TEAa1N rect.text{fill:none;stroke-width:0;}#mermaid-svg-OrLHOrWHQ3TEAa1N .icon-shape,#mermaid-svg-OrLHOrWHQ3TEAa1N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OrLHOrWHQ3TEAa1N .icon-shape p,#mermaid-svg-OrLHOrWHQ3TEAa1N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OrLHOrWHQ3TEAa1N .icon-shape .label rect,#mermaid-svg-OrLHOrWHQ3TEAa1N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OrLHOrWHQ3TEAa1N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OrLHOrWHQ3TEAa1N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OrLHOrWHQ3TEAa1N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户输入
输入校验
参数化查询
数据库执行
动态字段/排序
白名单校验
数据库账号
最小权限
代码流程
Review / 安全扫描
🟩 中级答案
不要把用户输入直接拼接到 SQL 里。使用 ORM、query builder 或参数化查询来防止 SQL 注入。
🟦 高级答案
防 SQL 注入的核心是参数化查询:
js
await db.query(
'select id, name from users where id = ?',
[id]
);
但有些东西不能简单参数化,比如排序字段:
js
// 危险
const sql = `select * from users order by ${sortField}`;
应该使用白名单:
js
const allowedSortFields = ['id', 'name', 'created_at'];
if (!allowedSortFields.includes(sortField)) {
throw new BadRequestError('Invalid sort field');
}
🟪 资深答案
资深会考虑整体安全体系:
- 参数化查询;
- 动态字段白名单;
- 输入校验;
- ORM raw SQL 审查;
- 数据库账号最小权限;
- 代码 review;
- SAST 安全扫描;
- SQL 审计;
- 不把数据库错误直接返回给用户。
⚠️ 容易踩坑
ORM 不是绝对安全。
一旦你写 raw SQL,仍然可能注入。
✅ 复习重点
参数化查询是防 SQL 注入的核心,动态字段必须走白名单。
10. 什么情况下你会用 Redis?请说至少 3 个真实业务场景。
场景图
#mermaid-svg-7d9cfAd0BE2X7sMf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7d9cfAd0BE2X7sMf .error-icon{fill:#552222;}#mermaid-svg-7d9cfAd0BE2X7sMf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7d9cfAd0BE2X7sMf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7d9cfAd0BE2X7sMf .marker.cross{stroke:#333333;}#mermaid-svg-7d9cfAd0BE2X7sMf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7d9cfAd0BE2X7sMf p{margin:0;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge{stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .section--1 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section--1 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section--1 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section--1 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section--1 text{fill:#ffffff;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth--1{stroke-width:17;}#mermaid-svg-7d9cfAd0BE2X7sMf .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-0 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-0 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-0 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-0 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-0 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-0{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-0{stroke-width:14;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-1 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-1 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-1 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-1 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-1 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-1{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-1{stroke-width:11;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 text{fill:#ffffff;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-2{stroke-width:8;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-3 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-3 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-3 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-3 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-3 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-3{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-3{stroke-width:5;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-4 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-4 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-4 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-4 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-4 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-4{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-4{stroke-width:2;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-5 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-5 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-5 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-5 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-5 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-5{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-5{stroke-width:-1;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-6 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-6 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-6 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-6 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-6 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-6{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-6{stroke-width:-4;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-7 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-7 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-7 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-7 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-7 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-7{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-7{stroke-width:-7;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-8 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-8 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-8 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-8 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-8 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-8{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-8{stroke-width:-10;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-9 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-9 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-9 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-9 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-9 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-9{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-9{stroke-width:-13;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-10 rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-10 path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-10 circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-10 polygon,#mermaid-svg-7d9cfAd0BE2X7sMf .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-10 text{fill:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .node-icon-10{font-size:40px;color:black;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .edge-depth-10{stroke-width:-16;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled circle,#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:lightgray;}#mermaid-svg-7d9cfAd0BE2X7sMf .disabled text{fill:#efefef;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-root rect,#mermaid-svg-7d9cfAd0BE2X7sMf .section-root path,#mermaid-svg-7d9cfAd0BE2X7sMf .section-root circle,#mermaid-svg-7d9cfAd0BE2X7sMf .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-7d9cfAd0BE2X7sMf .section-root text{fill:#ffffff;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-root span{color:#ffffff;}#mermaid-svg-7d9cfAd0BE2X7sMf .section-2 span{color:#ffffff;}#mermaid-svg-7d9cfAd0BE2X7sMf .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-7d9cfAd0BE2X7sMf .edge{fill:none;}#mermaid-svg-7d9cfAd0BE2X7sMf .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-7d9cfAd0BE2X7sMf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Redis
缓存
商品详情
用户权限
首页配置
计数
点赞
浏览量
接口调用次数
排行榜
zset
积分榜
热度榜
限流
IP
用户
接口
分布式锁
防重复提交
库存保护
幂等
支付回调
消息消费
🟩 中级答案
Redis 可以用于热点数据缓存、排行榜、计数器、频繁访问接口的数据缓存。
🟦 高级答案
真实业务场景包括:
- 缓存商品详情、用户权限、系统配置;
- zset 做排行榜;
- 计数器,比如点赞、浏览量;
- 限流,比如按 IP、用户、接口维度限流;
- 分布式锁,比如防重复提交;
- 幂等控制,比如支付回调、消息消费;
- session 或 token 黑名单。
🟪 资深答案
资深不会只说"用 Redis",还会补上 Redis 的代价:
- 缓存一致性;
- 热 key;
- 大 key;
- TTL 设计;
- Redis 故障降级;
- 缓存穿透、击穿、雪崩;
- 命中率监控;
- 内存淘汰策略;
- 集群和高可用;
- key 命名规范。
⚠️ 容易踩坑
Redis 能提高性能,但它也会引入新的复杂度。
不能把 Redis 当成"万能性能补丁"。
✅ 复习重点
每次说"用 Redis",都要补一句:key 怎么设计、TTL 怎么定、一致性怎么保证、Redis 挂了怎么办。
11. 缓存穿透、缓存击穿、缓存雪崩分别是什么?如何处理?
对比图
#mermaid-svg-iPFBVEI9VFHT0Nl2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .error-icon{fill:#552222;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .marker.cross{stroke:#333333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 p{margin:0;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .cluster-label text{fill:#333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .cluster-label span{color:#333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .cluster-label span p{background-color:transparent;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .label text,#mermaid-svg-iPFBVEI9VFHT0Nl2 span{fill:#333;color:#333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .node rect,#mermaid-svg-iPFBVEI9VFHT0Nl2 .node circle,#mermaid-svg-iPFBVEI9VFHT0Nl2 .node ellipse,#mermaid-svg-iPFBVEI9VFHT0Nl2 .node polygon,#mermaid-svg-iPFBVEI9VFHT0Nl2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .rough-node .label text,#mermaid-svg-iPFBVEI9VFHT0Nl2 .node .label text,#mermaid-svg-iPFBVEI9VFHT0Nl2 .image-shape .label,#mermaid-svg-iPFBVEI9VFHT0Nl2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .rough-node .label,#mermaid-svg-iPFBVEI9VFHT0Nl2 .node .label,#mermaid-svg-iPFBVEI9VFHT0Nl2 .image-shape .label,#mermaid-svg-iPFBVEI9VFHT0Nl2 .icon-shape .label{text-align:center;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .node.clickable{cursor:pointer;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .arrowheadPath{fill:#333333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iPFBVEI9VFHT0Nl2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iPFBVEI9VFHT0Nl2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iPFBVEI9VFHT0Nl2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .cluster text{fill:#333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .cluster span{color:#333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iPFBVEI9VFHT0Nl2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .icon-shape,#mermaid-svg-iPFBVEI9VFHT0Nl2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .icon-shape p,#mermaid-svg-iPFBVEI9VFHT0Nl2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .icon-shape .label rect,#mermaid-svg-iPFBVEI9VFHT0Nl2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iPFBVEI9VFHT0Nl2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iPFBVEI9VFHT0Nl2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iPFBVEI9VFHT0Nl2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 命中
未命中
不存在
存在但热点 key 过期
大量 key 同时失效/Redis 故障
请求进入
缓存是否命中?
返回缓存
数据库是否存在?
穿透
击穿
雪崩
🟩 中级答案
缓存穿透:缓存和数据库都没有,请求一直打数据库。
缓存击穿:热点 key 过期,大量请求打数据库。
缓存雪崩:大量 key 同时过期或 Redis 故障,大量请求打数据库。
🟦 高级答案
解决方式:
穿透:
- 参数校验;
- 布隆过滤器;
- 空值缓存;
- 限流。
击穿:
- 互斥锁;
- singleflight;
- 热点 key 逻辑过期;
- 后台异步刷新。
雪崩:
- TTL 加随机值;
- 缓存预热;
- Redis 高可用;
- 多级缓存;
- 限流、熔断、降级。
🟪 资深答案
资深会结合业务一致性考虑缓存策略:
- 强一致数据不要随便缓存;
- 弱一致热点数据可以 cache aside;
- 热点 key 可以逻辑过期;
- 大促前要预热;
- Redis 故障时服务要有降级路径;
- 监控缓存命中率、Redis 延迟、热 key、大 key。
⚠️ 容易踩坑
"缓存没了查数据库"不等于击穿。
击穿强调的是:热点 key 失效 + 高并发同时打 DB。
✅ 复习重点
穿透是"查不存在",击穿是"热点过期",雪崩是"大面积失效或缓存整体不可用"。
12. 用户下单:创建订单、扣库存、扣余额、发送通知,如何保证一致性?
流程图
#mermaid-svg-luNFZKN6UrUMHPM2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-luNFZKN6UrUMHPM2 .error-icon{fill:#552222;}#mermaid-svg-luNFZKN6UrUMHPM2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-luNFZKN6UrUMHPM2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-luNFZKN6UrUMHPM2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-luNFZKN6UrUMHPM2 .marker.cross{stroke:#333333;}#mermaid-svg-luNFZKN6UrUMHPM2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-luNFZKN6UrUMHPM2 p{margin:0;}#mermaid-svg-luNFZKN6UrUMHPM2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-luNFZKN6UrUMHPM2 .cluster-label text{fill:#333;}#mermaid-svg-luNFZKN6UrUMHPM2 .cluster-label span{color:#333;}#mermaid-svg-luNFZKN6UrUMHPM2 .cluster-label span p{background-color:transparent;}#mermaid-svg-luNFZKN6UrUMHPM2 .label text,#mermaid-svg-luNFZKN6UrUMHPM2 span{fill:#333;color:#333;}#mermaid-svg-luNFZKN6UrUMHPM2 .node rect,#mermaid-svg-luNFZKN6UrUMHPM2 .node circle,#mermaid-svg-luNFZKN6UrUMHPM2 .node ellipse,#mermaid-svg-luNFZKN6UrUMHPM2 .node polygon,#mermaid-svg-luNFZKN6UrUMHPM2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-luNFZKN6UrUMHPM2 .rough-node .label text,#mermaid-svg-luNFZKN6UrUMHPM2 .node .label text,#mermaid-svg-luNFZKN6UrUMHPM2 .image-shape .label,#mermaid-svg-luNFZKN6UrUMHPM2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-luNFZKN6UrUMHPM2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-luNFZKN6UrUMHPM2 .rough-node .label,#mermaid-svg-luNFZKN6UrUMHPM2 .node .label,#mermaid-svg-luNFZKN6UrUMHPM2 .image-shape .label,#mermaid-svg-luNFZKN6UrUMHPM2 .icon-shape .label{text-align:center;}#mermaid-svg-luNFZKN6UrUMHPM2 .node.clickable{cursor:pointer;}#mermaid-svg-luNFZKN6UrUMHPM2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-luNFZKN6UrUMHPM2 .arrowheadPath{fill:#333333;}#mermaid-svg-luNFZKN6UrUMHPM2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-luNFZKN6UrUMHPM2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-luNFZKN6UrUMHPM2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-luNFZKN6UrUMHPM2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-luNFZKN6UrUMHPM2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-luNFZKN6UrUMHPM2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-luNFZKN6UrUMHPM2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-luNFZKN6UrUMHPM2 .cluster text{fill:#333;}#mermaid-svg-luNFZKN6UrUMHPM2 .cluster span{color:#333;}#mermaid-svg-luNFZKN6UrUMHPM2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-luNFZKN6UrUMHPM2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-luNFZKN6UrUMHPM2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-luNFZKN6UrUMHPM2 .icon-shape,#mermaid-svg-luNFZKN6UrUMHPM2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-luNFZKN6UrUMHPM2 .icon-shape p,#mermaid-svg-luNFZKN6UrUMHPM2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-luNFZKN6UrUMHPM2 .icon-shape .label rect,#mermaid-svg-luNFZKN6UrUMHPM2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-luNFZKN6UrUMHPM2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-luNFZKN6UrUMHPM2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-luNFZKN6UrUMHPM2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户下单
创建订单
扣减库存
扣减余额
提交事务
写出事件 / outbox
异步发送通知
失败重试 / 死信 / 补偿
🟩 中级答案
订单、库存、余额需要保证一致性,应该放在事务里。通知不应该放进主事务,可以异步发送。
🟦 高级答案
如果订单、库存、余额在同一个数据库,可以使用本地事务:
- 创建订单;
- 扣减库存;
- 扣减余额;
- 写本地消息表;
- 提交事务;
- 异步发送通知。
如果是跨服务,就不能简单用本地事务。可以使用:
- TCC;
- Saga;
- 可靠消息最终一致;
- MQ + 幂等 + 补偿;
- outbox 模式。
🟪 资深答案
资深会考虑异常闭环:
- 消息如何保证不丢;
- 消费者如何保证幂等;
- 库存扣了但余额失败怎么办;
- 通知失败是否影响订单;
- 支付回调重复怎么办;
- 超时订单如何取消;
- 对账如何发现异常;
- 补偿失败如何人工介入。
⚠️ 容易踩坑
MQ 不能自动保证一致性。
它只是提供异步和解耦,业务一致性还要靠可靠投递、幂等、补偿、对账。
✅ 复习重点
下单系统的核心不是"用不用 MQ",而是事务边界、消息可靠性、幂等、补偿和对账。
13. Node.js 单线程是什么意思?它真的只能用一个 CPU 核心吗?
概念图
#mermaid-svg-EfpXhnwh8DPnUE9q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EfpXhnwh8DPnUE9q .error-icon{fill:#552222;}#mermaid-svg-EfpXhnwh8DPnUE9q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EfpXhnwh8DPnUE9q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EfpXhnwh8DPnUE9q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EfpXhnwh8DPnUE9q .marker.cross{stroke:#333333;}#mermaid-svg-EfpXhnwh8DPnUE9q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EfpXhnwh8DPnUE9q p{margin:0;}#mermaid-svg-EfpXhnwh8DPnUE9q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EfpXhnwh8DPnUE9q .cluster-label text{fill:#333;}#mermaid-svg-EfpXhnwh8DPnUE9q .cluster-label span{color:#333;}#mermaid-svg-EfpXhnwh8DPnUE9q .cluster-label span p{background-color:transparent;}#mermaid-svg-EfpXhnwh8DPnUE9q .label text,#mermaid-svg-EfpXhnwh8DPnUE9q span{fill:#333;color:#333;}#mermaid-svg-EfpXhnwh8DPnUE9q .node rect,#mermaid-svg-EfpXhnwh8DPnUE9q .node circle,#mermaid-svg-EfpXhnwh8DPnUE9q .node ellipse,#mermaid-svg-EfpXhnwh8DPnUE9q .node polygon,#mermaid-svg-EfpXhnwh8DPnUE9q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EfpXhnwh8DPnUE9q .rough-node .label text,#mermaid-svg-EfpXhnwh8DPnUE9q .node .label text,#mermaid-svg-EfpXhnwh8DPnUE9q .image-shape .label,#mermaid-svg-EfpXhnwh8DPnUE9q .icon-shape .label{text-anchor:middle;}#mermaid-svg-EfpXhnwh8DPnUE9q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EfpXhnwh8DPnUE9q .rough-node .label,#mermaid-svg-EfpXhnwh8DPnUE9q .node .label,#mermaid-svg-EfpXhnwh8DPnUE9q .image-shape .label,#mermaid-svg-EfpXhnwh8DPnUE9q .icon-shape .label{text-align:center;}#mermaid-svg-EfpXhnwh8DPnUE9q .node.clickable{cursor:pointer;}#mermaid-svg-EfpXhnwh8DPnUE9q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EfpXhnwh8DPnUE9q .arrowheadPath{fill:#333333;}#mermaid-svg-EfpXhnwh8DPnUE9q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EfpXhnwh8DPnUE9q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EfpXhnwh8DPnUE9q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EfpXhnwh8DPnUE9q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EfpXhnwh8DPnUE9q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EfpXhnwh8DPnUE9q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EfpXhnwh8DPnUE9q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EfpXhnwh8DPnUE9q .cluster text{fill:#333;}#mermaid-svg-EfpXhnwh8DPnUE9q .cluster span{color:#333;}#mermaid-svg-EfpXhnwh8DPnUE9q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EfpXhnwh8DPnUE9q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EfpXhnwh8DPnUE9q rect.text{fill:none;stroke-width:0;}#mermaid-svg-EfpXhnwh8DPnUE9q .icon-shape,#mermaid-svg-EfpXhnwh8DPnUE9q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EfpXhnwh8DPnUE9q .icon-shape p,#mermaid-svg-EfpXhnwh8DPnUE9q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EfpXhnwh8DPnUE9q .icon-shape .label rect,#mermaid-svg-EfpXhnwh8DPnUE9q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EfpXhnwh8DPnUE9q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EfpXhnwh8DPnUE9q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EfpXhnwh8DPnUE9q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Node 进程
JS 主线程
libuv 线程池
Worker Threads
Cluster
Node 进程 1
Node 进程 2
Node 进程 N
🟩 中级答案
Node 的业务 JS 主要运行在一个主线程上。CPU 密集型任务会阻塞这个主线程。但 Node 可以通过 Cluster 或 Worker Threads 利用多核。
🟦 高级答案
要区分几个概念:
- JS 执行主线程;
- libuv 线程池;
- Worker Threads;
- Cluster 多进程。
Cluster 不是多线程,而是多进程。多个进程可以利用多个 CPU 核心,但进程间内存不共享。
Worker Threads 是 Node 里处理 CPU 密集任务的多线程方案。
🟪 资深答案
资深会结合部署:
- PM2 cluster mode;
- Docker/K8s 多副本;
- 单容器 CPU limit;
- 进程数和 CPU 核心数匹配;
- 进程间状态不能放内存;
- sticky session 问题;
- Redis/DB/MQ 作为共享状态。
✅ 复习重点
Node 的"单线程"主要指 JS 主线程,不代表 Node 不能利用多核。
14. 如果一个接口 QPS 很高,你会怎么优化?
优化地图
#mermaid-svg-EDkrMqrdG5JzL31j{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EDkrMqrdG5JzL31j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EDkrMqrdG5JzL31j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EDkrMqrdG5JzL31j .error-icon{fill:#552222;}#mermaid-svg-EDkrMqrdG5JzL31j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EDkrMqrdG5JzL31j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EDkrMqrdG5JzL31j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EDkrMqrdG5JzL31j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EDkrMqrdG5JzL31j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EDkrMqrdG5JzL31j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EDkrMqrdG5JzL31j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EDkrMqrdG5JzL31j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EDkrMqrdG5JzL31j .marker.cross{stroke:#333333;}#mermaid-svg-EDkrMqrdG5JzL31j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EDkrMqrdG5JzL31j p{margin:0;}#mermaid-svg-EDkrMqrdG5JzL31j .edge{stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .section--1 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section--1 path,#mermaid-svg-EDkrMqrdG5JzL31j .section--1 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section--1 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section--1 text{fill:#ffffff;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth--1{stroke-width:17;}#mermaid-svg-EDkrMqrdG5JzL31j .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-0 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-0 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-0 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-0 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-0 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-0{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-0{stroke-width:14;}#mermaid-svg-EDkrMqrdG5JzL31j .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-1 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-1 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-1 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-1 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-1 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-1{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-1{stroke-width:11;}#mermaid-svg-EDkrMqrdG5JzL31j .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-2 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-2 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-2 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-2 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-2 text{fill:#ffffff;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-2{stroke-width:8;}#mermaid-svg-EDkrMqrdG5JzL31j .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-3 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-3 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-3 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-3 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-3 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-3{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-3{stroke-width:5;}#mermaid-svg-EDkrMqrdG5JzL31j .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-4 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-4 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-4 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-4 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-4 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-4{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-4{stroke-width:2;}#mermaid-svg-EDkrMqrdG5JzL31j .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-5 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-5 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-5 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-5 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-5 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-5{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-5{stroke-width:-1;}#mermaid-svg-EDkrMqrdG5JzL31j .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-6 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-6 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-6 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-6 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-6 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-6{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-6{stroke-width:-4;}#mermaid-svg-EDkrMqrdG5JzL31j .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-7 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-7 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-7 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-7 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-7 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-7{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-7{stroke-width:-7;}#mermaid-svg-EDkrMqrdG5JzL31j .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-8 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-8 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-8 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-8 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-8 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-8{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-8{stroke-width:-10;}#mermaid-svg-EDkrMqrdG5JzL31j .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-9 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-9 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-9 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-9 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-9 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-9{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-9{stroke-width:-13;}#mermaid-svg-EDkrMqrdG5JzL31j .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-10 rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-10 path,#mermaid-svg-EDkrMqrdG5JzL31j .section-10 circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-10 polygon,#mermaid-svg-EDkrMqrdG5JzL31j .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-10 text{fill:black;}#mermaid-svg-EDkrMqrdG5JzL31j .node-icon-10{font-size:40px;color:black;}#mermaid-svg-EDkrMqrdG5JzL31j .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .edge-depth-10{stroke-width:-16;}#mermaid-svg-EDkrMqrdG5JzL31j .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled,#mermaid-svg-EDkrMqrdG5JzL31j .disabled circle,#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:lightgray;}#mermaid-svg-EDkrMqrdG5JzL31j .disabled text{fill:#efefef;}#mermaid-svg-EDkrMqrdG5JzL31j .section-root rect,#mermaid-svg-EDkrMqrdG5JzL31j .section-root path,#mermaid-svg-EDkrMqrdG5JzL31j .section-root circle,#mermaid-svg-EDkrMqrdG5JzL31j .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-EDkrMqrdG5JzL31j .section-root text{fill:#ffffff;}#mermaid-svg-EDkrMqrdG5JzL31j .section-root span{color:#ffffff;}#mermaid-svg-EDkrMqrdG5JzL31j .section-2 span{color:#ffffff;}#mermaid-svg-EDkrMqrdG5JzL31j .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-EDkrMqrdG5JzL31j .edge{fill:none;}#mermaid-svg-EDkrMqrdG5JzL31j .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-EDkrMqrdG5JzL31j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 高 QPS 优化
观测
QPS
P95/P99
CPU
event loop lag
代码
减少阻塞
批量处理
避免 N+1
数据库
索引
慢 SQL
连接池
读写分离
缓存
Redis
本地缓存
多级缓存
预热
流量
限流
熔断
降级
部署
水平扩容
多副本
负载均衡
🟩 中级答案
可以从代码、数据库、缓存、部署、限流几个方面优化。比如减少阻塞逻辑、优化 SQL 和索引、加 Redis 缓存、水平扩容、令牌桶限流。
🟦 高级答案
高级回答会先说:不要先优化,先定位瓶颈。
观察指标:
- QPS;
- 平均延迟;
- P95/P99;
- CPU;
- 内存;
- event loop lag;
- DB 慢查询;
- Redis 延迟;
- 下游服务耗时。
优化方向:
- 代码层:减少同步阻塞、避免 N+1 查询、减少大对象序列化;
- DB 层:索引、SQL、连接池、读写分离、分页优化;
- 缓存层:Redis、本地缓存、多级缓存、热点预热;
- 流量层:限流、熔断、降级;
- 架构层:异步化、队列削峰、预计算、水平扩容。
🟪 资深答案
资深会考虑成本和业务边界:
- 接口是否必须实时;
- 是否能异步化;
- 是否能预计算;
- 是否能 CDN 缓存;
- 是否存在热点租户;
- 是否有 SLA/SLO;
- 扩容成本和收益;
- 是否会把压力转移到下游;
- 压测模型是否符合真实流量。
⚠️ 容易踩坑
不要一听 QPS 高就立刻加 Redis。
如果瓶颈在 CPU、JSON 序列化、DB 连接池或第三方接口,加 Redis 可能没有用。
✅ 复习重点
高 QPS 优化第一步是定位瓶颈,不是盲目加缓存。
15. 什么是内存泄漏?Node 项目中常见来源有哪些?
泄漏图
#mermaid-svg-KG25TqEtsd61n3pC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KG25TqEtsd61n3pC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KG25TqEtsd61n3pC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KG25TqEtsd61n3pC .error-icon{fill:#552222;}#mermaid-svg-KG25TqEtsd61n3pC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KG25TqEtsd61n3pC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KG25TqEtsd61n3pC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KG25TqEtsd61n3pC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KG25TqEtsd61n3pC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KG25TqEtsd61n3pC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KG25TqEtsd61n3pC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KG25TqEtsd61n3pC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KG25TqEtsd61n3pC .marker.cross{stroke:#333333;}#mermaid-svg-KG25TqEtsd61n3pC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KG25TqEtsd61n3pC p{margin:0;}#mermaid-svg-KG25TqEtsd61n3pC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KG25TqEtsd61n3pC .cluster-label text{fill:#333;}#mermaid-svg-KG25TqEtsd61n3pC .cluster-label span{color:#333;}#mermaid-svg-KG25TqEtsd61n3pC .cluster-label span p{background-color:transparent;}#mermaid-svg-KG25TqEtsd61n3pC .label text,#mermaid-svg-KG25TqEtsd61n3pC span{fill:#333;color:#333;}#mermaid-svg-KG25TqEtsd61n3pC .node rect,#mermaid-svg-KG25TqEtsd61n3pC .node circle,#mermaid-svg-KG25TqEtsd61n3pC .node ellipse,#mermaid-svg-KG25TqEtsd61n3pC .node polygon,#mermaid-svg-KG25TqEtsd61n3pC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KG25TqEtsd61n3pC .rough-node .label text,#mermaid-svg-KG25TqEtsd61n3pC .node .label text,#mermaid-svg-KG25TqEtsd61n3pC .image-shape .label,#mermaid-svg-KG25TqEtsd61n3pC .icon-shape .label{text-anchor:middle;}#mermaid-svg-KG25TqEtsd61n3pC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KG25TqEtsd61n3pC .rough-node .label,#mermaid-svg-KG25TqEtsd61n3pC .node .label,#mermaid-svg-KG25TqEtsd61n3pC .image-shape .label,#mermaid-svg-KG25TqEtsd61n3pC .icon-shape .label{text-align:center;}#mermaid-svg-KG25TqEtsd61n3pC .node.clickable{cursor:pointer;}#mermaid-svg-KG25TqEtsd61n3pC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KG25TqEtsd61n3pC .arrowheadPath{fill:#333333;}#mermaid-svg-KG25TqEtsd61n3pC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KG25TqEtsd61n3pC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KG25TqEtsd61n3pC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KG25TqEtsd61n3pC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KG25TqEtsd61n3pC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KG25TqEtsd61n3pC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KG25TqEtsd61n3pC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KG25TqEtsd61n3pC .cluster text{fill:#333;}#mermaid-svg-KG25TqEtsd61n3pC .cluster span{color:#333;}#mermaid-svg-KG25TqEtsd61n3pC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KG25TqEtsd61n3pC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KG25TqEtsd61n3pC rect.text{fill:none;stroke-width:0;}#mermaid-svg-KG25TqEtsd61n3pC .icon-shape,#mermaid-svg-KG25TqEtsd61n3pC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KG25TqEtsd61n3pC .icon-shape p,#mermaid-svg-KG25TqEtsd61n3pC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KG25TqEtsd61n3pC .icon-shape .label rect,#mermaid-svg-KG25TqEtsd61n3pC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KG25TqEtsd61n3pC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KG25TqEtsd61n3pC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KG25TqEtsd61n3pC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 对象已不再需要
仍被引用
GC 无法回收
heap 持续增长
频繁 GC
响应变慢 / OOM
🟩 中级答案
内存泄漏是指不再需要的对象仍然被引用,无法被 GC 回收。常见原因包括闭包、全局变量、未清理的定时器等。
🟦 高级答案
Node 项目常见泄漏来源:
- 全局 Map / 数组无限增长;
- 缓存没有上限;
- EventEmitter listener 没有移除;
- 定时器没有清理;
- 闭包持有大对象;
- 请求对象被异步任务长期引用;
- stream 没有关闭;
- 数据库连接或文件句柄没有释放。
🟪 资深答案
资深排查会看:
- heapUsed;
- rss;
- GC 次数和耗时;
- event loop lag;
- heap snapshot;
- 对象增长趋势;
- 引用链;
- 线上内存曲线。
工具包括:
- Chrome DevTools;
- heapdump;
- clinic.js;
- node --inspect;
- APM。
⚠️ 容易踩坑
内存上涨不一定都是泄漏。
也可能是缓存预热、流量上涨、对象池、V8 heap 策略导致。要看是否持续不可回落。
✅ 复习重点
内存泄漏的本质是"无用对象仍被引用"。排查核心是找引用链。
16. 如何设计一个文件上传接口?
架构图
#mermaid-svg-VNR1VMl2L9slLODc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VNR1VMl2L9slLODc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VNR1VMl2L9slLODc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VNR1VMl2L9slLODc .error-icon{fill:#552222;}#mermaid-svg-VNR1VMl2L9slLODc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VNR1VMl2L9slLODc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VNR1VMl2L9slLODc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VNR1VMl2L9slLODc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VNR1VMl2L9slLODc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VNR1VMl2L9slLODc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VNR1VMl2L9slLODc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VNR1VMl2L9slLODc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VNR1VMl2L9slLODc .marker.cross{stroke:#333333;}#mermaid-svg-VNR1VMl2L9slLODc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VNR1VMl2L9slLODc p{margin:0;}#mermaid-svg-VNR1VMl2L9slLODc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VNR1VMl2L9slLODc .cluster-label text{fill:#333;}#mermaid-svg-VNR1VMl2L9slLODc .cluster-label span{color:#333;}#mermaid-svg-VNR1VMl2L9slLODc .cluster-label span p{background-color:transparent;}#mermaid-svg-VNR1VMl2L9slLODc .label text,#mermaid-svg-VNR1VMl2L9slLODc span{fill:#333;color:#333;}#mermaid-svg-VNR1VMl2L9slLODc .node rect,#mermaid-svg-VNR1VMl2L9slLODc .node circle,#mermaid-svg-VNR1VMl2L9slLODc .node ellipse,#mermaid-svg-VNR1VMl2L9slLODc .node polygon,#mermaid-svg-VNR1VMl2L9slLODc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VNR1VMl2L9slLODc .rough-node .label text,#mermaid-svg-VNR1VMl2L9slLODc .node .label text,#mermaid-svg-VNR1VMl2L9slLODc .image-shape .label,#mermaid-svg-VNR1VMl2L9slLODc .icon-shape .label{text-anchor:middle;}#mermaid-svg-VNR1VMl2L9slLODc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VNR1VMl2L9slLODc .rough-node .label,#mermaid-svg-VNR1VMl2L9slLODc .node .label,#mermaid-svg-VNR1VMl2L9slLODc .image-shape .label,#mermaid-svg-VNR1VMl2L9slLODc .icon-shape .label{text-align:center;}#mermaid-svg-VNR1VMl2L9slLODc .node.clickable{cursor:pointer;}#mermaid-svg-VNR1VMl2L9slLODc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VNR1VMl2L9slLODc .arrowheadPath{fill:#333333;}#mermaid-svg-VNR1VMl2L9slLODc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VNR1VMl2L9slLODc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VNR1VMl2L9slLODc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VNR1VMl2L9slLODc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VNR1VMl2L9slLODc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VNR1VMl2L9slLODc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VNR1VMl2L9slLODc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VNR1VMl2L9slLODc .cluster text{fill:#333;}#mermaid-svg-VNR1VMl2L9slLODc .cluster span{color:#333;}#mermaid-svg-VNR1VMl2L9slLODc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VNR1VMl2L9slLODc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VNR1VMl2L9slLODc rect.text{fill:none;stroke-width:0;}#mermaid-svg-VNR1VMl2L9slLODc .icon-shape,#mermaid-svg-VNR1VMl2L9slLODc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VNR1VMl2L9slLODc .icon-shape p,#mermaid-svg-VNR1VMl2L9slLODc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VNR1VMl2L9slLODc .icon-shape .label rect,#mermaid-svg-VNR1VMl2L9slLODc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VNR1VMl2L9slLODc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VNR1VMl2L9slLODc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VNR1VMl2L9slLODc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
前端选择文件
计算 hash
申请 uploadId / 预签名 URL
分片上传到对象存储
记录分片状态
是否全部完成?
合并分片
后端记录文件元数据
权限控制 / 安全扫描
🟩 中级答案
大文件使用分片上传和断点续传。文件要限制大小、类型、数量。上传失败后支持重试。文件可以存储到对象存储。
🟦 高级答案
设计要点:
- 前端计算 hash;
- 后端创建 uploadId;
- 分片上传;
- 记录 chunkIndex 和状态;
- 支持断点续传;
- 支持秒传;
- 后端校验文件魔数;
- 使用白名单限制类型;
- 限制大小、数量和频率;
- 对象存储使用预签名 URL 或临时凭证。
🟪 资深答案
资深会进一步考虑:
- 上传状态表;
- 未完成分片清理任务;
- 病毒扫描;
- 图片重新编码;
- 文件名污染;
- 私有桶权限;
- CDN 访问控制;
- 文件权限模型;
- 上传回调校验;
- 大文件合并失败补偿;
- 多租户隔离。
⚠️ 容易踩坑
前端校验不可信。
后端必须做文件类型、大小、权限、安全校验。
✅ 复习重点
文件上传不是一个简单接口,而是性能、安全、可靠性、权限控制的组合题。
17. 一个 Node 服务上线后,用户反馈"偶尔失败",但本地无法复现。你会怎么排查?
排查链路图
#mermaid-svg-t5h2xn8bKJUz4LNn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-t5h2xn8bKJUz4LNn .error-icon{fill:#552222;}#mermaid-svg-t5h2xn8bKJUz4LNn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-t5h2xn8bKJUz4LNn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-t5h2xn8bKJUz4LNn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-t5h2xn8bKJUz4LNn .marker.cross{stroke:#333333;}#mermaid-svg-t5h2xn8bKJUz4LNn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-t5h2xn8bKJUz4LNn p{margin:0;}#mermaid-svg-t5h2xn8bKJUz4LNn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-t5h2xn8bKJUz4LNn .cluster-label text{fill:#333;}#mermaid-svg-t5h2xn8bKJUz4LNn .cluster-label span{color:#333;}#mermaid-svg-t5h2xn8bKJUz4LNn .cluster-label span p{background-color:transparent;}#mermaid-svg-t5h2xn8bKJUz4LNn .label text,#mermaid-svg-t5h2xn8bKJUz4LNn span{fill:#333;color:#333;}#mermaid-svg-t5h2xn8bKJUz4LNn .node rect,#mermaid-svg-t5h2xn8bKJUz4LNn .node circle,#mermaid-svg-t5h2xn8bKJUz4LNn .node ellipse,#mermaid-svg-t5h2xn8bKJUz4LNn .node polygon,#mermaid-svg-t5h2xn8bKJUz4LNn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-t5h2xn8bKJUz4LNn .rough-node .label text,#mermaid-svg-t5h2xn8bKJUz4LNn .node .label text,#mermaid-svg-t5h2xn8bKJUz4LNn .image-shape .label,#mermaid-svg-t5h2xn8bKJUz4LNn .icon-shape .label{text-anchor:middle;}#mermaid-svg-t5h2xn8bKJUz4LNn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-t5h2xn8bKJUz4LNn .rough-node .label,#mermaid-svg-t5h2xn8bKJUz4LNn .node .label,#mermaid-svg-t5h2xn8bKJUz4LNn .image-shape .label,#mermaid-svg-t5h2xn8bKJUz4LNn .icon-shape .label{text-align:center;}#mermaid-svg-t5h2xn8bKJUz4LNn .node.clickable{cursor:pointer;}#mermaid-svg-t5h2xn8bKJUz4LNn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-t5h2xn8bKJUz4LNn .arrowheadPath{fill:#333333;}#mermaid-svg-t5h2xn8bKJUz4LNn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-t5h2xn8bKJUz4LNn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-t5h2xn8bKJUz4LNn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-t5h2xn8bKJUz4LNn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-t5h2xn8bKJUz4LNn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-t5h2xn8bKJUz4LNn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-t5h2xn8bKJUz4LNn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-t5h2xn8bKJUz4LNn .cluster text{fill:#333;}#mermaid-svg-t5h2xn8bKJUz4LNn .cluster span{color:#333;}#mermaid-svg-t5h2xn8bKJUz4LNn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-t5h2xn8bKJUz4LNn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-t5h2xn8bKJUz4LNn rect.text{fill:none;stroke-width:0;}#mermaid-svg-t5h2xn8bKJUz4LNn .icon-shape,#mermaid-svg-t5h2xn8bKJUz4LNn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-t5h2xn8bKJUz4LNn .icon-shape p,#mermaid-svg-t5h2xn8bKJUz4LNn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-t5h2xn8bKJUz4LNn .icon-shape .label rect,#mermaid-svg-t5h2xn8bKJUz4LNn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-t5h2xn8bKJUz4LNn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-t5h2xn8bKJUz4LNn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-t5h2xn8bKJUz4LNn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户反馈偶尔失败
定义失败类型
500?
超时?
业务错误?
网关错误?
前端取消?
traceId
聚合失败样本
关联发布/流量/用户/租户/地区/依赖
🟩 中级答案
先找到具体接口、失败时间、用户信息,然后查日志、数据库、缓存、网络和最近发布记录。
🟦 高级答案
高级排查第一步是定义"失败":
- HTTP 500;
- HTTP 502/504;
- 超时;
- 业务错误;
- 权限错误;
- 前端取消;
- 第三方失败。
然后拿到:
- traceId;
- userId;
- tenantId;
- 请求参数;
- 版本号;
- 时间点;
- 错误堆栈。
再聚合失败样本,看是否集中在某个时间、版本、地区、租户、用户、接口参数或机房。
🟪 资深答案
资深会分两条线:
短期止血:
- 回滚;
- 降级;
- 熔断;
- 限流;
- 扩容;
- 关闭可疑功能开关。
长期治理:
- 补 trace;
- 补关键日志字段;
- 建立 dashboard;
- 增加告警;
- 灰度发布;
- 自动化回滚;
- 复盘机制;
- 补充压测和回归用例。
⚠️ 容易踩坑
"本地无法复现"很正常。
线上偶发问题往往和并发、数据、流量、依赖、发布、网络有关,本地环境天然缺少这些条件。
✅ 复习重点
偶发问题靠样本聚合和链路追踪,不靠单点猜测。
18. 如何设计 Node 服务的日志、监控和告警?
可观测性图
#mermaid-svg-ridGqvWCUpnJWtZW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ridGqvWCUpnJWtZW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ridGqvWCUpnJWtZW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ridGqvWCUpnJWtZW .error-icon{fill:#552222;}#mermaid-svg-ridGqvWCUpnJWtZW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ridGqvWCUpnJWtZW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ridGqvWCUpnJWtZW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ridGqvWCUpnJWtZW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ridGqvWCUpnJWtZW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ridGqvWCUpnJWtZW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ridGqvWCUpnJWtZW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ridGqvWCUpnJWtZW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ridGqvWCUpnJWtZW .marker.cross{stroke:#333333;}#mermaid-svg-ridGqvWCUpnJWtZW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ridGqvWCUpnJWtZW p{margin:0;}#mermaid-svg-ridGqvWCUpnJWtZW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ridGqvWCUpnJWtZW .cluster-label text{fill:#333;}#mermaid-svg-ridGqvWCUpnJWtZW .cluster-label span{color:#333;}#mermaid-svg-ridGqvWCUpnJWtZW .cluster-label span p{background-color:transparent;}#mermaid-svg-ridGqvWCUpnJWtZW .label text,#mermaid-svg-ridGqvWCUpnJWtZW span{fill:#333;color:#333;}#mermaid-svg-ridGqvWCUpnJWtZW .node rect,#mermaid-svg-ridGqvWCUpnJWtZW .node circle,#mermaid-svg-ridGqvWCUpnJWtZW .node ellipse,#mermaid-svg-ridGqvWCUpnJWtZW .node polygon,#mermaid-svg-ridGqvWCUpnJWtZW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ridGqvWCUpnJWtZW .rough-node .label text,#mermaid-svg-ridGqvWCUpnJWtZW .node .label text,#mermaid-svg-ridGqvWCUpnJWtZW .image-shape .label,#mermaid-svg-ridGqvWCUpnJWtZW .icon-shape .label{text-anchor:middle;}#mermaid-svg-ridGqvWCUpnJWtZW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ridGqvWCUpnJWtZW .rough-node .label,#mermaid-svg-ridGqvWCUpnJWtZW .node .label,#mermaid-svg-ridGqvWCUpnJWtZW .image-shape .label,#mermaid-svg-ridGqvWCUpnJWtZW .icon-shape .label{text-align:center;}#mermaid-svg-ridGqvWCUpnJWtZW .node.clickable{cursor:pointer;}#mermaid-svg-ridGqvWCUpnJWtZW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ridGqvWCUpnJWtZW .arrowheadPath{fill:#333333;}#mermaid-svg-ridGqvWCUpnJWtZW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ridGqvWCUpnJWtZW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ridGqvWCUpnJWtZW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ridGqvWCUpnJWtZW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ridGqvWCUpnJWtZW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ridGqvWCUpnJWtZW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ridGqvWCUpnJWtZW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ridGqvWCUpnJWtZW .cluster text{fill:#333;}#mermaid-svg-ridGqvWCUpnJWtZW .cluster span{color:#333;}#mermaid-svg-ridGqvWCUpnJWtZW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ridGqvWCUpnJWtZW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ridGqvWCUpnJWtZW rect.text{fill:none;stroke-width:0;}#mermaid-svg-ridGqvWCUpnJWtZW .icon-shape,#mermaid-svg-ridGqvWCUpnJWtZW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ridGqvWCUpnJWtZW .icon-shape p,#mermaid-svg-ridGqvWCUpnJWtZW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ridGqvWCUpnJWtZW .icon-shape .label rect,#mermaid-svg-ridGqvWCUpnJWtZW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ridGqvWCUpnJWtZW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ridGqvWCUpnJWtZW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ridGqvWCUpnJWtZW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可观测性
Logs 日志
Metrics 指标
Tracing 链路
traceId / userId / path / error
RED / USE / Node 指标
网关 -> 服务 -> DB -> Redis -> 第三方
告警
错误率 / P99 / 资源耗尽 / 业务下跌
🟩 中级答案
日志使用结构化 JSON。监控 RED 和 USE 指标。告警关注错误率、延迟、CPU、内存等。
🟦 高级答案
日志字段应该包括:
- traceId / requestId;
- userId / tenantId;
- method;
- path;
- statusCode;
- latency;
- error stack;
- service;
- env;
- version;
- upstream latency;
- DB duration;
- cache hit/miss。
监控包括:
- RED:Rate、Errors、Duration;
- USE:Utilization、Saturation、Errors;
- Node:event loop lag、heap、GC、active handles;
- DB:连接池、慢 SQL、锁等待;
- Redis:命中率、延迟、内存、连接数;
- MQ:堆积、消费延迟。
🟪 资深答案
资深会设计:
- SLO;
- error budget;
- 告警分级;
- 告警聚合;
- 业务指标监控;
- 核心链路 dashboard;
- 灰度观察指标;
- 复盘模板;
- 噪音告警治理。
业务指标包括:
- 下单成功率;
- 支付成功率;
- 上传成功率;
- 登录成功率;
- 消息消费延迟;
- 关键接口转化率。
⚠️ 容易踩坑
只有日志没有指标,排障会很慢。
只有指标没有 trace,知道出事了但不知道哪一段出事。
只有 trace 没有业务指标,可能技术上正常但业务已经崩了。
✅ 复习重点
日志、指标、链路追踪三者缺一不可。
19. 如何设计一个权限系统?
权限模型图
#mermaid-svg-Mi7uFutEeSvexOYK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Mi7uFutEeSvexOYK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Mi7uFutEeSvexOYK .error-icon{fill:#552222;}#mermaid-svg-Mi7uFutEeSvexOYK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Mi7uFutEeSvexOYK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Mi7uFutEeSvexOYK .marker.cross{stroke:#333333;}#mermaid-svg-Mi7uFutEeSvexOYK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Mi7uFutEeSvexOYK p{margin:0;}#mermaid-svg-Mi7uFutEeSvexOYK .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-Mi7uFutEeSvexOYK .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-Mi7uFutEeSvexOYK .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-Mi7uFutEeSvexOYK .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-Mi7uFutEeSvexOYK .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-Mi7uFutEeSvexOYK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Mi7uFutEeSvexOYK .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-Mi7uFutEeSvexOYK .node rect,#mermaid-svg-Mi7uFutEeSvexOYK .node circle,#mermaid-svg-Mi7uFutEeSvexOYK .node ellipse,#mermaid-svg-Mi7uFutEeSvexOYK .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Mi7uFutEeSvexOYK .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-Mi7uFutEeSvexOYK .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Mi7uFutEeSvexOYK .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Mi7uFutEeSvexOYK .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-Mi7uFutEeSvexOYK .edgeLabel .label text{fill:#333;}#mermaid-svg-Mi7uFutEeSvexOYK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} has
assigned
owns
granted
controls
belongs_to
has
USER
USER_ROLE
ROLE
ROLE_PERMISSION
PERMISSION
RESOURCE
DEPARTMENT
DATA_SCOPE
🟩 中级答案
可以使用 RBAC 模型:
- 用户;
- 角色;
- 权限;
- 资源。
用户绑定角色,角色绑定权限。菜单、按钮、接口都可以作为资源权限。
🟦 高级答案
权限要分两类:
功能权限:
- 菜单;
- 页面;
- 按钮;
- 接口;
- 路由。
数据权限:
- 只能看自己;
- 看本部门;
- 看本部门及下级;
- 看指定部门;
- 看指定项目;
- 看指定租户。
前端控制菜单和按钮只是体验,不是安全边界。后端接口必须做权限校验。
🟪 资深答案
资深会考虑:
- 多租户隔离;
- 权限缓存;
- 权限变更后的缓存失效;
- 超级管理员;
- 审计日志;
- 权限变更记录;
- 临时授权;
- 数据权限 SQL 注入风险;
- ABAC / 策略引擎;
- 最小权限原则。
⚠️ 容易踩坑
只在前端隐藏按钮不叫权限系统。
真正的权限必须在后端校验。
✅ 复习重点
RBAC 解决"谁能做什么",数据权限解决"谁能看哪些数据"。
20. 多个业务线使用同一个 Node 服务,核心流程相同但部分逻辑不同,如何避免一堆 if else?
架构图
#mermaid-svg-dtCnkd143G2YPZbk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dtCnkd143G2YPZbk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dtCnkd143G2YPZbk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dtCnkd143G2YPZbk .error-icon{fill:#552222;}#mermaid-svg-dtCnkd143G2YPZbk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dtCnkd143G2YPZbk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dtCnkd143G2YPZbk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dtCnkd143G2YPZbk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dtCnkd143G2YPZbk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dtCnkd143G2YPZbk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dtCnkd143G2YPZbk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dtCnkd143G2YPZbk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dtCnkd143G2YPZbk .marker.cross{stroke:#333333;}#mermaid-svg-dtCnkd143G2YPZbk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dtCnkd143G2YPZbk p{margin:0;}#mermaid-svg-dtCnkd143G2YPZbk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dtCnkd143G2YPZbk .cluster-label text{fill:#333;}#mermaid-svg-dtCnkd143G2YPZbk .cluster-label span{color:#333;}#mermaid-svg-dtCnkd143G2YPZbk .cluster-label span p{background-color:transparent;}#mermaid-svg-dtCnkd143G2YPZbk .label text,#mermaid-svg-dtCnkd143G2YPZbk span{fill:#333;color:#333;}#mermaid-svg-dtCnkd143G2YPZbk .node rect,#mermaid-svg-dtCnkd143G2YPZbk .node circle,#mermaid-svg-dtCnkd143G2YPZbk .node ellipse,#mermaid-svg-dtCnkd143G2YPZbk .node polygon,#mermaid-svg-dtCnkd143G2YPZbk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dtCnkd143G2YPZbk .rough-node .label text,#mermaid-svg-dtCnkd143G2YPZbk .node .label text,#mermaid-svg-dtCnkd143G2YPZbk .image-shape .label,#mermaid-svg-dtCnkd143G2YPZbk .icon-shape .label{text-anchor:middle;}#mermaid-svg-dtCnkd143G2YPZbk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dtCnkd143G2YPZbk .rough-node .label,#mermaid-svg-dtCnkd143G2YPZbk .node .label,#mermaid-svg-dtCnkd143G2YPZbk .image-shape .label,#mermaid-svg-dtCnkd143G2YPZbk .icon-shape .label{text-align:center;}#mermaid-svg-dtCnkd143G2YPZbk .node.clickable{cursor:pointer;}#mermaid-svg-dtCnkd143G2YPZbk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dtCnkd143G2YPZbk .arrowheadPath{fill:#333333;}#mermaid-svg-dtCnkd143G2YPZbk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dtCnkd143G2YPZbk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dtCnkd143G2YPZbk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dtCnkd143G2YPZbk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dtCnkd143G2YPZbk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dtCnkd143G2YPZbk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dtCnkd143G2YPZbk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dtCnkd143G2YPZbk .cluster text{fill:#333;}#mermaid-svg-dtCnkd143G2YPZbk .cluster span{color:#333;}#mermaid-svg-dtCnkd143G2YPZbk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dtCnkd143G2YPZbk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dtCnkd143G2YPZbk rect.text{fill:none;stroke-width:0;}#mermaid-svg-dtCnkd143G2YPZbk .icon-shape,#mermaid-svg-dtCnkd143G2YPZbk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dtCnkd143G2YPZbk .icon-shape p,#mermaid-svg-dtCnkd143G2YPZbk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dtCnkd143G2YPZbk .icon-shape .label rect,#mermaid-svg-dtCnkd143G2YPZbk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dtCnkd143G2YPZbk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dtCnkd143G2YPZbk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dtCnkd143G2YPZbk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 稳定主流程
validate
beforeCreate
create
afterCreate
notify
业务线 A 策略
业务线 B 策略
业务线 C 策略
🟩 中级答案
可以抽取公共逻辑,把不同业务线的逻辑封装成 hooks、父类、模块或 npm 包,避免重复代码。
🟦 高级答案
高级设计的重点不是"复用代码",而是识别:
- 稳定主流程;
- 变化点;
- 扩展点;
- 边界。
可以使用:
- 策略模式;
- 模板方法;
- pipeline;
- lifecycle hooks;
- 配置驱动。
示例:
js
class OrderFlow {
async submit(ctx) {
await this.validate(ctx);
await this.beforeCreate(ctx);
const order = await this.createOrder(ctx);
await this.afterCreate(ctx, order);
await this.notify(ctx, order);
return order;
}
async validate(ctx) {}
async beforeCreate(ctx) {}
async createOrder(ctx) {}
async afterCreate(ctx, order) {}
async notify(ctx, order) {}
}
不同业务线只实现自己的策略:
js
const strategies = {
retail: retailOrderStrategy,
wholesale: wholesaleOrderStrategy,
overseas: overseasOrderStrategy
};
await strategies[bizType].submit(ctx);
🟪 资深答案
资深会进一步控制扩展能力:
- 哪些 hooks 可以开放;
- 哪些核心流程不能改;
- 插件之间如何隔离;
- 业务线如何测试;
- 灰度如何发布;
- 策略如何版本化;
- 新业务线接入成本;
- 失败后如何回滚;
- 如何避免插件污染核心领域模型。
⚠️ 容易踩坑
"封装父类"不一定就是好架构。
如果父类越来越大,业务线到处 override,最后会变成另一种灾难。
✅ 复习重点
真正的架构抽象不是复用代码,而是把变化限制在可控边界内。
最后一段话
你现在不是缺"更多名词",而是要把已有名词变成工程判断力。
中级程序员知道工具。
高级程序员知道工具的边界。
资深程序员知道系统为什么会失控,以及如何让它不失控。