Node.js 掌握度 20 题自测水平

中级常见状态是:

我知道这个问题大概要用 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_threads
  • cluster
  • 多进程部署
  • 任务队列
  • 把计算任务拆到专门的服务

🟪 资深答案

资深回答会进一步区分:

  • 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 的事件循环和任务队列优先级。

执行顺序是:

  1. 同步代码进入调用栈,先打印 A
  2. setTimeout 回调进入 timers 阶段等待;
  3. Promise .then 进入 microtask 队列;
  4. process.nextTick 进入 nextTick 队列;
  5. 同步代码继续执行,打印 E
  6. 当前调用栈清空后,Node 优先清空 nextTick 队列,打印 D
  7. 再执行 Promise 微任务,打印 C
  8. 最后进入 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. requireimport 有什么区别?

概念图

#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.allPromise.allSettledPromise.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
异步发送通知
失败重试 / 死信 / 补偿

🟩 中级答案

订单、库存、余额需要保证一致性,应该放在事务里。通知不应该放进主事务,可以异步发送。

🟦 高级答案

如果订单、库存、余额在同一个数据库,可以使用本地事务:

  1. 创建订单;
  2. 扣减库存;
  3. 扣减余额;
  4. 写本地消息表;
  5. 提交事务;
  6. 异步发送通知。

如果是跨服务,就不能简单用本地事务。可以使用:

  • 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,最后会变成另一种灾难。

✅ 复习重点

真正的架构抽象不是复用代码,而是把变化限制在可控边界内。


最后一段话

你现在不是缺"更多名词",而是要把已有名词变成工程判断力。

中级程序员知道工具。

高级程序员知道工具的边界。

资深程序员知道系统为什么会失控,以及如何让它不失控。

相关推荐
Java.熵减码农2 小时前
Hermes Agent 安装踩坑记录:DNS 解析失败 & Node.js 幽灵文件冲突
node.js·ai编程·hermes
Xd聊架构2 小时前
为什么 OpenClaw 和 Claude Code 都使用 Node.js
node.js·agent·智能体·claudecode·openclaw
小小龙学IT3 小时前
告别 Node.js?Bun 2.0 深度解析
node.js
Aolith3 小时前
Express + TypeScript 下写 JWT 中间件,我踩了三个坑
typescript·node.js·express
右耳朵猫AI4 小时前
JS/TS周刊2026W22 | Deno 2.8、Node.js v26.2.0、Firefox 151、Storybook 10.4、npm 12.0
javascript·node.js·firefox
右耳朵猫AI4 小时前
前端周刊2026W22 | React 13周年、TanStack Router、Deno 2.8、Node.js 26、npm 分阶段发布
前端·react.js·node.js
Java.熵减码农6 小时前
Windows 下 Node.js 安装与配置完全指南
windows·node.js
凌云拓界14 小时前
文件管理:让AI安全操作你的电脑 ——CogitoAgent开发实战(三)
javascript·人工智能·架构·开源·node.js
凌云拓界15 小时前
联网能力:让AI看见更广阔的世界 ——CogitoAgent开发实战(四)
javascript·人工智能·架构·node.js·创业创新