# 运维常见题-网站维护


## 🤔 简述 HTTP/1.0、1.1、2、3 区别？	
- **`HTTP` 协议经历了四代演进，每一代解决前一代的核心痛点：连接复用、并发能力、传输效率。**
    - **`HTTP/1.0（1996，RFC 1945）`**
        - **特点**：每个请求/响应使用一个独立的 `TCP` 连接，请求完成后连接立即关闭（短连接）
        - **问题**：一次页面加载需要多个资源（`HTML`、`CSS`、`JS`、图片），每个资源都要新建 `TCP` 连接，`TCP` 握手（尤其 `HTTPS` 的 `TLS` 握手）开销巨大
        - **其他**：无 `Host` 头（同一 `IP` 上无法在 `HTTP` 层区分多个域名，即虚拟主机）；缓存控制简陋（仅有 `Expires`/`Pragma`/`Last-Modified`，无 `Cache-Control`/`ETag`）

    - **`HTTP/1.1`（`1997` 首版 `RFC 2068`，`1999` 定稿 `RFC 2616`，现为 `RFC 9110`/`9111`/`9112`）**
        - **持久连接（`Keep-Alive`）** ：默认复用同一个 `TCP` 连接发送多个请求，解决频繁握手问题
        - **`Host` 头**：支持一个 `IP` 上部署多个域名（虚拟主机）
        - **管线化（Pipelining）**：客户端可以连续发送多个请求而不必等前一个响应——但由于响应必须按请求顺序返回（`FIFO`），一个慢响应会阻塞后面的所有响应（队头阻塞），实际很少启用
        - **其他**：`chunked` 传输编码、更完善的缓存控制（`Cache-Control`、`ETag`）
        - **核心问题**：队头阻塞（`Head-of-Line Blocking`） ——同一连接上的请求必须串行处理，一个响应慢，后续全等

    - **`HTTP/2`（`2015`，`RFC 7540`，现为 `9113`）**
        - **二进制分帧**：把 `HTTP` 消息拆成二进制帧（`HEADERS`、`DATA` 等），替代 `HTTP/1.1` 的纯文本格式
        - **多路复用（`Multiplexing`）** ：多个请求/响应可以在同一个 `TCP` 连接上并行交错传输，解决了应用层的队头阻塞
        - **头部压缩（`HPACK`）** ：压缩首部（请求和响应两侧），减少重复头部传输
        - **服务器推送（`Server Push`）** ：规范层面仍保留（`RFC 9113 §8.4`），但 `Chrome 106（2022-09）`、`Firefox（2022）`已移除实现、`Safari` 从未实现，实际未普及；`HTTP/3` 无此功能，业界替代是 `103 Early Hints`（`RFC 8297`）
        - **流优先级**：`RFC 7540` 的依赖-权重方案已被 `RFC 9218`（`Extensible Prioritization`，`2022`）取代（原方案"并不成功"）
        - **局限**：虽然解决了应用层队头阻塞，但 `TCP` 层队头阻塞仍然存在——`TCP` 保证数据有序，一个 `TCP` 包丢失会导致后续所有包等待重传，即使它们属于不同的请求流

    - **`HTTP/3（2022，RFC 9114）`**
        - 传输层改用 `QUIC` 协议（`RFC 9000`，承载于 `UDP` 之上）替代 `TCP`
        - 解决 `TCP` 层队头阻塞：`QUIC` 在用户空间实现可靠传输和有序交付，但丢失的包只影响它所在的流，其他流不受影响
        - `0-RTT` 连接恢复：基于 `TLS 1.3` 会话恢复票据（`PSK`），第二次连接可免握手直接发送数据（注意 `0-RTT` 有重放风险，`RFC 9001`）
        - 连接迁移：`QUIC` 用连接 `ID` 标识连接，`IP` 地址变化（如 `Wi-Fi` 切到移动网络）连接不中断
        - 内建 `TLS 1.3`：加密是 `QUIC` 的强制部分，没有明文 `HTTP/3`
        - 头部压缩用 `QPACK`（`RFC 9204`）而非 `HPACK`（应对乱序到达的首部）
        - 部署现状：`W3Techs 2026-08` 数据显示约 `40%` 的网站已支持 `HTTP/3`

    - **四代对比速览**
        - **`1.0`**：短连接，一请求一连接
        - **`1.1`**：持久连接，但有队头阻塞
        - **`2`**：多路复用（解决应用层队头阻塞），基于 `TCP`
        - **`3`**：多路复用 + 解决传输层队头阻塞，基于 `UDP/QUIC`

- **协助记忆**
    - 一代一痛点：`1.0` 连接费（短连接）→ `1.1` 连接省（持久连接）但排队（队头阻塞）→ `2` 并行（多路复用）但堵在 `TCP` → `3` 换路（`QUIC/UDP`）彻底不堵
    - 队头阻塞有两个层面：应用层（`HTTP/1.1` 的 `FIFO`）和传输层（`TCP` 包丢失重传） ——— `HTTP/2` 解决前者，`HTTP/3` 解决后者

- **进阶思考**
    - **为什么 `HTTP/2` 多路复用后还要升级到 `HTTP/3`？**
        - `HTTP/2` 解决的是应用层队头阻塞——多个请求可以在一个 `TCP` 连接上并行，不需要按序等待。但 `TCP` 本身有传输层队头阻塞：`TCP` 保证数据按序交付，如果某一个 `TCP` 段丢失，接收方必须等待该段重传才能继续处理后续数据，即使这些数据属于完全不同的请求流。在丢包率高的弱网环境（移动网络）这个问题尤其严重。`HTTP/3` 用 `QUIC`（`UDP`）解决了这个问题——每个流独立处理，一个流的丢包不影响其他流

    - **`HTTP/3` 用了 `UDP`，可靠性和顺序性怎么保证？**
        - `QUIC` 在 `UDP` 之上自己实现了可靠传输和有序交付——它有类似 `TCP` 的序号、确认、重传机制，但作用域是"流"而不是整个连接。每个流独立编号、独立确认、独立重传，所以一个流的丢包只重传该流的数据，不影响其他流。这也是 `QUIC` 解决队头阻塞的本质：把 `TCP` 的"全局有序"变成 `QUIC` 的"流内有序"

## 🤔 HTTP 有哪些常见的状态码？
- **[`HTTP` 状态码](/posts/linux/http响应码/)是服务器在响应中返回的三位数字，用于告知客户端请求的处理结果。按首位数字分为五类：`1xx`（信息）、`2xx`（成功）、`3xx`（重定向）、`4xx`（客户端错误）、`5xx`（服务器错误）。**

    - **`1xx`**：信息性响应
        - **`100 Continue`**：客户端可以继续发送请求体
        - **`101 Switching Protocols`**：协议切换（如升级到 `WebSocket`）
        - **`103 Early Hints`**：提前发送提示（如预加载资源链接），配合 `301/308` 重定向场景做性能优化
        - 日常运维很少直接见到 `1xx`，多由协议层内部处理

    - **`2xx`**：成功
        - **`200 OK`**：请求成功，最常见
        - **`201 Created`**：资源创建成功（常用于 `POST` 创建资源）
        - **`202 Accepted`**：请求已接受但尚未处理完成（异步任务）
        - **`204 No Content`**：请求成功但无内容返回（常用于 `DELETE`、以及 `POST/PUT` 的"成功无内容"场景）

    - **`3xx`**：重定向
        - **`301 Moved Permanently`**：永久重定向，后续请求应使用新 `URL`（默认允许缓存，受 `Cache-Control` 控制）
        - **`302 Found`**：临时重定向，后续请求仍用原 `URL`
        - **`303 See Other`**：方法强制改为 `GET`（`SHOULD use GET`，`RFC 9110 §15.4.4`）——常用于表单提交后重定向到结果页
        - **`304 Not Modified`**：资源未修改，可使用缓存（配合 `If-Modified-Since` / `ETag`）
        - **`307 Temporary Redirect`**：临时重定向，且保留请求方法和请求体
        - **`308 Permanent Redirect`**：永久重定向，且保留请求方法和请求体
        - **方法语义完整图谱**：`301/302`（`MAY` 改方法）→ `303`（`SHOULD` 用 `GET`）→ `307/308`（必须保留方法）

    - **`4xx`**：客户端错误
        - **`400 Bad Request`**：请求语法错误，服务器无法或不愿处理
        - **`401 Unauthorized`**：未认证（未登录或凭据无效）。注意：`401` 是"没证明你是谁"，`403` 是"不让进"
        - **`403 Forbidden`**：服务器理解请求但拒绝处理/授权——常因权限不足，但也可能是未认证、`IP` 封禁、`WAF` 拦截；未认证时服务器也可能返回 `403` 而非 `401`（如不想暴露资源存在性）
        - **`404 Not Found`**：资源不存在，最常见
        - **`405 Method Not Allowed`**：请求方法不被允许（如对只读接口发 `POST`）
        - **`408 Request Timeout`**：请求超时
        - **`409 Conflict`**：请求与当前资源状态冲突（如版本冲突）
        - **`410 Gone`**：资源已永久删除（区别于 `404` 的"不存在"，`410` 是"曾经存在但没了"）
        - **`413 Content Too Large`**：请求体过大（`RFC 9110` 新名，旧名 `Payload Too Large` 仍广泛使用）
        - **`429 Too Many Requests`**：请求过多，触发限流（`RFC 6585` 定义，常配合 `Retry-After` 头）

    - **`5xx`**：服务器错误
        - **`500 Internal Server Error`**：服务器内部错误，通用错误码
        - **`501 Not Implemented`**：服务器不支持请求的方法
        - **`502 Bad Gateway`**：网关/代理收到上游服务器的无效响应（`Nginx` 作为反向代理时常见）
        - **`503 Service Unavailable`**：服务暂时不可用（过载、维护中），常配合 `Retry-After` 头，通常过一段时间会恢复
        - **`504 Gateway Timeout`**：网关/代理等待上游响应超时

- **协助记忆**
    - **五位分类**：`1` 信、`2` 成、`3` 转、`4` 客户错、`5` 服务器错
    - **易混三对**：
        - `301` vs `308`：都是永久，但 `301` 方法可能变（POST→GET），`308` 保留方法
        - `302` vs `307`：都是临时，`302` 方法可能变，`307` 保留方法
        - `401` vs `403``：401` 是"你是谁"，`403` 是"不让进"（注：`403` 不要求已认证，是"拒绝授权"）

    - `502` vs `503` vs `504`：`502` 是上游答了但答错（无效响应）、`503` 是自己没空（过载）、`504` 是上游没答（超时）

- **进阶思考**
    - **`301` 和 `308` 到底有什么区别？什么时候用哪个？**
        - 核心区别是请求方法是否保留。`301`（和 `302`）在重定向时，很多浏览器和客户端会把 `POST` 请求改为 `GET` 请求（`RFC` 使用 `MAY`，历史行为普遍存在）；`303` 明确要求改用 `GET`；`308`（和 `307`）明确要求保留原方法和请求体。实践中：永久迁移且希望客户端用新 `URL` 重新发起 `GET` 请求，用 `301`；`API` 场景希望 `POST/PUT` 的请求体和方法原样转发到新地址，用 `308`。现代 `Web` 的 `API` 重定向推荐 `308/307` 以保证语义一致

    - **反向代理场景，`502`、`503`、`504` 分别对应什么排查方向？**
        - **`502（Bad Gateway）`**：上游服务器响应了但响应无效——如上游进程崩溃返回空响应、返回了非法响应格式。排查上游应用是否挂了、端口是否监听。
        - **`503（Service Unavailable）`**：本机（代理）或上游过载/维护——如后端连接数打满、限流触发。排查后端负载、连接池。
        - **`504（Gateway Timeout）`**：上游没有及时响应——如后端处理超时、慢查询。排查后端应用延迟、数据库慢查询。
        - **一句话**：`502` 是上游答了但答错，`503` 是没空， `504` 是没答
    


## 🤔 浏览器访问域名经历了哪些流程？	
- **从在地址栏输入域名到页面显示，浏览器经历了一条完整的链路：`URL` 解析 → `DNS` 解析 → `TCP` 连接 → `TLS` 握手（`HTTPS`）→ `HTTP` 请求 → 服务器处理 → `HTTP` 响应 → 浏览器渲染。每一步都有对应的可排查点。**

    - **第一步**：`URL` 解析
        - 浏览器解析输入的 `URL`，拆分成协议（`https`）、域名（`www.example.com`）、端口（默认 `443`）、路径（`/index.html`）、查询参数等
        - 判断协议是否合法、域名格式是否正确

    - **第二步**：`DNS` 解析（把域名变成 `IP`）
        - 按缓存层级逐级查找：浏览器缓存 → 系统解析器（`hosts` 文件、系统缓存、`DNS` 服务器的相对顺序由操作系统决定 ——— `Windows` 通常是 `hosts` → `DNS Client` 缓存 → `DNS` 服务器，`Linux` 按 `nsswitch.conf` 默认 `files dns` 通常 `hosts` 优先）
        - 客户端向递归 `DNS` 服务器发起递归查询；递归解析器对根 `DNS` → 顶级域 `DNS`（`.com`）→ 权威 `DNS`（`example.com` 的 `NS`）的逐级查询称为迭代查询（浏览器本身不逐级问根服务器）
        - 细节：现代浏览器和系统可能启用 `DNS-over-HTTPS`（`DoH`，`RFC 8484`），`DNS` 查询走 `HTTPS` 加密通道

    - **第三步**：`TCP` 连接（三次握手）
        - 拿到 `IP` 后，浏览器与服务器建立 `TCP` 连接：`SYN` → `SYN-ACK` → `ACK`（`RFC 9293`）
        - 涉及 `TCP` 相关优化：`Fast Open`（`TFO`）、连接复用（`keep-alive`）；若启用 `HTTP/3`，`TCP+TLS` 合并为 `QUIC` 一次握手

    - **第四步**：`TLS` 握手（`HTTPS` 专属）
        - **`HTTPS` 下进行 `TLS` 握手（以 `TLS 1.3` 为主，现行规范 `RFC 9846`）：**
            - 客户端发送 `ClientHello`（支持的加密套件、`TLS` 版本、`key_share` 密钥共享）
            - 服务器返回 `ServerHello`（含密钥共享）、证书；证书在加密消息中发送（`EncryptedExtensions` → `Certificate` → `CertificateVerify` → `Finished`）
            - 客户端验证证书（信任链、域名匹配、有效期）
            - 双方确认，握手完成，开始加密通信

        - 注：`TLS 1.2` 流程是"独立的密钥交换回合（`ECDHE` 等）"，`TLS 1.3` 把密钥共享直接放进 `ClientHello`/`ServerHello`，更快更简洁
        - `TLS 1.3` 首次握手 `1-RTT`、会话恢复 `0-RTT`（注意 `0-RTT` 有重放攻击风险，规范要求默认禁用，浏览器默认不开 `early data`）

    - **第五步**：发送 `HTTP` 请求
        - 浏览器构造 `HTTP` 请求（方法、`URL`、头部、`cookie`），通过已建立的加密通道发送
        - 经过 `CDN` 时，请求先到最近的 `CDN` 节点，`CDN` 命中缓存则直接返回，未命中则回源

    - **第六步**：服务器处理
        - 请求到达服务器（或反向代理 `Nginx`），经过：负载均衡 → `Web` 服务器 → 应用代码 → 数据库/缓存查询
        - 服务器生成响应（状态码、响应头、响应体）

    - **第七步**：`HTTP` 响应返回
        - **服务器返回响应**：状态码（`200`/`404`/`500` 等）、响应头（`Content-Type`、`Cache-Control`、`Set-Cookie` 等）、响应体（`HTML`）
        - **浏览器判断状态码**：`2xx` 正常渲染，`3xx` 跟随重定向（重新走流程），`4xx/5xx` 显示错误页

    - **第八步**：浏览器渲染
        - 解析 `HTML` 构建 `DOM` 树
        - 解析 `CSS` 构建 `CSSOM`
        - `DOM` + `CSSOM` 合并生成渲染树（`Render Tree`） （注：`Render Tree` 是 `WebKit/Blink` 的实现术语，非统一 `Web` 标准定义，`Gecko` 叫 `Frame tree`）
        - **布局（Layout/Reflow）** ：计算每个元素的几何位置
        - **绘制（Paint）** ：绘制到屏幕
        - **合成（Composite）** ：把各层合并呈现 ——— `transform/opacity` 动画只触发合成，不触发重排重绘
        - **脚本与样式对渲染的影响**：`<script>` 是 `parser-blocking`（阻塞 `DOM` 解析进而阻塞渲染，除非 `async/defer`；`<script type="module">` 默认 `defer`），但现代浏览器有预加载扫描器提前并行下载脚本（下载并行、执行阻塞）；`CSS` 是 `render-blocking` 但不阻塞 `DOM` 构建
        - `JavaScript` 执行修改 `DOM/CSSOM` 会触发重排（`Reflow`）和重绘（`Repaint`）

- **协助记忆**
    - **八步链路**：拆 `URL` → 查 `DNS` → 连 `TCP` → 握 `TLS` → 发请求 → 服务器处理 → 收响应 → 渲染页面
    - **"三握一握"**：`TCP` 三次握手 + `TLS` 一次握手（`HTTPS` 专属）
    - **渲染五步**：`DOM`（结构）→ `CSSOM`（样式）→ `Render Tree`（合并）→ `Layout` + `Paint`（画出来）→ `Composite`（合成呈现）
    - **排查口诀**：访问慢，先看 `DNS` 快不快，再看 `TCP/TLS` 握多久，再看服务器响应时间，最后看渲染卡不卡

- **进阶思考**
    - **为什么 `HTTPS` 站点首次访问比 `HTTP` 慢？慢在哪？**
        - `HTTP` 只需 `TCP` 三次握手（`1` 个 `RTT`）；`HTTPS` 除了 `TCP` 还要 `TLS` 握手 ——— `TLS 1.2` 需要 `2` 个 `RTT`、`TLS 1.3` 需要 `1` 个 `RTT`（首次），加上 `TCP` 总共 `2~3` 个 `RTT` 才能发出第一个请求。`RTT` 越大（物理距离越远），差距越明显。优化手段：`TLS 1.3` 会话恢复（`0-RTT`，需权衡重放风险）、`HTTP/3`（`QUIC` 把 `TCP+TLS` 合并为一次握手）、连接复用与 `TFO`。在 `HTTP/3` 或连接复用场景下 `HTTPS` 与 `HTTP` 差距已不明显，但全新连接下 `HTTPS` 仍多约 `1` 个 `RTT`

    - **输入域名后浏览器直接显示"无法访问此网站"，最可能是哪一步出了问题？**
        - 按经验上最常见的顺序：一是 `DNS` 解析失败（域名拼错、`DNS` 服务器故障、本地 `hosts` 被改）——浏览器提示"找不到服务器 `IP` 地址"；二是 `TCP` 连接失败（服务器宕机、端口不通、防火墙拦截）——— 提示"连接超时"或"连接被拒绝"；三是 `TLS` 握手失败（证书过期、不受信任）——提示"您的连接不是私密连接"；四是服务器返回 `5xx` 错误（有响应但处理失败）。浏览器通常给出具体错误提示，根据提示即可定位到是哪一步


## 🤔 HTTP 请求头和响应头有哪些内容？
- **`HTTP` 头部（`Header`）是请求/响应中"键值对"形式的元数据，用于传递请求上下文、内容描述、缓存策略、认证信息等。现代分类（`RFC 9110`）不再使用传统的"通用头/实体头"分类，头部按用途分为请求头、响应头、表示头、内容头等；本文按常见场景组织，仍会说明新旧分类对应关系。**
    - **请求行 / 状态行结构（头部之前）**
        - **请求行**：`GET /index.html HTTP/1.1`（方法 + 路径 + 版本）
        - **状态行**：`HTTP/1.1 200 OK`（版本 + 状态码 + 原因短语）

    - **请求头（客户端 → 服务器）**
        - **`Host`**：目标主机和端口（`HTTP/1.1` 必需，虚拟主机依据）
        - **`User-Agent`**：客户端标识（浏览器/版本/操作系统）
        - **`Accept`**：客户端可接受的内容类型（如 `text/html`、`application/json`）
        - **`Accept-Encoding`**：客户端支持的压缩算法（`gzip`、`br`、`zstd` —— `2024-2025` 主流浏览器已支持）
        - **`Accept-Language`**：客户端语言偏好
        - **`Content-Type / Content-Length`**：请求体类型和长度（`POST`/`PUT` 时；注意这属于"表示头/内容头"，同一字段在请求和响应中含义一致）
        - **`Authorization`**：认证凭据（`Bearer token`、`Basic`）
        - **`Cookie`**：客户端携带的 `cookie`（`RFC 6265` 定义，注意不是 `RFC 9110`）
        - **`Referer`**：来源页面 `URL`（注意拼写是 `Referer` 而非 `Referrer`）
        - **`Origin`**：跨域请求的来源（协议+域名+端口，`CORS` 用）
        - **`X-Forwarded-For`**：经过代理时的原始客户端 `IP`（非标准头，事实标准；标准替代为 `Forwarded`，`RFC 7239`）

    - **响应头（服务器 → 客户端）**
        - **`Content-Type / Content-Length`**：响应体类型和长度
        - **`Set-Cookie`**：服务器设置 cookie（RFC 6265 定义，含 Secure、HttpOnly、SameSite 等属性）
        - **`Location`**：重定向目标地址（3xx 响应；也用于 201 Created 返回新资源地址）
        - **`Cache-Control`**：响应缓存策略（RFC 9111 定义）
        - **`ETag`**：资源版本标识（配合 If-None-Match 缓存验证；也配合 If-Match 做乐观并发控制）
        - **`Last-Modified`**：资源最后修改时间（配合 If-Modified-Since）
        - **`Expires`**：缓存过期时间（HTTP/1.0 遗留，被 Cache-Control 取代）
        - **`Server`**：服务器软件信息
        - **`Date`**：响应时间
        - **`Access-Control-Allow-Origin`**：CORS 允许的跨域来源
        - **`Retry-After`**：多久后重试（配合 429/503）
        - **`Strict-Transport-Security`**：强制 HTTPS（HSTS，RFC 6797）

    - **传统分类与新分类的对应（旧文档常见，帮助理解）**
        - 传统四分类：通用头、请求头、响应头、实体头
        - `RFC 9110` 已同时废弃"通用头"和"实体头"概念，重构为：请求头、响应头、表示头（Representation，描述资源表示，如 Content-Type、ETag、Last-Modified）、内容头（Content，描述消息内容，如 Content-Length）、验证器字段（Validator，ETag/Last-Modified 单独归为验证器）
        - 旧教材把 ETag 归入实体头是简化说法，严格按 RFC 2616 ETag 是响应头

    - **常见安全响应头（现代 Web 标配）**
        - **`CSP（Content-Security-Policy）`** ：内容安全策略，限制资源加载来源
        - **`X-Content-Type-Options: nosniff`**：禁止 MIME 嗅探
        - **`X-Frame-Options / frame-ancestors`**：点击劫持防护
        - **`Referrer-Policy`**：控制 Referer 发送策略
        - **`Permissions-Policy`**：限制浏览器功能权限
        - **`COOP/COEP/CORP`**：跨源隔离相关头

- **协助记忆**
    - **请求头记一组**：`Host`、`UA`、`Accept` 家族、`Cookie`、`Authorization`——"你是谁、要什么、带什么"
    - **响应头记一组**：`Content-Type`、`Set-Cookie`、`Location`、`Cache-Control`、`ETag` ——— "给你什么、让你存什么、跳去哪"
    - **排查缓存问题看这四个**：`Cache-Control`、`ETag`、`Last-Modified`、`Expires`
    - **区分两个"内容"**：`Content-Type`（媒体类型）≠ `Content-Length`（字节长度）

- **进阶思考**
    - **`Cookie` 和 `Authorization` 都能传递身份信息，有什么区别？**
        - 机制不同。`Authorization` 是请求头，每次请求由客户端代码显式携带（如 `Bearer token`），无状态、常用于 `API`。`Cookie` 是浏览器自动管理的状态机制——服务器 `Set-Cookie` 后，浏览器自动在后续请求中带上对应域名的 `cookie`，有状态、常用于 `Web` 会话。`Cookie` 会自动附加到同域请求（不受代码控制），`Authorization` 需要代码显式添加。安全上：`Authorization` 适合 `API token`，`Cookie` 需配合 `Secure`/`HttpOnly`/`SameSite` 防窃取

    - **`Nginx` 反代场景，如何保证后端能看到真实客户端 `IP`？**
        - `Nginx` 在转发请求时把客户端真实 `IP` 写入 `X-Forwarded-For` 或 `X-Real-IP` 头，后端应用从这些头读取。但 `X-Forwarded-For` 是可伪造的——客户端可以直接构造这个头。安全做法：在 `Nginx` 层用 `set_real_ip_from` + `real_ip_header` 处理，或在信任的代理边界重写 `X-Forwarded-For`（不要直接信任客户端传入的值）。这是反代场景的常见安全坑


## 🤔 网站显示中文乱码会是什么原因？	
- **中文乱码的根本原因是编码不一致——网页的字节是按 `A` 编码写入的，但浏览器用 `B` 编码解读，导致字节序列被错误解码。排查方向就是找出"写入编码"和"读取编码"哪里对不上。**

- **编码机制速览（先理解再排查）**
    - **UTF-8**：现代标准，兼容 `ASCII`，中文字符占 `3` 字节，全栈默认。`2025-2026` 年 `UTF-8` 已占全网超 `98%`，且 `Encoding` 标准已把 `UTF-8` 定为新协议的强制编码
    - **`GBK / GB2312`**：中文传统编码，中文字符占 `2` 字节，旧系统常见（注意 `GBK` 是 `GB2312` 的超集，现行国标是 `GB18030`）
    - **乱码的本质**：同一串字节，用不同编码解读得到不同字符

- **常见原因一**：`HTML` 文件内部声明与实际编码不符
    - `HTML` 文件实际以 `UTF-8` 保存，但 `<meta charset="GBK">` 声明为 `GBK`（或反过来）——浏览器按声明解码，乱码
    - 注意 `file` 命令的局限：`file` 能可靠识别 `UTF-8`/`UTF-16`（靠 `BOM` 和字节特征），但对 `GBK` 等东亚双字节编码通常只报含糊的 "`ISO-8859 text`" 或 "`data`"。排查 `GBK` 更可靠的方式：用 `iconv -f GBK -t UTF-8` 文件 和 `iconv -f UTF-8 -t GBK` 文件 双向试转，看哪个方向能转出正常文字；或用 `chardet` / `uchardet` 检测
    - **修复**：统一文件保存编码和 `meta` 声明

- **常见原因二**：`HTTP` 响应头 `charset` 与 `meta` 声明冲突
    - `HTTP` 响应头 `Content-Type: text/html; charset=UTF-8` 与 `HTML` 内 `meta` 声明不一致时，`HTTP` 头的优先级更高
    - **排查**：`curl -I <url>` 查看响应头 `charset`；`curl -s <url> | head -20` 查看 `meta` 声明
    - **修复**：让两者一致（改 `Nginx`/应用配置或改 `meta`）
    - **补充**：还有更高优先级的 `BOM` ——— 文件开头的 `UTF-8 BOM` 优先级高于包括 `HTTP` 头在内的一切声明。这是"改了 `meta` 还是乱码"的另一个隐蔽原因
    - `meta` 声明必须位于文件前 `1024` 字节内才生效

- **常见原因三**：数据库存储编码与连接编码不一致
    - 数据库表是 `UTF-8`，但应用连接数据库时用了错误的连接字符集（如 `latin1`），导致"存进去是乱码"或"读出来是乱码"
    - **机制（`MySQL`）**：服务器按 `character_set_client` 解释客户端发来的字节，再转 `character_set_connection`，最后转目标列字符集；读出的结果由 `character_set_results` 决定
    - **排查**：`SHOW VARIABLES LIKE 'character_set%'` 查看各字符集配置
    - **修复**：连接时 `SET NAMES utf8mb4`（同时设置 `client`/`connection`/`results` 三个变量）；建库时 `CREATE DATABASE ... DEFAULT CHARACTER SET utf8mb4`
    - 注：`MySQL 8.0+` 默认即 `utf8mb4`，旧的 `utf8`（`utf8mb3`）已废弃、未来大版本移除

- **常见原因四**：编码被二次转换
    - 数据经过多个环节时，某个环节做了一次错误的编码转换
    - 是否可逆取决于原始字节是否被改写：纯显示层乱码（字节仍是原始 `UTF-8`，只是被按 `GBK` 显示）可逆 ——— 著名例证是"锟斤拷" （`UTF-8` 替换符被按 `GBK` 显示）；只有经真实转码（如 `iconv` 转错后落库）才不可逆

- **常见原因五**：页面完全没有编码声明
    - `HTTP` 头无 `charset`、`HTML` 内也无 `meta` 声明时，浏览器按默认（现代浏览器默认 `UTF-8`）尝试解码，非 `UTF-8` 字节就会乱码
    - 这比"用户改了浏览器设置"常见得多——排查时先确认页面是否有编码声明，而不是去查浏览器设置

- **协助记忆**
    - 一句话：乱码 = 写的时候用 `A` 编码，读的时候用 `B` 编码，对不上
    - 读取侧优先级（从高到低） ：`HTTP` 头 `charset` → `BOM` → `meta` 声明 → 默认 `UTF-8`
    - 写入侧检查：文件实际编码（iconv 双向试转）、数据库/连接字符集
    - 排查命令：`curl -I` 看响应头、`iconv -f X -t Y` 文件 试转换、`chardet` 检测
    - `utf8mb4` 记住：`MySQL` 存 `emoji` 必须用 `utf8mb4` 而非 `utf8`

- **进阶思考**
    - **`HTTP` 响应头 `charset` 和 `HTML meta charset` 哪个生效？**
        - `HTTP` 响应头的 `Content-Type: text/html; charset=xxx` 优先级更高——浏览器先看 `HTTP` 头，如果 `HTTP` 头没有明确 `charset`，才看 `HTML` 内的 `meta` 声明。但注意 `BOM` 优先级最高：文件开头的 `UTF-8 BOM` 会覆盖包括 `HTTP` 头在内的一切声明。所以"改了 `meta` 还是乱码"要查两个地方：`HTTP` 头里的旧 `charset`、文件是否带 `BOM`

    - **数据库已经是 `UTF-8`，为什么页面还乱码？**
        - 存储编码和连接编码是两回事。表字符集是 `UTF-8` 只保证数据以 `UTF-8` 存，但写入时服务器按 `character_set_client`（连接时声明的客户端字符集）解释应用发来的字节——如果连接用的还是 `latin1` 或 `GBK`，写入时就错了。`MySQL` 排查：`character_set_server`（服务器默认）、`character_set_database`（当前库）、`character_set_client`/`connection`/`results`（连接链路）。统一 `SET NAMES utf8mb4` + 建库指定 `utf8mb4` 通常能解决

## 🤔 网站访问很慢，该如何排查？
- **网站访问慢的排查要遵循"分层定位"思路：把一次访问拆成多个阶段，先定位慢在哪一层（DNS？网络？服务器？数据库？），再针对该层深入。不要一上来就盯着服务器 CPU——很多"网站慢"其实是网络或前端问题。**
    - **第一步**：先量化"慢"在哪——用 `curl` 计时
        - `curl -o /dev/null -s -w 'DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB:%{time_starttransfer}s\nTotal: %{time_total}s\n' https://example.com`
        - **重要**：这些变量是"从 `curl` 启动到该阶段完成的累计时间"，不是各阶段本身耗时。要算阶段耗时需做差：
            - `DNS` 解析耗时 = `time_namelookup`
            - `TCP` 握手耗时 = `time_connect` − `time_namelookup`
            - `TLS` 握手耗时 = `time_appconnect` − `time_connect`
            - 服务器处理时间 = `time_starttransfer` − `time_appconnect`（近似）

        - **`TTFB（time_starttransfer）`** ：从 `curl` 启动到收到第一个字节的累计时间，包含 `DNS` 查询 + `TCP` 握手 + `TLS` 握手 + 服务器处理（`MDN` 定义）
        - **判断方法**：`DNS` 差值长 → `DNS` 问题；`TCP` 差值长 → 网络问题；`DNS`/`TCP`/`TLS` 各阶段差值都正常但 `TTFB` 仍长 → 服务器处理慢；`Total` 长但 `TTFB` 正常 → 响应体传输慢（带宽/大文件）

    - **第二步**：按阶段定位
        - **`DNS` 慢**：`dig + time = 2 example.com` 看解析耗时，`dig @8.8.8.8 example.com` 对比公共 `DNS`；检查本地 `DNS` 服务器、递归解析器、上游权威、`DNSSEC`；考虑 `DoH/DoT`。注意：智能 `DNS/CDN` 解决的是"解析到哪个节点"的调度准确性，不直接加速解析耗时本身
        - **网络慢**：`ping` 看 `RTT`，`mtr` 看中间链路是否有丢包或高延迟节点；跨地域访问考虑 `CDN`。`2025` 新坑：`HTTP/3` 走 `UDP`，部分网络对 `UDP` 做 `QoS` 限速/防火墙拦截——如果 `time_connect` 长但 `ping` 正常，可怀疑 `UDP` 被限速
        - **服务器响应慢（`TTFB` 高）** ：进入服务器端排查（第三步）
        - **传输慢（`Total` − `TTFB` 长）** ：响应体太大、带宽不足、压缩未开（`gzip`/`brotli`）、大图片未优化

    - **第三步**：服务器端深入
        - **Nginx 层**：
            - 先确认日志格式包含耗时字段：默认 `combined` 格式没有 `$request_time`，需要在 `log_format` 中显式配置，如 `log_format main '$remote_addr $request_time $upstream_response_time';，然后 awk '{print $2}' access.log | sort -n | tail  -20` 看最慢的请求（注意取的是配置里的耗时字段列）
            - 检查反向代理超时配置（`proxy_read_timeout` 等）、`upstream` 是否有慢节点

        - **应用层**：
            - 看应用日志的请求处理时间，定位是哪个接口慢
            - 检查应用是否有死锁、线程池打满、`GC` 频繁（`Java`）、`GIL` 阻塞（`Python`）

        - **数据库层**：
            - 开慢查询日志，看是否有全表扫描、缺索引的 `SQL`
            - `SHOW PROCESSLIST` 看是否有长时间运行的查询
            - 检查数据库连接池是否打满（连接等待导致 `TTFB` 升高）

        - **缓存层**：
            - Redis/缓存是否生效（缓存命中率低会导致每次都打数据库）
            - 缓存穿透/击穿/雪崩

        - **资源层**：
            - `top/free` 看 `CPU`、内存是否打满
            - `iostat` 看磁盘 `IO` 是否瓶颈
            - 连接堆积用 `ss -s`（汇总统计）或 `ss -ant state established | wc -l` 看连接数（注意 `ss -lntp` 只显示监听中的 `socket`，看不到 `ESTABLISHED` 堆积；`-lntp` 中 `Send-Q` 列可看 `backlog` 溢出）

    - **第四步**：前端/浏览器侧（如果服务器很快但用户觉得慢）
        - **`Chrome DevTools` 的 `Network` 面板**：看每个资源的加载时间，`Waterfall`（瀑布图）能看出哪些资源是瓶颈
        - **`Core Web Vitals`（`2025` 年核心指标）** ：`LCP`（最大内容绘制）、`INP`（交互延迟，`2024` 年取代 `FID`）、`CLS`（布局偏移）——用 `Lighthouse` 或 `RUM`（`CrUX` 字段数据）审计
        - **常见前端问题**：页面引用了太多大资源、图片未优化、未用 `CDN`、缓存策略未配置
        - **注意**：`HTTP/2` 时代"`JS/CSS` 合并文件"已不必要（多路复用下合并反而有害），现代实践是 `bundling` + `code splitting` + `tree-shaking` + 按需加载
        - **补充**：`103 Early Hints` 可提前 `TTFB`、`Server-Timing` 响应头可暴露服务端耗时

- **协助记忆**
    - **分层定位口诀**：先 `curl` 计时做差分层，再按层深挖 ——— `DNS` 慢查解析，`TCP` 慢查网络，`TTFB` 慢查服务器，`Total` 慢查传输
    - **`curl` 变量是累计值，算阶段要"做差"**：`TCP` = `connect` − `namelookup`，`TLS` = `appconnect` − `connect`，服务器 = `starttransfer` − `appconnect`
    - 排查顺序：网络层 → 服务器层 → 数据库层 → 缓存层 → 前端层，别跳过

- **进阶思考**
    - **`TTFB` 高但服务器 `CPU`/内存都正常，可能是什么原因？**
        - `TTFB` 包含网络 `RTT` + 服务器处理。如果 `CPU`/内存正常，可能的隐藏原因：一是数据库慢查询——请求卡在等数据库返回，但数据库 `CPU` 不高（如缺索引全表扫描、锁等待）；二是外部依赖慢——应用调第三方 `API`，等外部响应；三是连接池/线程池打满——请求排队等待可用连接；四是磁盘 `IO`——日志或缓存写盘慢。排查：看应用日志确认请求在等什么，必要时用 `APM`（链路追踪）定位耗时分布

    - **用户分布广（全国/全球），如何区分是"网站慢"还是"用户网络慢"？**
        - 用多点拨测——在不同地域的机器上执行同样的 `curl` 计时（云厂商都有多地拨测工具）。如果所有地域 `TTFB` 都高 → 服务器端问题；如果只有某些地域高 → 网络/`CDN` 节点问题（该地域到服务器链路差，或 `CDN` 在该地域节点少）。这是"网站慢"与"网络慢"的经典区分方法


## 🤔 QPS、TPS、PV、UV 是什么？怎么统计？	
- **`QPS`/`TPS` 是实时性能指标（衡量系统处理能力），`PV`/`UV` 是业务流量指标（衡量用户访问量）。两者维度不同，但常放在一起讨论。**

- **`QPS`（`Queries Per Second`，每秒查询数）**
    - **含义**：系统每秒处理的请求/查询数量，衡量系统吞吐能力
    - **注意**：`QPS` 在不同语境含义不同 ——— `Web` 场景指每秒 `HTTP` 请求数（严格说这是 `RPS`，`Requests Per Second`），数据库场景指每秒 `SQL` 查询数。中文语境常混用，面试和讨论中要明确语境
    - **统计方法**：
        - **监控系统**：`Prometheus` + `nginx-prometheus-exporter`（从 `nginx stub_status` 取数，`nginx_http_requests_total` 速率为 `QPS`）或应用埋点
        - **`Nginx access log`**：按时间窗口统计日志条数
        - **压测工具**：`ab`、`wrk`、`JMeter` 直接报 `QPS`

    - **相关概念**：峰值 `QPS`（扩容依据，行业更常用最大 `5` 分钟窗口均值或 `TP99`，而非秒级瞬时毛刺值）vs 平均 `QPS`（容量规划参考）

- **`TPS`（`Transactions Per Second`，每秒事务数）**
    - **含义**：系统每秒处理的事务数量。事务是业务层面的完整操作，一个事务可能包含多个请求/查询
    - **典型**：一次下单 = 一个事务，但可能包含"查库存 + 扣款 + 生成订单 + 通知"等多个请求
    - 通常 `TPS` ≤ `QPS`（一个事务由多个请求组成时），但取决于事务定义与接口粒度——批量/批处理接口（一个 `HTTP` 请求处理 `N` 笔转账）会出现 `TPS` > `QPS`
    - **统计方法**：应用埋点（在事务完成点打点）、`APM` 工具
    - **适用**：电商下单、支付、银行转账等有明确"事务边界"的业务

- **`PV`（`Page View`，页面浏览量）**
    - **含义**：页面被浏览的次数。每次刷新/打开页面都算一次 `PV`，同一用户重复浏览累加
    - **统计方法**：
        - **`Nginx access log`**：统计 `HTML` 页面请求数（需过滤静态资源、排除非 `2xx`、过滤爬虫）
        - 前端埋点 / RUM（真实用户监测）

    - **爬虫污染**：`2025` 年 `AI` 爬虫（`GPTBot`、`ClaudeBot` 等）激增，严重污染 `PV`/`UV` 统计，行业标准做法是 `UA` 白名单过滤
    - 注意 `PV` ↔ `QPS` 不能直接换算：`1` 次 `PV`（页面加载）通常产生 `1` 个 `HTML` + 数个静态资源/接口请求（`1 PV` ≈ `5~20` 个请求）；容量规划估算：峰值 `QPS` ≈（日均 `PV` × 页面平均请求数 × 峰值系数）/ `86400`

- **`UV`（`Unique Visitor`，独立访客数）**
    - **含义**：去重后的独立访客数，同一用户只算一次
    - **统计方法（去重口径，不同口径结果不同）**：
        - **基于 `Cookie` / 第一方标识**：给访客分配唯一 `ID` 去重。`2025` 年隐私环境下的主流已演变为"第一方 `Cookie` + 登录 `ID` + 设备指纹"的混合识别（`GA4` 等基于 `first-party` 标识去重）——— 因为第三方 `Cookie` 在 `Safari`/`Firefox` 默认拦截、`GDPR`/个保法限制下受限（`Chrome 2024` 年放弃强制淘汰第三方 `Cookie`、`2025` 年终止 `Privacy Sandbox` 计划）
        - **基于 `IP`**：按 `IP` 去重（不准确 —— `NAT` 后多人同 `IP` 算 `1` 个；同一人换 `IP` 算多个）
        - **基于用户登录 `ID`**：最准确，但只能统计登录用户

    - 注意：`UV` 是"人"的维度，`PV` 是"次"的维度。`PV/UV` 比值 = 人均浏览页数 ——— 不能简单解读为"内容吸引"，比值高也可能因导航差反复点击、长文分页；需结合跳出率、停留时长解读

- **四个指标的维度对比**
    - **`QPS`**：系统处理能力（实时、按请求数）
    - **`TPS`**：业务事务处理能力（实时、按事务数）
    - **`PV`**：页面访问量（累计、按次数）
    - **`UV`**：独立访客数（累计、按人去重，与 `QPS` 无换算关系）

- **协助记忆**
    - `QPS` 是"每秒能扛多少请求"，`TPS` 是"每秒能完成多少业务"，`PV` 是"被看了多少次"，`UV` 是"有多少人来看"
    - `QPS/TPS` 看系统强不强，`PV/UV` 看业务火不火
    - `PV/UV` 比值：人均看几页，需结合跳出率解读

- **进阶思考**
    - **如何从 `Nginx access log` 统计 `QPS` 和 `PV`？**
        - `combined` 格式时间戳如 `10/Feb/2025:14:23:45`，`$4` 是完整时间戳。按分钟统计：`awk '{print $4}' access.log | cut -d: -f2-3 | sort | uniq -c`（必须 `sort` 再 `uniq`，多 `worker` 写日志时同分钟行不连续，不 `sort` 会重复计数）；按秒看峰值：`cut -d: -f2-4 | sort | uniq -c | sort -rn | head`。统计 `PV`：过滤静态资源 + 非 `2xx` + 爬虫 ——— `grep -E '\.(html|htm)'` 在 `URL` 带 `query string` 或动态路由站点会失效，更可靠的是按 `URL` 类型/路径前缀区分或用 `GoAccess`、`ELK` 等分析工具。`QPS` 更推荐用监控系统（`nginx-prometheus-exporter`）而非日志

    - **`UV` 统计中"基于 `Cookie`"和"基于 `IP`"哪个更准确？为什么行业多用 `Cookie`？**
        - 基于 `Cookie` 更接近真实人数，是行业主流——因为 `IP` 在 `NAT` 场景下多人共享一个 `IP`（会被算成 `1` 个 `UV`），而同一用户在不同网络下 `IP` 会变（会被算成多个 `UV`），`IP` 口径误差大。`Cookie` 的问题是用户清浏览器缓存会重新计数（略微高估）、禁 `Cookie` 则无法统计。`2025` 年隐私环境下的主流是"第一方 `Cookie` + 登录 `ID` + 设备指纹"混合识别（`GA4` 基于 `first-party` 标识去重），第三方 `Cookie` 因 `Safari/Firefox` 拦截和法规限制已不可依赖


- 扩展信息
    - **·**
        - `Apache HTTP Server` 自带的基准测试工具，单进程模型（`-c` 靠 `fork` 子进程，非线程），无 `HTTP/2`
        - 官方自认"未完整实现 `HTTP/1.x`"、解析脆弱——定位是快速冒烟测试（"给你当前 `Apache` 安装表现的一个印象"），不适合严谨压测
        - 适用：随手测一下吞吐量。`2025` 年已属过时，正式压测选下面这些

    - **`wrk` / `wrk2`**
        - `wrk`：多线程 + `epoll/kqueue` 事件驱动，单颗多核 `CPU` 即可产生高负载，`LuaJIT` 脚本定制请求
        - 维护状态：长期未实质更新（`wrk` 停留在 `2014` 年前后）；`wrk2` 是事实上的标准改进 `fork` ——— 恒定吞吐（`-R` 参数）+ `HdrHistogram` 精确延迟记录，修复"协调遗漏（`Coordinated Omission`）"，可报 `99.9999%` 分位延迟
        - 适用：命令行快速吞吐/延迟测试；要正确的高分位延迟用 `wrk2`

    - **`JMeter`**
        - `Apache` 开源、纯 `Java` 的全功能负载测试工具，从 `Web` 扩展到 `JDBC`/`JMS`/`FTP`/`LDAP`/`TCP` 等十余种协议
        - 图形化 `Test IDE` + `CLI` 无头模式 + 动态 `HTML` 报告，`Groovy/JSR223` 脚本化，高度可扩展
        - 注意：官方明确"`JMeter is not a browser`" ——— 协议层工作，不执行 `JS`
        - 适用：功能/负载测试一体化、多协议、团队 `GUI` 协作（重量级）

    - **现代压测工具（2025 年主流）**
        - **`k6`（最活跃）**：`Go` 内核 + `JS` 脚本"`tests as code`"，`HTTP/WebSocket/gRPC/Browser`，阈值/`SLO`、`CI` 集成，`2025` 年事实上的新一代主流
        - **`Locust`（活跃，`Microsoft` 赞助）**：纯 `Python` 写场景，`gevent` 协程 + `Web UI` + 分布式，适合高并发用户模拟
        - **`Vegeta`**：`Go`，恒定速率 + `UNIX` 组合式 `CLI` + `Go` 库，内置 `Prometheus exporter`
        - **`hey`**：`Go` 单文件，自称 "`ApacheBench (ab) replacement`"，`HTTP/2`、限速、`CSV` 输出，小而够用
        - **`oha`（活跃）**：`Rust` + `tokio`，实时 `TUI`，`HTTP/2/3`（实验）、`burst`、`--latency-correction`（同样修复协调遗漏）
        - **高分位延迟的正确性**：`wrk2` / `Vegeta` / `oha` 都处理协调遗漏，`ab` 和 `wrk` 不处理

    - **`APM`（`Application Performance Monitoring`，应用性能监控）**
      - **核心能力四件套**：链路追踪（跨服务请求流、瓶颈、根因、依赖分析）、性能剖析（方法级 `CPU`/内存 `profiling`）、错误监控（异常捕获、聚合、告警）、指标 + 仪表盘 + 告警
      - **`Jaeger`**：`CNCF` 毕业项目，纯分布式追踪平台，已发 `v2`、原生拥抱 `OpenTelemetry`（`OTLP`、`ClickHouse` 存储后端） ——— 轻量自托管选它
      - **`SkyWalking` / `Pinpoint`**：`Java agent` 无侵入字节码注入，覆盖 `trace` + 指标 + 日志 + `profiling` 的全栈 `APM` ——— 全栈自托管选它们
      - **`Datadog APM` / `New Relic`**：商业 `SaaS`，自动埋点 + 分布式追踪 + 错误监控 + 持续剖析 + `SLO`/告警的托管全栈——省事省运维选它们


    一句话选型：快速冒烟测试用 `ab`；命令行脚本化压测用 `wrk`/`wrk2`、`hey`；`CI` 常态化压测首选 `k6`；`Python` 团队用 `Locust`；多协议/团队协作用 `JMeter`；自托管链路追踪用 `Jaeger`；全栈 `APM` 自托管用 `SkyWalking`。

## 🤔 简述 HTTP 和 HTTPS 区别？
- **`HTTP` 和 `HTTPS` 的核心区别：`HTTPS` 是在 `HTTP` 与 `TCP` 之间插入了一层 `TLS`（旧称 `SSL`）加密层，保证传输安全。`HTTP` 是明文传输，`HTTPS` 是加密传输。**

    - **`HTTP（HyperText Transfer Protocol）`**
        - **明文传输**：请求和响应内容（`URL`、请求头、`Cookie`、表单数据、响应体）在网络中以明文传输，可被中间人抓包直接读取
        - **默认端口 `80`**
        - **无身份验证**：无法确认你连的服务器就是目标服务器（可能被 `DNS` 劫持/中间人冒充）
        - **无认证性完整性保护**：数据可能被中间人篡改而不被发现（`TCP` 校验和只能发现传输错误，防不了恶意篡改）
        - **适用**：非敏感场景。如今内网也推荐 `HTTPS`（零信任趋势）

    - **`HTTPS（HTTP over TLS）`**
        - **加密传输**：在 `HTTP` 和 `TCP` 之间加了一层 `TLS`（`Transport Layer Security`，旧称 `SSL`） ，数据加密后传输
        - **默认端口 `443`**
        - **提供三方面安全能力**：
            - **机密性**：内容加密，中间人抓包只能看到密文（但 `SNI` 域名与流量元数据仍可见，这正是 `ECH` 要解决的——见下文）
            - **完整性**：`AEAD` 认证加密（`TLS 1.2` 及以前用 `MAC`）保证数据未被篡改
            - **身份认证**：通过服务器证书验证服务器身份，防止中间人冒充

        - **证书机制**：服务器需要部署 `CA`（证书颁发机构）签发的证书，包含公钥和服务器身份信息，客户端验证证书的信任链

    - **`HTTPS` 的建立流程（简版）**
        - `TCP` 三次握手 → `TLS` 握手（协商加密套件、交换密钥、验证证书）→ 加密的 `HTTP` 通信
        - `TLS 1.3` 首次完整握手 `1-RTT`；`PSK` 会话恢复通常仍为 `1-RTT`；开启 `early data`（`0-RTT`）可零往返发送幂等请求，但有重放风险、无前向保密，规范要求默认不启用（部分 `CDN` 对 `QUIC 0-RTT` 会开启）

    - **性能差异**
        - `HTTPS` 比 `HTTP` 多一次 `TLS` 握手开销（首次连接多 `1~2` 个 `RTT`：`TLS 1.3` 为 `1-RTT`、`TLS 1.2` 为 `2-RTT`）
        - 现代优化（`TLS 1.3`、会话恢复、`HTTP/3` + `QUIC 0-RTT`）已让差异很小

    - **应用现状（截至 2026 年）**
        - `HTTPS` 已是绝对主流：主流浏览器对 `HTTP` 站点显示"不安全"警告，搜索引擎降权 `HTTP` 站点；`TLS 1.3` 自 `2018` 发布以来已广泛部署，`TLS 1.2` 是主要回退版本
        - 免费证书普及：`Let's Encrypt` 提供免费自动化证书（`certbot`），`HTTPS` 部署成本几乎为零
        - `HSTS` 强制浏览器只走 `HTTPS` ——— 注意其生效前提是首次请求已走 `HTTPS`（或加入 `preload list`），否则第一个请求仍可能被 `SSL-strip` 降级
        - `ECH`（`Encrypted Client Hello`，`2026` 年 `3` 月标准化为 `RFC 9849`） ：加密整个 `ClientHello`（含 `SNI` 域名），配合 `DoH` 防域名泄露——解决"抓包只见密文但域名仍可见"的最后一块短板。`Chrome/Firefox` 已默认启用，`OpenSSL 4.0`、`nginx` 已支持
        - `HTTP/3` 基于 `QUIC`，本身就要求 `TLS 1.3` 加密 ——— 现代 `Web` 已无"明文 `HTTP/3`"这回事

- **协助记忆**
    - **一句话**：`HTTPS` = `HTTP` + `TLS` 加密层，明文变密文
    - **三个安全能力**：机密性（加密）、完整性（防篡改）、身份认证（防冒充）
    - **端口记忆**：`80` 明文，`443` 加密
    - **类比**：`HTTP` 是明信片（谁都能看内容），`HTTPS` 是密封信（只有收发双方能看）

- **进阶思考**
    - **`HTTPS` 能防止所有攻击吗？**
        - 不能。`HTTPS` 保护的是传输过程，不保护端点本身 ——— `Web` 应用漏洞（`SQL` 注入、`XSS`）发生在应用层，`HTTPS` 管不了；钓鱼网站本身就用 `HTTPS`（有合法证书），`HTTPS` 只证明"你是连到了证书对应的服务器"，不证明"这个服务器是可信的"。`HTTPS` 解决的只是"传输中不被偷看、篡改、冒充"

    - **为什么 `HTTP/3` 没有明文版本？**
      - `HTTP/3` 基于 `QUIC`，`QUIC` 在设计上强制内置 `TLS 1.3`（加密是协议的一部分，不是可选项）。这和 `HTTP/2` 不同 ——— `HTTP/2` 虽然规范支持 `h2c`（明文），但浏览器从未实现明文 `HTTP/2`。所以到了 `HTTP/3` 时代，明文 `HTTP` 事实上只剩 `HTTP/1.1` 还在用

## 🤔 Session 共享是什么？有哪些实现方式？	
- **`Session` 是服务器端保存的用户会话状态（登录状态、购物车、临时数据）。单机部署时 `Session` 存在本机内存即可；多实例部署时，用户的请求可能落到不同的服务器——如果 `Session` 只存在某台服务器上，落到其他服务器的请求就"不认识"这个用户了。`Session` 共享就是把 `Session` 从单台服务器内存中拿出来，让集群中所有服务器都能访问同一份会话状态。**

    - **为什么需要 `Session` 共享**
        - **单机时代**：`Session` 存在本机内存，一个进程处理所有请求，天然可用
        - **集群时代**：多台服务器 + 负载均衡，请求会分散到不同机器，`Session` 存在 `A` 机器，请求落到 `B` 机器就丢失登录状态
        - **水平扩展**：缩容掉持有 `Session` 的那台机器，用户就被登出

    - **实现方式一**：`Session` 复制（已过时，不推荐）
        - 各服务器之间互相复制 `Session`。`Tomcat` 集群两种实现：`DeltaManager`（`all-to-all`，每台机器保存全量 `Session`）和 `BackupManager`（只复制到一台备份节点，主备模式）
        - **细节**：成员发现/心跳走 `multicast`（默认 `228.0.0.4:45564`），会话数据复制走 `TCP` ——— 大集群下 `all-to-all TCP` 复制 + `multicast` 心跳的网络开销剧增
        - 已基本被淘汰，适合小集群

    - **实现方式二**：`Session Sticky`（粘性会话，治标不治本）
        - 负载均衡器把同一个用户的请求始终转发到同一台服务器（基于 `IP hash`、`Cookie` 或 `URL` 参数）
        - 优点：实现简单，`Session` 仍可存在单台机器内存
        - 缺点：只是"绕开"了问题而不是解决——某台服务器宕机，粘在上面的用户全部掉线；扩容/缩容会破坏 `hash` 映射（用一致性哈希可大幅减少重映射，如 `Nginx hash ... consistent`）；无法做到真正的负载均衡（热点用户集中在一台机器）
        - 定位：不推荐作为唯一会话方案（云原生场景如 `K8s ingress`/`ALB` 中 `sticky` 仍是常规实践，但通常与集中共享叠加使用）

    - **实现方式三**：集中式 `Session` 存储（主流方案）
        - 把 `Session` 从各服务器内存中抽出来，存到一个集中存储（`Redis` / `Memcached`）
        - 所有服务器从同一个地方读写 `Session`，天然共享
        - `Redis` 方案（最常用）：支持 `TTL` 过期，正好匹配 `Session` 的过期机制；读写快、支持集群
        - 框架支持：`Java` 的 `Spring Session`（支持 `Redis`/`JDBC`，切换不改应用代码）、`Hazelcast`/`Infinispan`（社区扩展）、云托管 `Redis`
        - 优点：服务器无状态、支持水平扩展、单台服务器宕机不影响其他机器
        - 注意点：`Redis` 是新的单点——本身需要高可用（主从、哨兵、集群）；`Session` 是热数据，`Redis` 内存开销需要考虑

    - **实现方式四**：客户端存储 / 无状态 `Token`（趋势方案）
        - **不把会话状态存在服务器，而是放到客户端**：
            - **`JWT（JSON Web Token）`** ：签名后的 `token` 存在客户端（`localStorage`/`Cookie`），服务器验证签名即可，无需存储 `Session`。注意"无法主动失效"是简化说法——严格可用黑名单/`jti`/版本号实现服务端撤销，但代价是重新引入状态
            - 签名/加密 `Cookie` 直接存会话数据（如 `Rails cookie_store`、`Flask`、`Django signed_cookies`）：注意这是"消除共享需求"而非"共享"——数据在 `Cookie` 里服务端已无状态，不应再叫 `Session`；受 `Cookie` 约 `4KB` 大小限制

        - 优点：服务器完全无状态、天然支持分布式、不需要额外 `Session` 存储
        - 缺点：`JWT` 主动失效难、`token` 泄露后有攻击窗口、体积比 `Session ID` 大
        - 适用：`API`/前后端分离场景主流

    - **`2026` 年演进补充**
        - `OIDC/SSO`（企业认证标准，基于 `JWT`）成为多应用统一登录的主流方案
        - `Passkeys`/`WebAuthn`（无密码认证）兴起，减少对传统 `Session`/`Cookie` 的依赖
        - 第三方 `Cookie` 逐步淘汰对依赖 `Cookie` 的 `Session`/`Sticky` 方案有冲击；`SameSite` 默认 `Lax` 影响跨站 `Cookie` 会话

    - **方案对比速览**
        - **`Session` 复制**：已淘汰（同步开销大）
        - **`Sticky`**：临时过渡（宕机掉线、不均衡）
        - **`Redis` 集中存储**：主流（服务器无状态、支持扩展）
        - **`JWT` 无状态**：趋势（完全无状态、但难失效）

- **协助记忆**
    - **`Session` 共享的本质**：把"存在单台机器内存里的会话"搬到"所有机器都能访问的地方"
    - **一条主线**：`Session` 存储位置从"服务器内存"→"集中存储（`Redis`）"→"客户端（`JWT`）"，越来越无状态
    - **选型**：传统 `Web` 应用用 `Redis` 集中存储（`Spring Session`）；前后端分离/`API` 用 `JWT`

- **进阶思考**
    - **`Redis` 存 `Session` 和 `JWT` 怎么选？**
        - 看业务需求。需要主动失效（登出、踢人、封禁）、需要服务端可控（管理在线用户）、传统服务端渲染应用 → `Redis` 存 `Session`（服务端可随时删 `Session`）；需要跨域/多端无缝（小程序、`App`、`Web` 共享登录）、追求服务端零存储、`API` 场景 → `JWT`。混合方案也常见：`JWT` 做认证 + `Redis` 做登出黑名单

    - **`Session Sticky` 和 `Session` 共享是二选一吗？**
        - 不是，`Sticky` 是"让请求总去同一台机器"来规避共享问题，`Session` 共享是"让所有机器共享同一份状态"。两者可以叠加：生产常见做法是 `Sticky` 保性能 + 集中共享存储保故障转移——正常时请求粘在本地减少跨节点访问，节点宕机时共享存储兜底。但只依赖 `Sticky` 的方案（宕机全掉线）不可接受

## 🤔 Tomcat 8005、8009、8080 端口分别作用是什么？
- **`Tomcat` 的三个经典端口各有分工：`8005` 是 `Shutdown`（关闭）端口、`8009` 是 `AJP` 端口（与 `Apache httpd` 集成）、`8080` 是 `HTTP` 端口（对外服务）。都在 `conf/server.xml` 中配置。**

    - **`8005`**：`Shutdown`（关闭）端口
        - **作用**：用于关闭 `Tomcat` 的端口。向该端口发送特定的 `SHUTDOWN` 命令字符串，`Tomcat` 会优雅关闭。它只接受一个固定的 `SHUTDOWN` 字符串，没有其他管理能力（真正的管理机制是 `JMX`，默认随机端口）
        - **配置**：`<Server port="8005" shutdown="SHUTDOWN">`，关闭命令字符串默认为 `SHUTDOWN`
        - **现状**：`8005` 在 `Tomcat 9/10/11` 的默认 `server.xml` 中始终启用，且默认只监听 `localhost（127.0.0.1）`。"禁用"是社区加固建议，不是版本默认行为
        - **安全注意**：如果配置不当绑定到 `0.0.0.0` 或防火墙未限制，任何人连接 `8005` 发 `SHUTDOWN` 就能关掉 `Tomcat`（`DoS`）
        - **加固方式（注意别把 `Tomcat` 停不掉）**：
            - 确保监听 `127.0.0.1`、修改 `shutdown` 字符串
            - 用 `port="-1"` 完全禁用该端口 ——— 但注意 `catalina.sh stop` 默认正是通过 `8005` 发 `SHUTDOWN`，禁用后必须：设置 ·（此时 `catalina.sh` 走 `kill` 路径）、或用 `jsvc` / `Apache Commons Daemon` 停止，否则 `Tomcat` 无法优雅停止

    - **`8009`**：`AJP` 端口（与 `Apache httpd` 集成）
        - **作用**：`AJP`（`Apache JServ Protocol`）连接器端口，用于 `Tomcat` 与前端 `Apache httpd` 之间的通信（通过 `mod_jk` 或 `mod_proxy_ajp`）。注意 `Nginx` 不支持 `AJP` ——— `Nginx` 与 `Tomcat` 集成应走 `HTTP（proxy_pass）`
        - **场景**：`Apache httpd` 作为前端静态服务器 + `Tomcat` 处理动态请求，两者通过 `AJP` 通信
        - **配置**：`<Connector port="8009" protocol="AJP/1.3" />` ——— `8.5+` 默认 `server.xml` 中 `8009` 已注释（需手动启用，默认示例 `address="::1"`）
        - **`Ghostcat` 漏洞（`CVE-2020-1938`）** ：`AJP` 端口未限制时可读取 `webapps` 下任意文件甚至执行代码。修复版（`9.0.31`/`8.5.51+`）起默认要求 `secret`（`secretRequired` 默认 `true`）且默认只监听 `loopback`
        - **现状**：`AJP` 未被官方废弃（`Tomcat 10/11` 仍支持），但主要存在于历史遗留架构（`Apache httpd` + `Tomcat` 集成）；与 `HTTP` 相比对 `HTTP/2` 等现代特性支持不足

    - **`8080`**：`HTTP` 连接器端口（对外服务）
        - **作用**：`Tomcat` 对外提供 `HTTP` 服务的默认端口
        - **配置**：`<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />`
        - **`redirectPort="8443"`**：当应用配置了 `SSL` 要求（`web.xml` 中 `security-constraint` 且 `transport-guarantee=CONFIDENTIAL`）时，`HTTP` 请求自动重定向到 `8443`（`HTTPS` 端口）
        - **默认 `8080` 而非 `80`**：`Unix` 下 `1024` 以下端口需 `root` 权限而 `Tomcat` 默认非 `root` 运行（`Windows` 无此限制），兼为避免与既有 `Web` 服务冲突

    - **`Tomcat 10/11` 的变化（`2020` 年后）**
        - **最大变化**：`javax.servlet` → `jakarta.servlet` 包名迁移（`Tomcat 10` 起），老应用需改包名才能运行
        - **三个端口的机制与默认配置不变**：`8005`/`8080` 默认启用、`8009` 默认注释

- **协助记忆**
    - 三个端口一句话：`8005` 关 `Tomcat`（管理）、`8009` 连 `Apache`（`AJP` 集成）、`8080` 给用户（`HTTP` 服务）
    - `8080` → `8443` 重定向：`HTTP` 强制跳 `HTTPS` 时用（`redirectPort`）
    - 安全三查：`8005` 别绑外网（禁用前先配 `CATALINA_PID`）、`8009` 别裸奔（`Ghostcat`）、`8080` 按需开放

- 进阶思考
    - **`8005` 端口关闭命令的安全性如何保障？**
        - `8005` 的 `SHUTDOWN` 默认字符串是固定的 `SHUTDOWN`，一旦端口可达任何人都能关掉 `Tomcat`。保障手段：确保监听 `127.0.0.1`（默认）；防火墙限制本机访问；修改 `shutdown` 字符串；最彻底是 `port="-1"` 禁用 ——— 但禁用后必须设置 `CATALINA_PID` 或改用 `jsvc`，否则 `catalina.sh stop` 无法优雅停止（它默认走 `8005` 发 `SHUTDOWN`）

    - **为什么现在很多架构不用 `8009 AJP` 了？**
        - 一是 `Ghostcat` 漏洞（`CVE-2020-1938`）暴露了安全风险；二是现代前端架构变了 ——— `Nginx` 已成为主流反向代理，直接用 `proxy_pass` 走 `HTTP（8080）`就能完成转发，而且 `Nginx` 根本不支持 `AJP`；三是 `AJP` 对 `HTTP/2` 等现代特性支持不足。`AJP` 的优势（减少解析开销）在现代硬件上可忽略。所以 `AJP` 主要存在于 `Apache httpd` + `Tomcat` 的历史架构中

## 🤔 Tomcat 如何性能优化？	
- **`Tomcat` 性能优化要分层进行：`JVM` 参数 → 连接器（`Connector`）→ 应用层 → 操作系统层。核心原则是"先找准瓶颈再优化"，不要盲目调大参数。**

    - **第一层**：`JVM` 参数优化
        - **堆内存**：设置 `-Xms`（初始堆）和 `-Xmx`（最大堆）为相同值，避免运行时动态扩缩堆造成性能抖动。参考值：`-Xms2g` `-Xmx2g`（按实际内存调整；容器场景下 `JDK 10+` 默认启用 `UseContainerSupport`，`-Xmx` 受容器配额约束）
        - **元空间**：`-XX:MaxMetaspaceSize` 设置上限，防止元空间无限增长
        - **`GC` 选择**：`JDK 9+` 默认 `G1`，适合大堆（`4GB+`）；超大堆（几十 `GB`）追求低延迟用 `ZGC`（`JDK 11` 实验、`15` 生产可用）
        - **`GC` 日志**：`-Xlog:gc*` 启用 `GC` 日志，用于排查 `GC` 停顿
        - **常见误区**：盲目调大 `-Xmx` 不一定提升性能，堆过大反而增加 `GC` 停顿

    - **第二层**：连接器（`Connector`）优化
        - **连接器模式**：`Tomcat 8.5/9` 默认 `NIO`（装了 `tomcat-native` 时自动用 `APR`）；`Tomcat 11` 起默认纯 `Java NIO`。`BIO` 在 `8.5` 已移除
        - **线程池参数（`Executor`）**：
            - **`maxThreads`**：最大工作线程数，默认 `200`。不是越大越好——线程过多导致上下文切换开销增大；建议结合压测确定，一般 `200~500`
            - **`minSpareThreads`**：保底存活线程数（`Connector` 属性默认 `10`，`Executor` 上默认 `25`）；注意是"保底水位"，线程按需增长到该水位，并非启动即全部创建
            - **`acceptCount`**：等待队列长度，默认 `100`。高并发时调大（如 `500~1000`）
            - **`maxConnections`**：最大连接数，`NIO` 默认 `8192`

        - **连接超时**：`connectionTimeout` 文档默认 `60000ms`，但随 `Tomcat` 发行的默认 `server.xml` 显式配置为 `20000ms` ——— 如果自定义 `server.xml` 未写该属性，实际生效的是 `60000ms`；`keepAliveTimeout` 控制长连接存活时间
        - **压缩**：`compression="on"` 开启 `gzip` 压缩，减少传输量（对文本类资源效果明显）
        - **静态资源**：配置资源缓存或交给前端 `Nginx` 处理（推荐）

    - **第三层**：应用层优化
        - **数据库连接池**：配置合理的连接池参数（初始/最大连接数），避免频繁创建销毁连接
        - **缓存**：热点数据用 `Redis`/本地缓存，减少数据库压力
        - **异步处理**：长耗时操作用 `Servlet 3.1` 异步处理，释放线程
        - **避免阻塞**：`IO` 操作异步化，减少线程占用

    - **第四层**：操作系统层
        - **文件描述符**：调大 `ulimit -n`（默认 `1024` 太小，`Tomcat` 高并发会报 `too many open files`）
        - **内核参数**：`net.core.somaxconn`（配合 `acceptCount`）、`net.ipv4.tcp_*` 相关优化

- **协助记忆**
    - **优化四层**：`JVM`（堆 + `GC`）→ 连接器（线程池 + 超时）→ 应用（连接池 + 缓存）→ 系统（fd + 内核）
    - **核心原则**：先压测找瓶颈，再对症下药；盲目调大参数反而更糟
    - **三个默认值**：`maxThreads 200`、`acceptCount 100`、`maxConnections 8192（NIO）`

- **进阶思考**
    - **`Tomcat` 线程数（`maxThreads`）是不是越大越好？**
        - 不是。线程是"资源"而非"能力"——每个线程占用栈内存，线程过多导致内存占用大、`CPU` 上下文切换开销大（频繁切换反而降低吞吐）。正确做法：用压测工具（`JMeter`/`k6`）逐步加压，观察吞吐量拐点，在吞吐不再增长的点设置 `maxThreads`。通常 `200~500` 是常见范围，但具体要压测确定。还要配合 `maxConnections` 和 `acceptCount` 一起看——三者共同决定了并发处理能力

    - **`Tomcat 10/11` 有哪些值得关注的性能相关变化？**
        - `Tomcat 10` 最大变化是 `jakarta` 命名空间迁移（`javax→jakarta`）；`Tomcat 11` 对应 `Jakarta EE 11`。真正值得关注的是虚拟线程（`Virtual Threads`） ——— `Tomcat 10.1` 和 `11` 都支持（通过 `StandardVirtualThreadExecutor`，需 `JDK 21+`，且默认都不启用、需在 `server.xml` 显式配置）。注意概念澄清：`Tomcat NIO` 自 `6.0` 起就不是"`1` 线程 `1` 连接" ——— `acceptor` + `poller` 线程负责连接，工作线程池处理的是请求而非连接；虚拟线程改变的是"处理请求的工作线程"的实现（平台线程→虚拟线程），让每个请求跑在 `KB` 级栈的虚拟线程上，突破 `maxThreads=200` 的平台线程上限。仅对 `IO` 密集型（阻塞占比高）应用收益明显；`CPU` 密集型无增益；注意 `JDK 21` 上 `synchronized` 会固定（`pin`）虚拟线程（`JDK 24` 的 `JEP 491` 才解决）、`ThreadLocal` 规模放大问题


## 🤔 简述 LVS 的三种模式及其工作原理？
- **`LVS`（`Linux Virtual Server`）是 `Linux` 内核内置的负载均衡方案，工作在内核空间（第四层，基于 `IP` + 端口），性能高。`LVS` 有三种工作模式：`NAT`、`DR`（`Direct Routing`）、`TUN`（`IP Tunneling`），核心区别在于数据报文如何流转、响应流量是否经过调度器。**

    - **模式一**：`NAT` 模式（`VS`/`NAT`）
        - **原理**：调度器同时改写请求和响应的 `IP` 地址——请求进来做 `DNAT`（目标地址改为 `RS`(`Real Server`; 真实服务器)），响应方向做逆 `NAT`（源地址从 `RS` 改回 `VIP`，基于连接跟踪的双向改写）
        - **报文流转**：请求 → 调度器 → `RS`；响应 → 调度器 → 客户端。请求和响应都经过调度器
        - **特点**：
            - **优点**：`RS` 可以使用任意操作系统和私网 `IP`，配置简单；是唯一支持端口映射的模式（`VIP` 端口可与 `RS` 端口不同）
            - **缺点**：调度器是瓶颈——所有响应都经过它，吞吐受限于网卡带宽 + 连接表/`CPU`（每个 `NAT` 连接在哈希表占两个节点）
            - `RS` 的网关必须指向调度器

        - **适用**：`RS` 数量少、流量不是特别大的场景

    - **模式二**：`DR` 模式（`VS`/`DR`，直接路由）
        - **原理**：调度器只改写数据帧的目标 `MAC` 地址（源 `MAC` 也会改写）把请求转发给 `RS`；`RS` 处理完直接把响应绕过调度器返回客户端
        - **报文流转**：请求 → 调度器 → `RS`；响应 → `RS` → 客户端（不经过调度器）
        - **关键配置**：`VIP` 同时配置在调度器和所有 `RS` 上 ——— `RS` 的 `VIP` 配置在 `lo` 接口上，并抑制 `ARP` 响应（`arp_ignore=1`、`arp_announce=2`）
        - **特点**：
            - **优点**：响应不经过调度器，调度器只处理请求流量，性能高，生产环境最常用
            - **缺点**：`RS` 和调度器必须在同一二层网络（同网段）；`RS` 端口必须与 `VIP` 端口相同；`RS` 需额外配置 `VIP` 和 `ARP` 抑制
            - **适用**：大规模、高流量场景（`Web` 集群主流）


    - **模式三**：`TUN` 模式（`VS`/`TUN`，`IP` 隧道）
        - **原理**：调度器把请求封装在 `IP` 隧道（`IP-in-IP`，也支持 `GRE`/`SIT`/`GUE`）中转发给 `RS`；`RS` 解封装后处理，响应直接返回客户端
        - **报文流转**：请求 → 调度器（封装）→ `RS`（解封装）→ 处理 → 响应 → 客户端（不经过调度器）
        - **关键配置（和 `DR` 一样）** ：`VIP` 配置在 `RS` 的非 `ARP` 设备（`tunl0`/`dummy`/`lo`）上，同样要抑制 `ARP` 响应 ——— 否则 `RS` 会抢答 `ARP`
        - **`MTU` 坑（`TUN` 实战第一坑）** ：`IPIP` 每包 `+20` 字节头，`MTU 1500` 网络中 `DF`（不分片）大包会被拒（`PMTU` 问题）；内核可配置 `pmtu_disc` 关闭让 `TUN` 方法分片
        - **特点**：
        - **优点**：`RS` 可以跨地域（不在同一网络），响应不经过调度器
        - **缺点**：需 `RS` 支持隧道协议（`modprobe ipip`、`tunl0 up`）；封装带来开销和 `MTU` 问题；配置复杂度高
        - **适用**：异地多活、`RS` 分布在多个机房的场景


    - **三种模式对比速览**
        - **`NAT`**：请求和响应都过调度器（调度器瓶颈），唯一支持端口映射
        - **`DR`**：请求过调度器，响应直接回（同二层网络，最常用），`RS` 端口必须等于 `VIP` 端口
        - **`TUN`**：请求隧道封装，响应直接回（可跨地域），`RS` 端口必须等于 `VIP` 端口

    - **2026 年生态现状**
        - `ipvs` 至今在内核 `6.x` 中活跃维护；`kube-proxy` 的 `IPVS` 模式是 `K8s` 大规模集群的常用方案
        - 演进方向：`DPVS`（`DPDK` 版 `ipvs`）、`Katran`/`Cilium`（`XDP`/`eBPF` 负载均衡）、`ECMP+BGP` 替代 `Keepalived/VRRP`

- **协助记忆**
    - **一句话区分**：`NAT` 是"快递员两头跑"（进出发都经调度器），`DR` 是"只送件不取件"（请求经调度器、响应直达），`TUN` 是"打包寄送"（隧道封装跨地域）
    - **`DR` 两个关键**：`VIP` 放 `lo` 接口 + 抑制 `ARP`（`TUN` 同样要）
    - **选型口诀**：同网段高流量用 `DR`，跨地域用 `TUN`，简单场景用 `NAT`

- **进阶思考**
    - **`DR` 模式为什么要抑制 `RS` 的 `ARP` 响应？不抑制会怎样？**
        - `VIP` 同时配置在调度器和所有 `RS` 上。如果 `RS` 不抑制 `ARP`，局域网设备对 `VIP` 发起 `ARP` 请求时所有 `RS` 都会响应（`ARP` 是广播的），导致 `MAC` 地址混乱——有的请求被转发到 `RS`，有的被 `RS` 直接抢答。抑制 `ARP`（`arp_ignore=1`、`arp_announce=2`）让只有调度器响应 `VIP` 的 `ARP`，`RS` 的 `VIP` 只用于接收转发请求，不对外宣告

    - **`LVS` 和 `Nginx` 负载均衡怎么选？**
        - `LVS` 工作在四层（`IP` + 端口），内核态，性能高（并发连接数可达百万级，注意是并发连接数而非 `QPS`）；但只做转发，本身不带健康检查和故障摘除，需 `Keepalived` 等外部组件配合。`Nginx` 工作在七层（`HTTP`），能做 `URL` 路由、重写、限流、缓存，但含 `TLS` 终止/`HTTP` 解析，开销天然更大（可比对象是 `Nginx stream` 四层模块）。生产架构常见组合：`LVS`（四层入口）→ `Nginx`（七层反向代理）→ 应用服务器，高可用由 `Keepalived` 实现 ——— `VRRP` 做 `VIP` 漂移 + 对 `RS` 健康检查并动态增删 `ipvs` 规则

## 🤔 LVS 支持哪些调度算法？	
- **`LVS`（`ipvs`）支持的调度算法分为静态和动态两大类：静态算法不考虑后端实时负载，动态算法根据后端当前负载/连接数做决策。截至 `2026` 年，`ipvs` 共有 `14` 个调度算法（`RR`/`WRR`/`DH`/`SH`/`MH` + `LC`/`WLC`/`SED`/`NQ`/`LBLC`/`LBLCR`/`FO`/`OVF`/`TWOS`）。**

- **静态调度算法**（不考虑后端实时状态）
    - **`RR`（`Round Robin`，轮询）** ：请求依次分发到每个 `RS`，轮流来
    - **`WRR`（`Weighted Round Robin`，加权轮询）** ：按权重比例分发，权重高的 `RS` 分到更多请求
    - **`DH`（`Destination Hashing`，目标地址哈希）** ：按请求的目标 `IP` 哈希，相同目标 `IP` 的请求始终分发到同一台 `RS`（用于缓存场景）
    - **`SH`（`Source Hashing`，源地址哈希）** ：按客户端源 `IP` 哈希，相同来源的请求到同一台 `RS`（天然实现会话保持）。注意"始终"是简化说法——`RS` 增删/权重变化会重建哈希表，且 `SH` 有 `sh-port`（`IP`+端口哈希）和 `sh-fallback`（故障回退）两个 `flag`
    - **`MH`（`Maglev Hashing`，`Maglev` 哈希，`Linux 4.18` 合入）** ：基于源 `IP` 的一致性哈希（`Google Maglev` 论文，`NSDI'16`），表容量按权重分配，支持 `mh-port/mh-fallback flag`。天然适合会话保持，`RS` 变化时受影响连接少

- **动态调度算法**（根据后端实时负载决策）
    - **`LC`（`Least Connections`，最少连接）** ：把请求分给当前连接数最少的 `RS`。严格语义是"活动连接数加权"（内核公式 (`activeconns<<8`) + `inactconns`）
    - **`WLC`（`Weighted Least Connections`，加权最少连接）** ：`LC` + 权重，按"连接数/权重"最小的原则分配。是 `ipvsadm` 的缺省算法（内核层面无默认）
    - **`SED`（`Shortest Expected Delay`，最短期望延迟）** ：考虑"活动连接数 + 1"与权重的比值`（(active+1)/weight）`，预测哪个 `RS` 延迟最短
    - **`NQ`（`Never Queue`，永不排队）** ：`SED` 的改进——如果某台 `RS` 活动连接数为 `0`，直接分配给第一个空闲者，避免请求排队；否则用 `SED`
    - **`LBLC`（`Locality-Based Least Connections`，基于局部性的最少连接）** ：目标 `IP` 哈希 + 最少连接结合，适合 `Cache` 集群（相同目标 `IP` 优先到同一台 `RS`）
    - **`LBLCR`（带复制的局部性最少连接）** ：`LBLC` 的改进——每个目标 `IP` 对应一个服务器集合（`set`），集合内按最少连接选、目标 `IP` 热度高时集合扩容（注意不是"复制流量"，是集合内多台 `RS` 可选）
    - **`FO`（`Weighted Failover`，加权故障转移）** ：把连接发给当前可用且权重最高的 `RS` ——— 类似主备故障转移（注意：不是"失效次数/权重"，那是网上的以讹传讹，内核源码就是"选权重最高的 `dest` 全给流量"）
    - **`OVF`（`Overflow-Connection`，溢出连接）** ：优先把连接给权重最高的 `RS`，当活动连接数超过其 `weight` 时"溢出"到权重次高者；全部过载则调度失败（无 `WLC` 回退）；只统计活动连接，不适合 `UDP`
    - **`TWOS`（`Power of Two Choices`，两随机选择，`Linux 6.4` 合入）** ：按权重随机抽两台候选 `RS`，比较活动连接数归一化开销，选更空闲的一台

- **选型建议**
    - 通用 `Web` 集群、无特殊需求：`WLC`（`ipvsadm` 默认）
    - 后端性能差异大：`WRR`（静态权重明确）
    - 需要会话保持：`SH` / `MH`（源地址哈希，`RS` 变化影响小）或配合持久性（`persistence`）
    - `Cache`/缓存集群：`DH` 或 `LBLC`（目标地址哈希保证缓存命中）
    - 防止单台过载：`OVF`
    - 高可用故障转移倾向：`FO`

- **协助记忆**
    - 静态五兄弟：`RR`、`WRR`、`DH`、`SH`、`MH`（"轮询、加权轮询、目标哈希、源哈希、`Maglev` 哈希"）
    - 动态九兄弟：`LC`、`WLC`、`SED`、`NQ`、`LBLC`、`LBLCR`、`FO`、`OVF`、`TWOS`
    - 默认是 `WLC`（`ipvsadm` 缺省） ，大多数场景够用；有特殊需求（会话保持、缓存命中）再换

- **进阶思考**
    - **`SH`（源地址哈希）和持久性（`persistence`）都能做会话保持，有什么区别？**
        - `SH` 是基于 `IP` 哈希的确定性分配 ——— 同一源 `IP` 按哈希到同一台 `RS`，不依赖连接状态；但 `NAT` 后面的多个用户共享一个 `IP` 会被分到同一台（负载不均）；且 `RS` 宕机时哈希到它的用户受影响（可通过 `sh-fallback` 缓解）。持久性（`persistence`）是基于连接跟踪的超时绑定——在一定时间窗口（默认 `300` 秒）内把同一来源的请求绑定到同一台 `RS`，超时重新分配。持久性更灵活（支持故障转移、负载更均衡），是更推荐的会话保持方式

    - **加权算法（`WRR`/`WLC`）的权重怎么确定？**
        - 权重反映 `RS` 的处理能力差异——通常按 `CPU` 核数、内存大小或压测结果定。例如两台服务器，一台 `8` 核一台 `4` 核，权重可设为 `2:1`。注意：权重是相对值不是绝对值；权重相同（如 `1:1`）就退化为普通轮询/最少连接。权重要根据实际压测调整，不能拍脑袋


## 🤔 简述 HTTP Cookie 和 Session 区别和联系？
- **`Cookie` 和 `Session` 都是 `Web` 应用中记录用户状态的机制，核心区别：`Cookie` 存在客户端（浏览器），`Session` 存在服务端。两者常配合使用 ——— `Session` 通过 `Cookie` 传递 `Session ID` 来识别用户。**

    - **`Cookie`（客户端状态）**
        - **定义**：由服务器通过 `Set-Cookie` 响应头下发的小段文本数据，浏览器保存在本地，后续请求自动通过 `Cookie` 请求头携带
        - **特点**：
            - **存储位置**：浏览器（客户端）
            - **容量**：约 `4KB` 上限（单个 `Cookie` 约 `4096` 字节，浏览器实现惯例；注意 `RFC 6265` 规范并未强制规定这个数值）
            - **生命周期**：可通过 `Expires/Max-Age` 设置过期时间——持久 `Cookie` 或会话 `Cookie`（无 `Expires/Max-Age` 的"会话 `Cookie`"与服务器 `Session` 是两个概念，仅名字相似）
            - **作用域**：同域（`Domain` + `Path` 限定），默认同站发送
            - **可被篡改**：客户端可修改 `Cookie` 内容（需签名/加密防篡改）

        - **常见属性**：`Secure`（仅 `HTTPS`）、`HttpOnly`（禁止 `JS` 读取）、`SameSite`（缓解 `CSRF`）、`Path/Domain`（作用域）

    - **`Session`（服务端状态）**
        - **定义**：服务器为每个会话创建的状态数据，存储在服务端（内存、文件、数据库、Redis）
        - **特点**：
            - **存储位置**：服务端
            - **容量**：无 `Cookie` 那样的硬限制（受服务端内存/存储限制）
            - **生命周期**：由服务端超时控制（如 `30` 分钟无活动过期），也可主动销毁
            - **标识**：通过 `Session ID`（一串随机字符串）识别，`Session ID` 对应服务端存储的会话数据
            - **安全性**：数据在服务端，客户端无法直接篡改


    - **联系**：`Session` 靠 `Cookie` 传递 `Session ID`
        - **典型流程**：用户登录 → 服务器创建 `Session` 并生成 `Session ID` → 通过 `Set-Cookie` 下发 `Session ID` → 浏览器存储 → 后续请求自动携带 → 服务器根据 `Session ID` 找到对应 `Session` 数据
        - **`Session ID` 的传递方式**：
            - **`Cookie` 方式（主流）** ：`Session ID` 存在 `Cookie` 中，自动携带
            - **`URL` 重写方式（备用）** ：`Session ID` 拼在 `URL` 参数中（如 `?jsessionid=xxx`）——适用于禁用了 `Cookie` 的场景，但有泄露风险（`URL` 可能被记录在日志/历史中）

        - **也就是说**：`Session` 本身不一定依赖 `Cookie`，但实践中几乎都靠 `Cookie` 传递 `Session ID`

    - **核心区别对比**
        - **存储位置**：客户端（`Cookie`）vs 服务端（`Session`）
        - **容量**：约 `4KB`（`Cookie`）vs 无硬限制（`Session`）
        - **安全性**：可篡改（`Cookie`）vs 服务端可控（`Session`）
        - **生命周期控制**：客户端可控制 vs 服务端控制
        - **性能**：每次请求都传输（`Cookie`）vs 服务端查询（`Session`）
        - **跨域**：`Cookie` 同域限制 vs `Session` 数据本身无域概念（但 `Session ID` 靠 `Cookie` 传递时仍受 `Cookie` 域限制）

    - **`2026` 年演进**
        - **`SameSite` 默认 `Lax`**：自 `Chrome 80（2020）`/`Firefox 86/Safari 13.1` 起默认。精确语义：跨站子资源、`fetch/XHR`、`iframe` 内请求默认不发送，但顶级导航 `GET` 请求仍携带（用户从别的站点击链接进入本站时会带 `Cookie`）；且默认值在 `Chromium` 系稳定为 `Lax`，其他浏览器略有差异
        - **第三方 `Cookie` 逐步淘汰**：`Safari`（`ITP`，`2020` 起）默认全面阻止；`Firefox` 默认启用 `ETP` + `Total Cookie Protection`（第三方 `Cookie` 按站点分区存储，标准模式仅拦截已知追踪器，并非拦截所有）；`Chrome` 至今不默认拦截，仅无痕模式或用户设置时拦截，`2024` 年放弃强制淘汰后逐步推出用户选择模式 
        - **无状态趋势**：`JWT` 等无状态 `token` 兴起 ——— 不依赖服务端 `Session` 存储，天然适合分布式；但主动失效难。`2026` 年业界出现回调反思——纯 `JWT` 存在吊销难、密钥轮换等痛点，部分场景回归"分布式服务端 `Session（Redis）`"或混合方案

- **协助记忆**
    - 一句话：`Cookie` 是"存在浏览器里的便签"，`Session` 是"存在服务器上的档案柜"，`Session ID` 是"打开档案柜的钥匙"（钥匙放便签里）
    - 钥匙在客户端，档案在服务端：客户端只有 `Session ID`（钥匙），真正的数据（档案）在服务端
    - 安全对比：档案（`Session` 数据）比便签（`Cookie`）安全，因为档案在服务器上，便签在用户手里随时可能被改

- **进阶思考**
    - **`Session` 数据都放服务端，为什么还需要 `Cookie`？不能不用 `Cookie` 吗？**
        - `HTTP` 是无状态协议，服务器不记得"你是谁"。`Session` 数据虽然存在服务端，但服务器要知道"这个请求对应哪份 `Session` 数据" ——— 这个对应关系（·）必须由客户端每次带来。· 是最自然的携带方式（自动发送）。如果不用 `Cookie`，只能靠 `URL` 重写传 `Session ID`，但 `URL` 易泄露（日志、历史记录、分享链接），所以实践中几乎都用 `Cookie`

    - **`Cookie` 和 `Session` 哪个更安全？**
        - 单从"存储"角度 `Session` 更安全 ——— 数据在服务端，客户端碰不到。`Cookie` 的主要风险其实是被窃取（`XSS` 窃取、明文传输窃听、`CSRF`）而非篡改 ——— 内容篡改在服务端签名校验后危害有限。`Session` 的风险在 `Session ID` 失窃（`XSS` 窃取 `Cookie`、中间人窃听）导致会话劫持；注意会话固定（`Session Fixation`）与窃取机制不同——它是攻击者预先固定一个 `ID` 给受害者使用（`RFC 6265 §8.4` 专述）。完整方案需组合：`Session ID` 用 `HttpOnly` + `Secure Cookie` 保护、传输用 `HTTPS`、防 `XSS（CSP）`、`SameSite` 缓解 `CSRF`

## 🤔 网站跨域报错 Access-Control-Allow-Origin 如何解决？	
- **跨域报错是浏览器同源策略（`Same-Origin Policy`）的保护机制：浏览器默认阻止网页脚本跨域读取响应。`CORS`（`Cross-Origin Resource Sharing`）是让服务器声明"允许哪些来源访问"的机制，报错 `Access-Control-Allow-Origin` 说明服务器没有正确声明允许的来源。**

    - **先理解跨域和同源**
        - **同源**：协议 + 域名 + 端口都相同才算同源。`http://a.com` 和 `https://a.com` 不同源（协议不同）、和 `http://a.com:8080` 不同源（端口不同）
        - **跨域请求**：前端页面（`http://a.com`）向后端（`http://api.b.com`）发请求，就是跨域
        - **同源策略**：浏览器只允许脚本读取同源响应，跨域响应默认被浏览器拦截（注意：请求可能已发出、服务器可能已处理，只是浏览器拦截了响应）

    - **两种请求类型（决定处理方式）**
        - **简单请求**：满足特定条件（`GET/HEAD/POST` + `Content-Type` 限定值 + 仅 `safelisted` 请求头） ——— 浏览器直接发送，服务器只需返回 `Access-Control-Allow-Origin`。注意 `Content-Type` 只有三个值算简单请求：`application/x-www-form-urlencoded`、`multipart/form-data`、`text/plain`（`application/json` 会触发预检）
        - **预检请求（Preflight）** ：非简单请求（如带 `Authorization` 头、`Content-Type: application/json`、`PUT/DELETE` 方法）——— 浏览器先发 `OPTIONS` 请求探测，服务器必须正确响应 `OPTIONS`（且必须返回 `2xx`）并返回允许的方法/头，才继续发真实请求
        - **`2024-2026` 新机制 `PNA`（`Private Network Access`）** ：`Chrome` 从 `2024` 年底（`localhost` 场景 `Chrome 130`、内网 `RFC1918` 场景 `Chrome 137`）起，对"公网页面 → 内网/本地"的请求无条件强制预检——无论方法、无论 `mode`（甚至 `no-cors` 和同源请求），新增 `Access-Control-Request-Private-Network: true` /  `Access-Control-Allow-Private-Network: true` 头。`2025-2026` 年"莫名 `OPTIONS`_预检/请求被拦"很大比例是 `PNA` 导致

    - **解决方案**：后端加 `CORS` 响应头（根本解法）
        - **在服务器响应中加**：
            - `Access-Control-Allow-Origin: http://a.com`（允许的来源）
            - `Access-Control-Allow-Methods: GET, POST, PUT, DELETE`（允许的方法）
            - `Access-Control-Allow-Headers: Content-Type, Authorization`（允许的请求头）
            - `Access-Control-Allow-Credentials: true`（允许携带凭证，值为字面量 `true`；配了它就不能用 `*`）
            - `Access-Control-Max-Age: 3600`（预检结果缓存时间，减少 `OPTIONS` 请求）
            - `Access-Control-Expose-Headers`（可选）：默认前端只能读 `6` 个简单响应头，自定义响应头需在此列出

        - **配置位置**：
            - `Nginx：add_header Access-Control-Allow-Origin ...`
            - `Spring Boot`：`@CrossOrigin` 注解或全局 `CORS` 配置
            - `Node.js：cors` 中间件

        - **注意通配符的坑**：`Access-Control-Allow-Origin: *` 允许所有来源，但不能和 `credentials`（携带 `Cookie`）一起用——用了 `withCredentials` 时不允许用 `*`，必须写具体来源

    - **解决方案**：`Nginx` 反向代理（同源化）
        - 前端和后端域名不同导致跨域时，用 `Nginx` 反代把 `/api` 转发到后端，前端只访问同源路径 ——— 从根上消除跨域
        - 配置示例：前端访问 `http://a.com/api`，`Nginx` 把 `/api` 代理到 `http://api.b.com`
        - 优点：不需要改后端代码、避免暴露多个 `CORS` 配置
        - 适用范围：前后端域名固定对应时是常见推荐方案；但 `API` 需被多个未知域名、第三方或移动端调用时（`SaaS`、开放平台），`CORS` 头方案才是唯一可行且是行业标配

    - **解决方案**：`JSONP`（历史遗留方案）
        - 利用 `script` 标签不受同源策略限制的特点，通过回调函数拿数据
        - 缺点：只支持 `GET`、安全性差（需要服务端配合）、已过时
        - 仅用于兼容老系统的特殊场景

- **协助记忆**
    - 一句话：跨域报错 = 服务器没声明允许这个来源访问
    - 两条路：后端加 `CORS` 头（改后端）或 `Nginx` 反代同源化（改架构）
    - 通配符的坑：`*` 和 `credentials` 不能同时用
    - 记住预检：带 `Authorization` / `JSON body` 的请求会先发 `OPTIONS` 预检，服务器要正确处理（`OPTIONS` 必须返回 `2xx`）

- **进阶思考**
    - **为什么有时候 `OPTIONS` 请求返回 `403`/`404`？**
        - 预检请求（`OPTIONS`）可能没被正确处理。常见原因：一是网关/防火墙拦截了 `OPTIONS` 方法（只放行 `GET`/`POST`）；二是后端没有对 `OPTIONS` 返回正确的 `CORS` 响应头（`Access-Control-Allow-Methods`/`Headers`）；三是 `Nginx` 的 `add_header` 默认只对部分 `2xx/3xx` 状态码添加（`200/201/204/206/301/302/303/304/307/308`），需加 `always` 参数才对所有响应码添加——且预检必须返回 `2xx`，`OPTIONS` 返回 `403` 时即使加了头也必然失败。排查：用 `curl` 手动发 `OPTIONS` 请求模拟预检，看响应头是否包含 `Allow-Methods/Headers` 且状态码为 `2xx`

    - **`Access-Control-Allow-Origin: *` 和 `withCredentials` 为什么冲突？**
        - 安全设计。如果服务器返回 `*` 允许所有来源，同时又允许携带凭证，任意站点就能代表用户跨域发带凭证请求并读取响应——比只写不读的 `CSRF` 更严重。所以浏览器规定：凭证请求下 `ACAO` 必须为具体来源（`*` 判失败），且要配合 `Access-Control-Allow-Credentials: true`。补充：现代浏览器 `Cookie` 默认 `SameSite=Lax`，跨站 `fetch/XHR`（非顶级导航）默认不携带 `Lax Cookie`，只有 `SameSite=None`; `Secure` 的 `Cookie` 会带上——实际攻击面比"任何恶意网站都能带上 `Cookie`"更小，但"带凭证 + 可读响应"的底线依然不能破

- **扩展信息**    
安全响应头是 `Nginx` 层可以统一配置的 `Web` 安全防护。核心是 `CSP`（`Content-Security-Policy`，内容安全策略）——— 它告诉浏览器"页面允许加载哪些来源的资源"，`CSP` 是浏览器侧缓解 `XSS` 最重要、最有效的防御机制之一，但不能替代输入校验（`Sanitization`）与输出编码（`Encoding`）等后端/开发层面的安全措施。
    - **`CSP` 是什么**
        - `CSP` 通过响应头声明"允许加载什么"，浏览器强制执行：不符合策略的资源（脚本、图片、样式、iframe）被阻止加载
        - 缓解 `XSS` 原理：即使攻击者注入 `<script>`，`CSP` 如果没允许内联脚本/不可信来源，浏览器会拒绝执行
        - `CSP` 的威胁模型：缓解内容注入（`XSS`）与页面被恶意嵌入（`clickjacking`——注意这是 `UI redress` 而非"内容注入"）
    - **`CSP` 常用指令**
        - **`default-src`**：大多数 `fetch` 类指令的兜底策略。但需注意：`frame-ancestors`、`base-uri`、`form-action`、`sandbox` 等指令不会继承 `default-src`（未指定即默认为无限制）。
        - **`script-src`**：脚本来源（最关键的指令）
        - **`style-src`**：样式来源
        - **`img-src`**：图片来源
        - **`connect-src`**：`XHR/fetch/WebSocket` 连接来源（注意它和 `CORS` 是两层不同机制——`CSP` 是浏览器端限制，`CORS` 是服务端授权）
        - **`frame-ancestors`**：允许哪些来源嵌入本页（防点击劫持，取代 `X-Frame-Options`）
        - **`object-src`**：`<object>/<embed>` 来源（建议 'none'）
        - **base-uri**：`<base>` 标签来源（限制可被篡改的基准 `URL`）；`form-action`：表单提交目标
        - **`upgrade-insecure-requests`**：页面内 `HTTP` 请求自动升级为 `HTTPS`
        - **`report-to`**：违规上报（`report-uri` 已废弃，用 `Reporting-Endpoints` + `report-to` 替代）
        - **源关键字**：`'self'`、`'none'`、`'unsafe-inline'`（不推荐）、`'unsafe-eval'`（不推荐）、`'strict-dynamic'`（注意：`strict-dynamic` 应与 `nonce` 或 `hash` 一起使用。在支持该特性的浏览器中，会忽略传统脚本白名单（如 `'self'`、主机白名单），改由受信任脚本继续加载其他脚本。）、`'nonce-xxx'`（每次响应随机生成）
    - **`CSP` 配置案例**
        - **基础案例（静态站）** ：`Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'`
        - **进阶案例（带 `CDN` 和 `API`）** ： `Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src 'self' data: https://cdn.example.com; connect-src 'self' https://api.example.com; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'`
        - **上线前先开报告模式（只报告不拦截）**：`Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self' Reporting-Endpoints: csp-endpoint="https://example.com/csp-violations" Content-Security-Policy-Report-Only: ...; report-to csp-endpoint`； 注意两个坑：`Report-Only` 模式下 `frame-ancestors` 不生效（既不拦截也不产生报告）；若未配置 `report-to/report-uri`（或 `Reporting-Endpoints`），`Report-Only` 依然在浏览器端生效：它会在开发者工具控制台输出违规警告，并可被前端 `securitypolicyviolation` 事件捕获，只是不会向服务器发送 `HTTP` 违规上报包。
        - **`Nginx` 配置 `CSP`**: `add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;`
            - **`always` 必须加**：`add_header` 默认只对 `200/201/204/206/301/302/303/304/307/308` 生效，加 `always` 才对所有响应码生效（错误页也带 `CSP`）
            - **`add_header` 继承坑**：子 `location` 写了 `add_header` 会覆盖父级全部（`nginx 1.29.3+` 可用 `add_header_inherit merge`; 改为追加）
        - **`Nginx` 其他安全头（一起配，注意与 CSP 语义一致）**
            ```ini
            add_header X-Content-Type-Options "nosniff" always;
            add_header X-Frame-Options "DENY" always;             # 与 frame-ancestors 'none' 配套
            add_header Referrer-Policy "strict-origin-when-cross-origin" always;
            add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;  # 仅 HTTPS 站点
            ```
      - **策略一致性**：`CSP` 的 `frame-ancestors 'none'` 应配 `X-Frame-Options "DENY"`（或 `'self' + SAMEORIGIN`），两者语义要一致——不要出现"`CSP` 禁止嵌入、`XFO` 允许同源嵌入"的自相矛盾
      - **`HSTS` 的坑**：`includeSubDomains` 要求子域全部 `HTTPS`，否则误伤子域；`preload` 需去 `hstspreload.org` 提交后才生效
      - `X-Frame-Options` 有效值只有两档（`DENY/SAMEORIGIN`）——— `ALLOW-FROM` 已废弃且会导致整个头被浏览器忽略
    - **进阶思考**
      - **为什么 `'unsafe-inline'` 不推荐？`nonce` 和它什么关系？**
        - `'unsafe-inline'` 允许所有内联脚本执行——`XSS` 注入的正是内联脚本，等于开大后门。完全禁止内联脚本后，页面自带的 `<script>` 也会被拦截，需要移到外部 `.js` 或用 `nonce/hash` 白名单。在必须用内联脚本时，`nonce` 远比 `'unsafe-inline'` 安全、且比完全禁止更灵活 ——— `nonce` 每次响应随机生成，攻击者无法预知。实操坑：`nonce` 每次响应都变，页面被浏览器/`CDN` 缓存后 `nonce` 过期、脚本被误杀——这是高频事故，缓存页面慎用 `nonce`
      - **`CSP` 和 `X-Frame-Options` 都能防点击劫持，用哪个？**
        - `frame-ancestors（CSP）`是现代推荐、`X-Frame-Options` 是旧方案。`frame-ancestors` 更灵活（可精确指定多个来源），`X-Frame-Options` 只有 `DENY/SAMEORIGIN` 两档。两者同时存在时，`CSP` 优先（仅在 `enforce` 模式成立；`X-Frame-Options` 在响应含 `frame-ancestors` 时被忽略）。为兼容老浏览器可两者都配，但语义必须一致


## 🤔 简述 CDN 工作原理？
- **`CDN`（`Content Delivery Network`，内容分发网络）的核心思想：把内容缓存到离用户更近的节点，让用户从"附近的服务器"而不是"远方的源站"获取内容。本质是"空间换时间"——用分布在全国/全球的缓存节点，换取更短的访问延迟。**

    - **`CDN` 的核心组件**
        - **边缘节点（`Edge Node` / `POP`，`Points of Presence`）** ：分布在各地区/运营商的缓存服务器，真正响应最终用户请求
        - **中心节点 / 源站（`Origin`）** ：内容的原始来源（你的服务器）
        - **`GSLB`（`Global Server Load Balancing`，全局负载均衡）** ：负责把用户引导到"最合适"的边缘节点——通常以智能 `DNS` 方式实现，根据用户地理位置、所属运营商、节点负载，返回最优边缘节点 `IP`
        
    - **`CDN` 工作原理流程（一次访问）**
        1. 用户访问 `http://www.example.com`，发起 `DNS` 解析
        2. 智能 `DNS` / `GSLB` 调度：`DNS` 服务器根据用户所属网络的递归解析器出口 `IP` 判断地理位置和运营商，返回就近的边缘节点 `IP`（北京用户解析到北京节点，电信用户到电信节点。严格说 `DNS` 默认看到的是递归解析器出口 `IP`，启用 `EDNS Client Subnet（RFC 7871）`才能获知用户真实 `IP` 前缀）
        3. 用户请求打到边缘节点
        4. 边缘节点判断缓存：
        - 命中：边缘节点有缓存且未过期，直接返回
        - 未命中：边缘节点回源——向源站请求内容，缓存一份后返回（实际大型 `CDN` 是多级缓存 `L1` 边缘/`L2` 区域/`L3` 中心，边缘未命中常先查上层节点而非直接回源）

        5. 后续同地区用户请求同一内容，直接命中边缘节点缓存

    - **`CDN` 缓存与回源的关键点**
        - **``TTL`（缓存有效期）`** ：由源站响应头控制 ——— `Cache-Control: s-maxage`（共享缓存专用，优先于 `max-age`）、`max-age`、`HTTP/1.0` 的 `Expires`；也可在 `CDN` 平台配置"强制缓存"忽略源站头
        - **`revalidation`（304 校验）** ：缓存未命中时，边缘节点先发 `If-Modified-Since/ETag` 条件请求回源，源站返回 `304` 则复用缓存、不传 `body`——这是缓存回源的重要环节
        - **缓存刷新**：源站内容更新后，需主动刷新（`purge`）`CDN` 缓存或等 `TTL` 过期
        - **回源率 / 缓存命中率（Cache Hit Ratio）** ：衡量 `CDN` 效率的关键指标——命中率越高，源站压力越小

    - **`CDN` 的作用**
        - **加速访问**：用户从就近节点拿内容，延迟大幅降低（尤其跨地域、跨境场景）
        - **减轻源站压力**：大部分请求被边缘节点缓存吸收，源站只处理未命中请求
        - **抗 `DDoS`**：用海量带宽池（`Anycast`）吸收流量型攻击、用 `WAF` 与限速过滤应用层（`CC`）攻击，同时隐藏源站 `IP` 使其无法被直接打击
        - **隐藏源站 `IP`**：用户只和边缘节点通信，源站 `IP` 降低暴露面（需配合回源 `IP` 白名单）

    - **现状与前沿（2026 年）**
        - **已是标配（成熟多年）** ：`HTTP/3`（`QUIC 2021` 标准化、`HTTP/3 2022` 标准化，主流 `CDN 2019` 年已商用，如今是标配）；`CDN` 证书托管/自动续期（用户侧边缘证书 + 源站回源证书是两个独立维度，可选 `mTLS`）；基础边缘计算（`Cloudflare Workers 2017`、`Lambda@Edge 2017`）
        - **当前热点（2023-2026）** ：边缘 `AI` 推理（`Workers AI`、`GPU` 边缘节点、`AI Gateway`）、边缘无服务器渲染/`SSR`（`Vercel`/`Netlify edge runtime`）、边缘 `KV`/数据库（`Durable Objects` 等）、`SASE`/零信任与 `CDN` 融合（`Cloudflare Zero Trust` 等）

- **协助记忆**
    - 一句话：`CDN` = 把内容放到离用户近的地方，就近取货
    - 三个角色：边缘节点（附近的分店）、源站（总仓库）、GSLB（导购员，告诉你哪家分店近）
    - 类比快递：CDN 是"提前把货放到你楼下的便利店"，用户不用每次去总部仓库取

- **进阶思考**
    - **`CDN` 一定能提升访问速度吗？什么场景反而更慢？**
        - 不一定。`CDN` 加速依赖"就近取货"——纯动态内容（用户专属页面、实时数据）不适合缓存（核心原因是缓存收益低/需个性化，不完全是"缓存了必然旧数据"，一致性可由 TTL 控制）；小体量站点用 `CDN` 可能增加 `DNS` 解析和节点跳转额外开销；跨运营商调度不当反而更慢。适合 `CDN` 的是：静态资源（图片、CSS、JS、视频）、大文件下载、`API` 只读数据。补充：现代 `CDN` 有动态加速（`DSA`） ——— 不缓存，但通过优化 `TCP/TLS`、智能路由选路来加速动态请求

    - **`CDN` 隐藏源站 `IP` 就一定安全吗？**
        - 不是绝对的。`CDN` 隐藏源站 `IP` 是"降低暴露面"，不是"绝对隐藏"——攻击者仍可能通过：源站直接对外解析的历史 `DNS` 记录、邮件头/证书透明度日志、源站自身的外发请求、子域爆破等找到真实 `IP`。真正加固需要：源站只允许 CDN 回源 `IP` 访问（回源 `IP` 白名单）、关闭源站不必要的对外端口。`CDN` 隐藏 `IP` 只是多层防护中的一层

## 🤔 四层与七层负载均衡有什么区别？	
- **四层（`L4`）和七层（`L7`）负载均衡工作在网络模型的不同层级，核心区别：`L4` 在传输层（`TCP/UDP`）基于 `IP`+端口转发，`L7` 在应用层（`HTTP`）基于内容路由。**
    - **四层负载均衡（`L4`）**
        - **工作层级**：传输层（`OSI` 第 `4` 层），基于 `IP` + 端口转发，不解析应用层内容（需解析 `TCP/UDP` 头）
        - **代表产品**：`LVS`、`HAProxy`（`tcp` 模式）、云厂商 `NLB`、`Katran`（`Meta` 开源，`XDP/eBPF` 转发面） ；`F5 BIG-IP LTM` 是 `L4`–`L7` 一体化 · 设备（不能仅归 `L4`）；`Nginx` 的 `stream` 模块也能做 `L4`
        - **转发方式**：`NAT`、`DR`（`DSR`，直接服务器返回——返回流量不经过 `LB`）、隧道（`LVS` 三模式）
        - **特点**：
            - **性能高**：内核态或硬件转发，权威性能指标是 `pps`（每秒包数）而非并发连接数
            - **透明**：看不到 `HTTP` 内容
            - **无法做**：`URL` 路由、内容改写、`Cookie` 会话保持（传统上基于源 `IP`/五元组哈希——注意标准术语是五元组：源/目的 `IP`+端口+协议，`Katran` 即用 `5-tuple` 一致性哈希）

        - **TLS**：传统软件 `L4`（`LVS`、`nginx stream` 透传、`HAProxy tcp passthrough`）只能透传，不能终止；但现代云 `L4`（`AWS`/阿里云 `NLB` 等，`2019` 年起）支持 `TLS` 监听器/终止（多在网卡/硬件卸载，`ACM` 管理证书）；另有灰色地带——`L4` 可只读 `TLS ClientHello` 的 `SNI` 做路由而不解密（`nginx stream ssl_preread`）
        - **健康检查**：传统 `LVS` 是 `TCP` 端口探测；现代 `L4`（`NLB` 等）同样支持 `HTTP`/`HTTPS` 健康检查与响应码匹配
        - 适用：高吞吐场景、`TCP/UDP` 通用协议、作为七层的入口

    - **七层负载均衡（`L7`）**
        - **工作层级**：应用层（`OSI` 第 `7` 层），解析 `HTTP` 内容
        - **代表产品**：`Nginx`、`HAProxy`（`http` 模式）、`Envoy`/`Traefik`（云原生/服务网格默认 `L7`）、云厂商 `ALB`
        - **特点**：
            - 功能丰富：`URL` 路径路由、`Header` 改写、`Cookie` 会话保持、限流、缓存、`TLS` 终止、`gRPC/HTTP3` 支持
            - 性能低于 `L4`（需解析报文）
            - 能感知应用状态（基于应用层健康检查）

        - **会话保持**：基于 `Cookie`
        - **健康检查**：`HTTP` 探测（检查具体 `URL` 的响应码，能感知应用是否真的可用）
        - **适用**：`HTTP/HTTPS` 业务、需要路由/改写的场景

    - **关键区别对比**
        - **工作层级**：传输层 vs 应用层
        - **性能**：`L4` 高（`pps` 指标）vs `L7` 低（需解析）
        - **功能**：`L4` 简单转发 vs `L7` 内容路由/改写
        - **`TLS` 终止**：传统 `L4` 只能透传，现代云 `L4` 支持 vs `L7` 原生支持
        - **会话保持**：`L4` 传统基于五元组 vs `L7` 基于 `Cookie`
        - **健康检查**：`L4` 传统 TCP 探测 vs `L7 HTTP` 探测（现代 `L4` 也支持 `HTTP`）
        - **典型代表**：`LVS/Katran` vs `Nginx/Envoy`

    - **常见架构组合**
        - **大型架构**：`L4`（入口，扛高并发）→ `L7`（应用路由）→ 后端服务器
        - **经典组合**：`LVS` /` Katran`（四层）→ `Nginx` / `Envoy`（七层）→ 应用

- **协助记忆**
    - 一句话：`L4` 是"快递分拣员"（只看地址不分内容），`L7` 是"前台接待员"（听你需求再安排）
    - `L4` 管"到不到"（`IP`+端口转发），`L7` 管"给谁"（`URL/Host` 路由）——仅为简化助记，严格说 `L4` 也做健康检查、`L7` 路由同样基于 `IP`+端口
    - 选型：只要转发选 `L4`，要路由/改写/TLS 终止选 `L7`，大流量先用 `L4` 挡一层

- **进阶思考**
    - **`L4` 能做 `TLS` 终止吗？**
        - 分情况。传统软件 `L4`（`LVS`、`nginx stream` 透传、`HAProxy tcp passthrough`）不能 ——— `TLS` 终止需要解密报文，`L4` 只转发加密 `TCP` 流，只能透传到后端解密。现代云 `L4`（`AWS`/阿里云 `NLB` 等，`2019` 年起）支持 `TLS` 监听器，多在网卡/硬件上卸载加解密。所以"`HTTPS` 业务 + `TLS` 终止"传统选 `L7`（`Nginx/ALB`），但现代架构也可以用支持 `TLS` 的云 `L4` 做入口

    - **四层负载均衡的性能优势有多大？**
        - `L4` 权威指标是 `pps`（每秒包数）——— `Katran` 等 `XDP/eBPF` 方案在 `DPDK`/网卡卸载下可达千万级 `pps`；`L7`（`Nginx`）通常几万到几十万 `QPS`（受 `HTTP` 解析和 `TLS` 开销限制），数字随硬件/配置浮动。差距来自：`L4` 不解析应用层内容、`L7` 要解析 `HTTP` 报文且常做 `TLS` 终止。注意量纲：`L4` 的"百万级"通常是并发连接数/`pps`，`L7` 的"几万"是 `QPS` ——— 两者不是同一个量纲，对比要说清楚

## 🤔 网站报错 502 Bad Gateway 怎么解决？
- **``502 Bad Gateway`` 的含义：网关/反向代理（`Nginx`）成功收到了请求，但从上游（`upstream`）没有得到有效响应——即"请求到了 `Nginx`，但 `Nginx` 没能从后端拿到结果"。定位核心：先区分是 `Nginx` 自己产生的 `502`，还是后端返回的 `502`，再看 `error log` 定位。**

    - **第一步**：区分 `502` 是谁产生的
        - **后端自己返回 `502`**：`Nginx` 只是透传 ——— `error log` 无记录，`access log` 里 `$upstream_status=502`（此时去后端日志查）
        - **`Nginx` 产生 `502`**：`error log` 有 `upstream` 相关记录（此时按下面的 `error log` 信息定位）
        - **判断方法**：`tail -f /var/log/nginx/error.log` 看有没有报错，再看 `access log` 的 `upstream_status`

    - **`502` 的本质与 `504` 的区别**
        - **`502 Bad Gateway`**：上游返回了无效响应 / 连接没建立起来（后端挂了、端口不通、协议错误）
        - **504 Gateway Timeout**：网关等待上游响应超时（后端还在处理但太慢）
        - **一句话**：`502` 是"拿不到结果"，`504` 是"等太久"

    - **常见原因与排查路径**（按 `error log` 信息定位）
        1. **后端服务未启动 / 挂了 / `upstream` 全不可用**：`connect() failed (111: Connection refused)` 或 `no live upstreams while connecting to upstream`
            - **后者触发机制**：`upstream` 所有 `server` 被判不可用（`max_fails/fail_timeout`，默认 `1` 次/`10s`）、全部 `down/backup`、或域名解析失败（`host not found in upstream`）
            - **检查**：`systemctl status <服务>`、`ss -lntp | grep <端口>`

        2. **后端端口不通（防火墙/安全组/监听地址）** ：`refused` 或 `timeout`
            - **检查**：后端进程监听 `127.0.0.1` 还是 `0.0.0.0`？防火墙/云安全组是否放行？跨机房网络是否通？
            - **注意**：防火墙 `REJECT` 策略也表现为 `Connection refused`，不一定是没监听

        3. **连接超时**：`connect() failed (110: Connection timed out)` ——— 网络层不通（防火墙 `DROP` 丢包、跨机房、安全组）
        4. **后端处理超时**：`upstream timed out (110: Connection timed out) while reading response header from upstream`
            - 后端接口处理太慢，超过 `proxy_read_timeout`（默认 `60s`）
            - **解决**：调大 `proxy_read_timeout`，或优化后端接口性能
            - **注意**：`errno 110` 出现阶段不同含义不同——建连阶段是网络不通，读响应阶段是后端太慢

        5. **响应头无效 / 过大**：`upstream sent invalid header while reading response header from upstream`（或 `sent too big header`）
            - 后端返回畸形/空响应头，或响应头超过 `proxy_buffer_size`（默认 `4k/8k`，超限判为无效响应）——生产环境 `502` 头号来源之一
            - 解决：修后端响应头问题，或调大 `proxy_buffer_size/proxy_buffers`

        6. **`HTTPS/TLS upstream` 场景**：`SSL_do_handshake() failed`、`upstream SSL certificate verify error`
            - `proxy_ssl_verify on` 时证书过期/`SNI` 不匹配/自签证书
            - 解决：更新证书或按需关闭校验

        7. **`PHP-FPM` 常见场景（经典）** ：
            - **`Unix socket` 路径不存在**：`connect() failed (2: No such file or directory) while connecting to upstream` ——— `sock` 路径写错、`fpm` 没启动
            - **`socket` 权限不足**：`connect() failed (13: Permission denied)——listen.owner/listen.group/listen.mode`（默认 `0660`，连接需要读/写权限）
            - **`worker` 进程耗尽**：`fpm` 日志报 `server reached pm.max_children` ——— `pm.max_children` 太小，调大或调优 `pm` 模式
            - **`PHP` 代码问题**：`fpm` 正常返回 `500` → `Nginx` 透传 `500`；`fpm` 进程崩溃/连接中断 → `Nginx` 报 `prematurely closed` 产生 `502` ——— 看 `fpm` 的 `error log` 区分

        8. **连接被对端提前关闭**：`upstream prematurely closed connection while reading response header from upstream`
            - **最高频场景**：后端进程崩溃/重启（`PHP-FPM reload`、`Java OOM kill`、容器 `Pod` 重启）
            - 一般 `Nginx` 会按 `proxy_next_upstream`（默认 `error timeout`）自动重连/换后端，无需人工 `reload`；若配置了 `keepalive` 且后端频繁重启，可调小 `keepalive` 数

        9. **后端连接数满 / 队列溢出**：`refused` 或 `timeout`（取决于 `tcp_abort_on_overflow`）+ 后端日志报 `accept queue` 满——后端负载过高
        10. **资源耗尽**：后端内存不足（`OOM`）、文件描述符耗尽——检查 `dmesg | grep -i oom`、`ulimit -n`
        11. **容器/K8s 场景（2026 年高频）** ：`Pod` 重启/滚动更新、`Service` 无 `Endpoint`、`DNS` 解析失败（`no resolver defined to resolve`）、网络策略未放行 `Pod IP`

    - **排查步骤（应急 → 根治）**
        1. **先看 `Nginx error log` + `access log`**：`tail -f /var/log/nginx/error.log`（区分透传 `502` 还是自身 `502`，再定位 `upstream` 错误类型）
        2. **本地验证后端**：`curl -v http://127.0.0.1:<端口>/`（后端本身通不通）
        3. **检查后端进程与日志**：`systemctl status <服务>`、`journalctl -u <服务> -n 50`
        4. **检查网络层**：`telnet <后端IP> <端口>`（连通性）、防火墙/安全组
        5. **检查资源**：内存（`free -h`）、`fd`（`ulimit -n`、`ss -s`）
        6. **定位后修复**：重启服务、调超时、调 fpm 参数、改权限、扩容

- **协助记忆**
    - **一句话**：`502` = 网关替后端"背锅"，后端没给它结果
    - **三步定位**：先看 `error log`（区分透传）→ 再 `curl` 后端 → 最后查服务状态
    - **记忆口诀**：`refused` = 连接被拒（没监听或防火墙 `REJECT`）；`timeout` = 网络丢包不通（`DROP`）或后端太慢；`prematurely closed` = 连接被对端掐断（后端重启/崩溃）

- **进阶思考**
    - **`502` 和 `503` 有什么区别？**
        - `502` 是"网关拿到了请求、但上游没给出有效响应"（后端挂了、超时、端口不通、响应头无效）；`503 Service Unavailable` 是"服务暂不可用"——通常是主动拒绝（负载过高限流、维护模式、健康检查不过被摘除）。`Nginx` 场景：`upstream` 全部无健康节点时可能返回 `502` 或 `503`（取决于配置），两者都要查后端

    - **后端明明在跑，为什么还 502？**
        - 最常见三个：① 监听地址不对——进程活着但监听 `127.0.0.1`，`Nginx` 从别的 `IP`/容器连不上；② 端口不通——防火墙/安全组没放行；③ 处理超时——后端在跑但响应太慢超过 `proxy_read_timeout`。所以"进程活着"不等于"能访问"，本地 `curl` 才是最直接的验证


## 🤔 网站 QPS 突降，如何快速定位问题？	
- **`QPS` 突降本质只有两类：来的人（流量）少了 或 能处理的人（能力）少了。快速定位的核心方法论：先验监控数据真伪 → 分层对比 + 时间线对齐 → 用"`QPS/RT`/错误码"组合区分故障类型。**

- **第 0 步**：先确认监控数据是真的
    - `2026` 年可观测性场景，第一排查项是采集端故障 ——— `agent` 挂掉、指标上报中断、`Prometheus target` 失联、面板采样问题，造成"假突降"
    - 检查：`target up` 状态、数据是否连续、对比两个监控源、直接看 `access log` 数一下真实请求量

- **第一步**：入口流量下降，还是后端处理能力下降？
    - **看入口层 `QPS（LB/CDN）`** ：
        - 入口 `QPS` 也同步下降 → 流量没进来（`DNS`/`CDN`/攻击/上游调度/业务自然下降）
        - 入口 `QPS` 正常、应用层 `QPS` 下降 → 流量进来了但处理不了（后端故障、限流、连接池）

    - **注意**：入口掉先对比历史同期/同比——业务流量自然减少（活动结束、反爬拦截、投放停止）是最常见的原因，别一上来就当故障

- **第二步**：用"`QPS` / `RT` / 错误码"三信号组合区分故障类型
    |                         信号组合                         |  故障类型  |               典型原因               |
    | :------------------------------------------------------: | :--------: | :----------------------------------: |
    | 应用 `QPS` 降 + 错误码升（`429/503/5xx`）+ `RT` 平稳或降 | 快速失败型 | 限流、熔断、连接池拒绝、健康检查摘除 |
    |           应用 `QPS` 降 + `RT` 升 + 错误码平稳           | 排队积压型 | 死锁、慢查询、`GC` 停顿、线程池占满  |
    |              入口与应用 `QPS` 同时断崖归零               |   入口型   | `DDoS` 黑洞、`DNS` 故障、`CDN` 故障  |
    |       入口正常 + 缓存命中率骤降 + `DB` 层负载飙升        |  缓存雪崩  |   缓存大批量过期，请求打穿到 `DB`    |

    - 关键点：快速失败的请求 RT 很短；RT 上升恰恰说明请求在排队等待而非快速失败——两者不要混淆

- **第三步**：分层对比——流量在哪一层掉的？
    - 链路：`CDN/LB`（入口）→ `Nginx` → 应用 → 数据库/缓存
    - 对比各层 `QPS` 监控曲线：哪一层先掉、掉多少
    - 入口掉 → 查 `DNS`/`CDN`/攻击/业务自然下降
    - 入口正常、应用掉 → 查应用（发布、限流、死锁、连接池）
    - 应用正常、`DB` 掉 → 查数据库（慢查询、锁、连接数）

- **第四步**：对齐时间线——那个时间点发生了什么？
    - **发布/变更**：服务发布、配置变更、数据库迁移、证书更新/过期——先看是否有发布
    - **告警**：监控告警、电话告警
    - **外部事件**：`DNS` 变更、`CDN` 故障、机房故障、攻击

- **常见原因清单**（按可能性，`2026` 视角）
    - **入口类**：
        - 业务流量自然减少（活动结束、反爬拦截、投放停止）——最常见但最容易漏
        - `DNS` 解析故障/变更：解析到错误地址、`DNS` 污染
        - `CDN` 故障/回源失败：`CDN` 节点异常、回源配置错误
        - `DDoS` 攻击触发黑洞：流量被运营商黑洞封禁，直接 `QPS` 断崖归零（注意黑洞前通常有 `bps/pps` 流量突增前兆；黑洞若发生在运营商侧，`LB` 层 `QPS` 也会归零）
        - `WAF` 误拦：误封 `IP`、规则误触发导致流量被拦

    - **应用类**：
        - **发布/回滚**：新版本有 `bug`，连接池/线程池耗尽
        - **`K8s`/容器场景（2020s 最高频）** ：`Pod` 驱逐/`OOMKilled`、`HPA` 缩容、滚动发布副本不足、`liveness/readiness` 探针失败摘流量、`Ingress/CoreDNS` 故障、`NetworkPolicy/CNI` 异常
        - **`HTTPS` 证书过期**：`TLS` 握手大面积失败，`QPS` 断崖——极高发根因
        - **死锁/卡死**：应用线程全部阻塞，请求排队
        - **连接池耗尽**：应用连不上 `DB`/缓存，请求快速失败
        - **限流/熔断触发**：限流规则配置错误、熔断阈值过低——注意熔断被触发是保护在起作用，问题通常是阈值/判定窗口配置不当，属"正常触发但配置不当"

    - **缓存/存储类**：
        - **缓存雪崩**：缓存大批量过期，请求全部打到 `DB` ——— 初期特征是缓存命中率骤降 + `DB` 层 `QPS`/负载飙升 + `RT` 飙升，对外吞吐下降是 `DB` 过载后的结果，不是判断特征

    - **资源类**：`CPU` 打满、内存 `OOM`、磁盘满
    - **数据库类**：慢查询、锁等待、连接数打满

- **快速排查命令**
    1. **看监控大盘**：各层 `QPS`、错误码、`RT`、缓存命中率曲线（先确认数据真实，再定位掉在哪一层）
    2. **看错误码分布**：统计 `access log` 状态码（先 `head -1` 确认字段位置 —— `$9` 只在默认 `combined` 格式下是状态码，自定义 `log_format` 后要换字段序号）
        ```bash
        head -1 /var/log/nginx/access.log
        awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
        ```
    3. **看后端资源**：`top`、`vmstat 1`、`free -h`、`iostat -x 1`
    4. **看连接**：`ss -s`、`ss -lntp`（`-p` 需 `root`）
    5. **看数据库**：`show processlist`;（`MySQL 8` 建议查 `performance_schema`/`sys` 库）、慢查询日志
    6. **看错误日志**：`journalctl -u <服务> -n 200`（仅 `systemd` 场景）

- **协助记忆**
    - **一句话**：`QPS` 突降 = 要么没人来了（入口问题），要么来了也处理不了（后端问题）
    - **四步定位**：先验监控真假 → 看入口 `QPS` 分内外 → 用 `QPS`/`RT`/错误码三信号分型 → 对齐时间线找事件
    - **口诀**：快速失败看错误码，排队积压看 `RT`，缓存雪崩看命中率，入口断崖看黑洞

- **进阶思考**
    - **`QPS` 突降和 `QPS` 不变但 `RT` 飙升，是一回事吗？**
        - 不是。`QPS` 突降通常是入口流量减少或后端快速失败（限流/熔断/连接池拒绝——这些失败请求 `RT` 很短）；`QPS` 不变/略增 + `RT` 飙升是典型的排队积压（资源打满、死锁、慢查询、`GC` 停顿）——请求堵在处理队列里，吞吐上不去。最迷惑的组合是"`QPS` 降 + `RT` 升"：这是排队积压型（请求变慢、还没失败），不是快速失败型，两者排查方向完全不同

    - **限流规则配错了，怎么快速确认？**
        - **两个特征**：① `QPS` 断崖式下降到限流阈值附近并稳定——比如配了 `100 QPS` 限流，`QPS` 恰好稳定在 `100` 附近，基本就是限流；② 错误码特征 ——— `Nginx limit_req` 默认拒绝返回 `503`（`limit_req_status` 可改为 `429`），看到大量 `503/429` 且 `QPS` 压线稳定，可确认。熔断同理：看熔断器状态指标（open 状态、错误率口径）


## 🤔 简述 HTTPS 工作原理？
- **`HTTPS` = `HTTP over TLS`。一句话：在 `TCP` 之上加了一层 `TLS`（传输层安全协议），用"混合加密"实现传输的三重保护——加密（防窃听）、完整性（防篡改）、身份验证（防冒充） 。核心难点在于：怎么在不安全的信道上安全地协商出密钥——这就是 `TLS` 握手解决的问题。**

    - **两个加密体系（混合加密）**
        - 非对称加密：握手阶段使用 ——— `RSA/ECDSA` 用于签名、`RSA` 加密（仅 `TLS 1.2`）、`ECDH` 用于密钥协商。慢
        - 对称加密（`AES-GCM`/`CHACHA20-Poly1305`）：双方用同一个密钥加解密。快，用于实际数据传输
        - 为什么混合：非对称加密慢，对称加密快但需要双方共享同一个密钥——TLS 握手就是用非对称加密安全地把对称密钥协商出来

    - **`TLS 1.2` 握手流程**
        1. **`ClientHello`**：客户端发送支持的 `TLS` 版本、加密套件列表、客户端随机数（`Client Random`）
        2. **`ServerHello`**：服务器选择 `TLS` 版本、加密套件，返回服务器随机数（`Server Random`）
        3. **`Certificate`**：服务器发送证书（含公钥和 `CA` 签名）
        4. **`ServerKeyExchange`**：服务器发送 `DH` 参数（使用 `ECDHE` 时）
        5. **`ServerHelloDone`**：服务器告知"我的消息发完了"
        6. **客户端验证证书（此时才验证）**：证书链完整、在有效期内、域名匹配（`SAN`）、（可选）吊销状态——浏览器默认不做实时吊销检查（`Chrome` 用 `CRLSets`、`Firefox` 用 `CRLite`，`OCSP` 自 `2023` 年起可选）
        7. **`ClientKeyExchange`**：客户端发送自己的 `DH` 公钥参数（或 `RSA` 加密的预主密钥）
        8. **生成会话密钥**：双方用随机数 + 预主密钥（`Pre-Master Secret`）通过 `PRF` 生成主密钥（`Master Secret`），再派生出会话密钥
        9. **`ChangeCipherSpec` + `Finished`**：双方确认开始用对称加密通信
        10. 之后所有数据用对称加密传输

    - **前向保密（`Forward Secrecy`）**
        - **核心思想**：即使服务器的长期私钥泄露，历史上抓包记录的数据也无法被解密
        - **实现方式**：`ECDHE`（临时 `DH`）——每次会话生成临时密钥对，会话结束即销毁；即使服务器长期私钥被窃，也无法推导出过去的会话密钥
        - **`RSA` 密钥交换没有前向保密**：私钥泄露 = 所有历史通信可解密（所以 `TLS 1.3` 移除了 `RSA` 密钥交换）

    - **证书与 CA（身份验证）**
        - 证书 = 公钥 + 域名 + 有效期 + CA 的数字签名
        - 验证链：客户端信任根 CA → 验证中级 CA 证书 → 验证服务器证书（信任链锚定）
        - 数字签名用非对称加密的"私钥签名、公钥验签"实现：CA 用私钥给证书签名，客户端用 CA 公钥验签——验签通过说明证书确实由该 CA 签发、未被篡改
        - 注意区分：传输完整性由 AEAD 的认证标签（对称 MAC）提供，不是数字签名——"签名防篡改"指的是 CA 给证书签名防证书被篡改

    - **`TLS 1.3` 的变化（2026 年主流）**
        - **握手更快**：`1-RTT`（一次往返完成握手，相比 1.2 的 2-RTT）；支持 0-RTT（会话恢复时首包即可发数据，但有重放风险）
        - **强制前向保密**：只保留 ECDHE 密钥交换，移除 RSA 密钥交换
        - **精简加密套件**：移除弱算法（RC4、CBC 模式、SHA-1），只保留 AEAD（`AES-GCM/CHACHA20-Poly1305`）
        - **握手消息精简**：`key_share` 并入 `ClientHello/ServerHello`（取代 `1.2` 的独立 `ServerKeyExchange/ClientKeyExchange` 消息），`ServerHello` 后双方即算出握手密钥；`ChangeCipherSpec` 被移除（仅保留为兼容性 `no-op`）

    - **`HTTPS` 全链路（一次访问）**
        1. `TCP` 三次握手建立连接
        2. `TLS` 握手协商出会话密钥（上述流程）
        3. 之后 `HTTP` 请求/响应全部用对称加密传输
        4. 连接关闭

    - **`2026` 年行业状态**
        - `TLS 1.3` 已是主流（2025 年约 84% 站点支持），`TLS 1.2` 仍兼容（降级兜底）
        - 证书有效期分阶段缩短：2025 年 4 月 CABF 通过 SC081v3 提案，2026 年 3 月起分阶段缩减，最终 2029 年 3 月降至 47 天（此前公开信任证书上限是 398 天，自 2020 年 9 月起）——— 自动化续期（Let's Encrypt ACME）成为刚需
        - `ECDSA` 证书占比上升（运算比 `RSA` 快，但 `Web PKI` 中 `RSA` 仍占绝大多数）

- **协助记忆**
    - 一句话：`HTTPS` = 先"锁匠换钥匙"（握手协商对称密钥），再"快递柜存件"（对称加密传输）
    - 三个保障：加密防偷看、认证标签防篡改、证书防冒充
    - 握手记忆：你好（ClientHello）→ 收到（ServerHello）→ 亮证件（证书）→ 我完了（ServerHelloDone）→ 验证件（验证证书）→ 换钥匙（密钥交换）→ 开锁（对称加密通信）

- **进阶思考**
    - **为什么说 `RSA` 密钥交换不安全，一定要 `ECDHE`？**
        - `RSA` 密钥交换下，客户端用服务器公钥加密预主密钥发给服务器——如果攻击者一直抓包记录流量，将来拿到服务器私钥，就能解密当年的预主密钥，从而还原所有历史会话。`ECDHE` 每次会话用临时密钥对，私钥泄露也无法回推历史会话——这就是前向保密。`2026` 年 `TLS 1.3` 已强制 `ECDHE`

    - **证书过期了，客户端为什么拒绝访问？**
        - 证书验证包含有效期检查——过期证书导致验证失败，浏览器显示"连接不安全"。这也解释了为什么证书有效期正在分阶段缩短到 47 天（2029 年前）后，自动化续期（ACME）是刚需：手动续期跟不上节奏，证书过期直接导致 HTTPS 大面积报错

## 🤔 如何设计高可用的 Web 服务架构？	
- **高可用的本质：消除单点 + 快速恢复。设计目标不是"不出故障"，而是"出了故障用户无感知或感知最小"。可用性指标：99.9%（一年宕机 < 8.76 小时）、99.99%（< 52.6 分钟）——不可用时间每差 10 倍，对架构的冗余和自动化要求指数级上升。**

    - **第一原则**：无状态化（`Stateless`）
        - 应用层无状态：每个请求不依赖本地存储，任何节点都能处理任何请求 ——— 这是水平扩展和故障转移的前提
        - `Session` 不存本地：外置到 `Redis`（或 JWT 无状态 token）
        - 有状态的组件（DB、Redis）单独部署，与应用解耦

    - **分层冗余**：每一层都要有多副本
        - **接入层**：`DNS` 多 `IP` / 多解析（智能 `DNS`）、`CDN`（静态加速 + 分散攻击流量；大流量 `DDoS` 需专门的云高防如阿里云 `DDoS` 高防/`AWS Shield`；静态 `CDN` 对动态 `API` 无效）
        - **负载均衡层**：L4（LVS/NLB）与 L7（Nginx/ALB）是分层串联关系（L4 入口 → L7 路由 → 应用），各层内部多实例——L4 主备 VIP 漂移、L7 多活，健康检查自动摘除故障节点
        - **应用层**：多副本部署（生产建议 ≥3 副本，如 3 个可用区各 1 — 2 副本时单 AZ 故障容量直接减半）、无状态水平扩展
        - **数据层**：主从复制（读写分离）、数据库集群、多副本备份
        - **缓存层**：Redis 主从 + 哨兵（或集群模式），避免单点

    - **故障转移与自我保护（失败处理）**
        - **健康检查**：LB 定期探测，故障节点自动摘除（TCP/HTTP 探测）
        - **超时控制**：所有外部调用必须设置超时（连接超时 + 读超时），防止故障蔓延
        - **重试有界**：重试要有次数上限和退避（指数退避 + 抖动），避免重试风暴放大故障
        - **熔断**：下游故障时快速失败（断路器打开），保护调用方自身的线程/连接资源不被耗尽，同时给下游恢复窗口（如 `Sentinel`、`Resilience4j`）
        - **限流降级**：流量超限时优先保核心业务（降级非核心功能），防止雪崩
        - **幂等设计**：接口幂等（重试不产生副作用），配合重试机制

    - **数据层高可用**
        - **主从复制**：一主多从，主挂自动切换（`Orchestrator`、`MySQL InnoDB Cluster` / `Group Replication`，或云 `RDS` 自带高可用 ——— 注意 `MHA` 已停止维护，不再推荐新用）
        - **读写分离**：读流量走从库，减轻主库压力；对一致性敏感的流量（写后立即读）仍走主库（主从延迟会导致"写后读"不一致）
        - **分库分表**：数据量超单库上限时水平拆分（但要权衡复杂度，通常先优化再分）
        - **备份与恢复**：定期全量 + `binlog` 增量，定期演练恢复（备份没演练过等于没有）
        - **容灾架构层级**：同城双活（机房级故障，`Active-Active`）→ 两地三中心（同城双活 + 异地灾备中心，灾备不承载流量）→ 异地多活/单元化（城市级故障，多城同时承载流量，复杂度极高）

    - **缓存高可用**
        - `Redis` 主从 + 哨兵自动故障转移，或 `Redis Cluster`
        - **防缓存雪崩**：过期时间加随机抖动、多级缓存（本地 + 分布式）
        - **防缓存穿透**：布隆过滤器、空值缓存
        - **防缓存击穿**：热点 key 加锁重建、永不过期 + 异步更新

    - **弹性伸缩**
        - **水平扩缩容**：云上 `AS`（`Auto Scaling`）、`K8s HPA`（按 `CPU`/内存/自定义指标）、`KEDA`（事件驱动）、`Cluster Autoscaler`/`Karpenter`（节点级）
        - **容量规划**：压测摸清单节点容量，预留 `buffer`（如 70% 水位告警）

    - **可观测性**
        - **监控**：指标（QPS、RT、错误率、资源）、日志、链路追踪（`Metrics/Logs/Traces` 三支柱，`Profiling` 作为第四信号已由 `OpenTelemetry` 推进）
        - **告警**：分级告警、值班响应
        - **故障演练（混沌工程）** ：主动注入故障（杀节点、断网络）验证架构韧性，如 `Chaos Mesh`、`Chaos Monkey`

    - **2026 年云原生实践**
        - K8s 多可用区部署（Pod 分布到不同 AZ，故障域隔离）
        - 服务网格（Istio，Ambient Mesh 模式已 GA——sidecar 非唯一形态）提供熔断/重试/超时（但引入复杂度，按需选）
        - 不可变基础设施（镜像部署，避免配置漂移）

- **协助记忆**
    - 一句话：高可用 = 消除单点（冗余）+ 快速恢复（自动化）
    - 口诀：无状态、多副本、超时熔断限流、备份演练
    - 比喻：高可用架构像"备用发电机"——平时用不到，停电时立刻顶上；设计目标是停电时用户家里的灯不闪

- **进阶思考**
    - **`99.9%` 和 `99.99%` 差多少？值不值得追求？**
        - 不可用时间相差 `10` 倍 ——— `99.9%` 一年允许 `8.76` 小时宕机，`99.99%` 只有 `52.6` 分钟。每多一个 `9`，成本不是线性而是指数增长：需要更多冗余（多机房多活）、更完善的自动化（自动故障转移）、更快的恢复（分钟级）。中小业务 `99.9%` 通常够用；金融/交易类才需要 `99.99%+`。设计时先问业务：宕机 `1` 小时损失多少？

    - **为什么说"异地多活"是大厂才做的？**
      - 异地多活不只是"多部署几份"——它要求单元化：流量按用户维度路由到固定单元、数据按单元分片、跨单元数据一致性靠异步同步，还有 "全局路由 + 冲突解决"等难题。复杂度是数量级上升，投入产出比只有超大流量和强合规场景才划算。注意区分：两地三中心 ≠ 异地多活——两地三中心通常是"同城双中心双活 + 异地灾备中心（不承载流量）"，异地多活要求多个城市节点同时承载流量。中小企业优先做同城双活 + 快速恢复

## 🤔 秒杀系统，怎么优化？
- **秒杀的本质挑战：瞬时超高并发 + 有限的库存 + 不能超卖。核心设计思想一句话：流量削峰、分层过滤、异步化、限流保护、防超卖——把"百万请求同时打到数据库"变成"百万请求被层层拦截，只有极少数真正到数据库"。**

    - **第一层**：前端/页面优化（把流量挡在最外面）
        - **页面静态化 + `CDN`**：秒杀页面预生成静态页，用户浏览不占后端资源
        - **按钮置灰 + 倒计时**：秒杀开始前按钮不可点，减少无效请求
        - **人机校验（验证码/滑块）** ：拦截脚本刷单（反作弊，防止秒杀器）
        - **注意**：前端校验可被脚本/抓包绕过，必须后端同样限流——前端校验只是减负，不是安全边界

    - **第二层**：接入层优化（网关/Nginx）
        - **网关限流**：按 IP/用户/接口限流（令牌桶），超限直接返回"拥挤"
        - **`WAF` 防刷**：拦截恶意流量
        - **`Nginx` 层快速失败**：秒杀接口在接入层就丢弃大部分流量（能挡在前面就不放进来）

    - **第三层**：应用层优化
        - **本地缓存热点**：秒杀商品信息（名称、价格、图片）放本地缓存 + Redis 二级缓存，秒杀期间商品信息接口根本不查库（注意热点 `key` 击穿：`key` 失效瞬间并发回源打爆 `DB`，可用"永不过期 + 异步刷新"或互斥重建）
        - **请求排队（MQ 削峰）** ：下单请求先写入 `MQ`，后端异步消费处理——把"瞬时洪峰"变成"平稳水流"（削峰填谷）
        - **库存预扣减放 `Redis`**：秒杀前把库存预热到 `Redis`，扣减用 `Redis` + `Lua` 脚本原子操作——脚本内完成"检查库存 > 0"和"扣减"（一个原子操作，`Redis` 执行 `Lua` 期间不插入其他命令），避免超卖（`Redis 7+` 官方推荐用 `Functions FCALL` 替代裸 `EVAL` 做脚本治理）
        - **令牌桶/信号量**：应用层并发控制，限制同时处理的下单数

    - **第四层**：数据层优化
        - **数据库兜底扣减**：Redis 预扣减后，真正落库时用条件更新（原子自减） ： `UPDATE t_stock SET stock = stock - 1 WHERE product_id = ? AND stock > 0` 影响行数为 0 说明库存不足——InnoDB 行锁保证同库存行串行，不超卖（这是防超卖最后一道防线。注：这常被通俗叫"乐观锁"，但与教科书 version 版本号乐观锁不同，秒杀场景 `WHERE stock > 0` 是主流）
        - **异步落库**：成功扣减的请求异步写订单，最终一致性
        - **超时未支付回补**：订单超时未支付，回补 `Redis` 库存（延迟消息/定时任务）——若创建订单时已做 `DB` 兜底扣减，需同时回补 `DB` 与 `Redis`，且防重复回补（幂等）

    - **核心链路（一次秒杀请求）**
        1. 用户点秒杀 → 前端校验 → 网关限流 → 应用层
        2. 应用层直接执行 Lua 原子扣减（脚本内完成"检查库存 + 扣减"）→ 成功则写 MQ（注意：Redis 扣减与写 MQ 之间不是同一事务，存在失败窗口，靠对账/回补兜底）
        3. 后端消费 MQ → 异步创建订单 → 数据库条件更新兜底
            - DB 兜底扣减失败时，必须回补 Redis 库存（幂等） ——否则失败请求的库存被吞，Redis 与实际销量不一致
        4. 用户支付；超时未支付 → 回补库存（DB + Redis）

    - **防超卖的三道防线**
        1. `Redis + Lua` 原子扣减（第一道，拦截绝大多数并发）
        2. 数据库条件更新 `WHERE stock > 0`（第二道，兜底，`DB` 失败需回补 `Redis`）
        3. 对账（第三道，事后发现超卖/漏单，补偿）

    - **`2026` 年云原生弹性（秒杀的天然场景）**
        - **`KEDA` 事件驱动伸缩**：按 `MQ` 队列深度/`Kafka lag` 自动扩缩（可缩到 0）——秒杀洪峰来了自动扩容，峰值过后缩容，避免按峰值长期备机
        - **`Serverless` 按量计费**：突发流量按调用量计费，适合秒杀这种"一年就几次"的场景
        - **策略**：预置容量到预估峰值（保证开场不被击穿）+ KEDA 按积压自动扩容 + 峰值后缩容

- **协助记忆**
    - 一句话：秒杀 = 把"洪水"变成"细水长流"——层层拦截 + 排队处理
    - 口诀：静态化挡浏览、限流挡请求、MQ 挡洪峰、Redis 挡超卖、条件更新兜底
    - 秒杀像"春运抢票"——窗口（数据库）就几个，黄牛（脚本）要拦，先排队（MQ）再出票（异步落库），票（库存）没了就显示无票（不会超卖）

- **进阶思考**
    - **为什么扣库存一定要用 `Lua` 原子脚本，直接用 `Redis` 的 `get + decr` 不行吗？**
        - 不行。`get` 和 `decr` 是两个独立命令，高并发下两个线程可能同时读到库存=1，然后都执行 decr——导致超卖。Lua 脚本把"检查库存 > 0"和"扣减"合并成一个原子操作（Redis 执行 Lua 脚本时不会插入其他命令），从机制上杜绝了竞态。同理数据库侧用 UPDATE ... WHERE stock > 0 而不是"先 SELECT 再 UPDATE"

    - **`Redis` 扣减成功了，但 `MQ` 消费失败怎么办？库存会不一致吗？**
        - 会——这是分布式事务问题。常见方案：① 本地消息表：落库扣减与消息记录同库同事务，定时任务扫表投递 MQ（注意：Redis 预扣减与投递之间的窗口无法同事务，靠对账/回补兜底；也可用 RocketMQ 事务消息/半消息）；② 对账补偿：定时任务对比 Redis 库存、订单、支付状态，发现不一致就补偿（回补或重发）；③ 消费端幂等：同一个请求只能创建一个订单（订单号唯一索引），重复消费不产生重复订单。秒杀场景通常接受"最终一致"——不要求实时强一致，但必须保证不超卖、不漏单

## 🤔 在高并发情况下，如何避免数据库的性能瓶颈？  
- **数据库瓶颈的本质：数据库是有状态的、共享的、单点写（传统单主架构下） 的资源——请求越多，连接数、锁竞争、磁盘 IO、CPU 都成为瓶颈。核心思路：能不进 DB 就不进 DB（缓存挡）、能不进主库就不进主库（读写分离）、能减少请求就减少请求（异步化）、能快速返回就快速返回（索引与 SQL 优化） 。**

    - **第一层**：缓存前置（把读流量挡在数据库外面）
        - **多级缓存**：本地缓存（`Caffeine/Guava`）+ 分布式缓存（`Redis`）
        - 热点数据（商品信息、配置、用户资料）缓存化，命中率高的场景能挡掉 `80%+` 的读流量
        - **缓存三防**：
            - **穿透**：查不存在的 `key` 每次都打 `DB` ——— 布隆过滤器、空值缓存
            - **击穿**：热点 `key` 失效瞬间并发回源——互斥锁重建、永不过期+异步刷新
            - **雪崩**：大量 `key` 同时过期——过期时间加随机抖动、多级缓存

    - **第二层**：读写分离（让主库专心写）
        - **一主多从**：读流量走从库，写流量走主库
        - 主从复制有延迟 ——— 对一致性敏感（写后立即读）的流量必须走主库，其余读走从库
        - **中间件**：应用层路由（读写分离框架）或代理层透明路由（`ProxySQL`、`MySQL Router`、`MaxScale`、`ShardingSphere` 等）
        - **共享存储一写多读架构（如 `Aurora`、`PolarDB`）**：从库直接读共享存储，显著降低传统 `binlog` 异步复制的延迟，是"主从延迟"问题的重要解法
        - **适用前提**：读多写少的业务（大部分互联网业务）

    - **第三层**：连接治理（防止连接被打爆）
        - **连接池**：应用侧连接池（`HikariCP`/`Druid`），池大小要合理——不是越大越好，过大反而增加上下文切换和锁竞争。经验公式（源自 `PostgreSQL`，`HikariCP wiki` 转引）：连接数 ≈ `CPU` 核数 × `2` + 有效磁盘数 ——— `SSD` 场景下趋近核数×2（SSD 越快阻塞越少），最终以压测为准
        - 数据库侧 `max_connections` 设置合理上限，防止连接数被打满
        - **排查连接打满**：`show processlist`（`MySQL 8` 建议查 `performance_schema/sys` 库）、`ss -lntp | grep 3306` 看连接数

    - **第四层**：`SQL` 与索引优化（让每个请求更快）
        - **慢查询日志定位**：`slow_query_log` 开启，`mysqldumpslow` 分析
        - **`EXPLAIN` 分析执行计划**：确认走索引、避免全表扫描（`MySQL 8.0.18+` 可用 `EXPLAIN ANALYZE` 看实际执行）
        - **常见索引失效**：函数包裹索引列（`MySQL 8.0` 支持函数索引可解）、隐式类型转换、前导模糊查询 `%xx`、`OR` 连接的列并非全部可走索引（`OR` 两侧都是索引列时可用 `index_merge`）
        - **避免大事务**：事务要短、批量操作分批、避免长事务持有锁
        - **避免深分页**：`LIMIT 100000`, `10` → 用游标/`WHERE id` > 上次最大值 方式

    - **第五层**：异步化与削峰（减少对 DB 的瞬时冲击）
        - **`MQ` 削峰**：写操作（日志、统计、非实时数据）先入队，异步批量落库——注意：订单等对"写后立即可见"敏感的流量不能异步，需同步落库或异步后读主库兜底
        - **批量写入**：合并多条 `insert` 为一条批量 `insert`，减少事务与网络开销

    - **第六层**：分库分表（水平扩展，最后手段）
        - **适用**：单库数据量巨大、写入吞吐超单库上限、缓存与读写分离都无法解决
        - **拆分方式**：垂直拆分（按业务域拆库）、水平拆分（按分片键拆表，如 `user_id` 取模/范围）
        - **代价**：跨分片 `join`、分布式事务、全局唯一 `ID`、扩容搬迁——复杂度极高，先优化再分，能不分就不分
        - **替代路线**：分布式数据库（`TiDB` 等）——对应用透明地水平扩展，避免自研分库分表中间件的复杂度；部分分布式数据库还提供 `HTAP` 列存（`TiDB TiFlash`、`PolarDB` 列存索引），分析型查询走列存，与在线交易隔离

    - **第七层**：参数与硬件兜底
        - **`InnoDB innodb_buffer_pool_size`**：经验值取内存的 `60%-70%`（需为连接、排序缓冲、`OS` 缓存留余量）；`MySQL` 官方推荐专用服务器用 `innodb_dedicated_server` 自动配置（`>4GB` 内存时取 `75%`）；`MySQL 8.0` 默认仅 `128MB` 需调大，`8.0.14+` 支持在线调整 `SET GLOBAL innodb_buffer_pool_size`
        - 磁盘用 `SSD/NVMe`，监控 `IOPS` 与延迟
        - **云数据库（RDS）**：自带高可用与弹性，规格不够直接升配；`Serverless` 模式（如 `Aurora Serverless`、`PolarDB Serverless`）按负载自动扩缩，适合流量波动大的场景

- **协助记忆**
    - **数据库是后厨的"一口炒锅"——客人再多，锅只有一口。怎么让店不崩？**
        1. 橱窗里摆好常点的菜（缓存），客人直接拿走，不用惊动后厨；
        2. 菜单按食材分类排好（索引），大厨一翻就能找到；
        3. 点单先写单排队（`MQ` 削峰），后厨一桌桌做；
        4. 加个帮厨（从库）专管切配（读），主厨只管炒菜（写）；
        5. 真要人满为患，就开分店（分库分表）——但开分店要协调食材采购、跨店调货，麻烦得很，能不开就不开
    - **口诀（一句）** ：缓存挡、索引快、队列削、分离扛

- **进阶思考**
    - **连接池是越大越好吗？**
        - 不是。连接池过大，空闲连接占用数据库内存，且线程切换、锁竞争反而加剧。业界经验公式：`连接数` ≈ `CPU 核数` × `2` + `有效磁盘数`（SSD 下趋近核数×2）——但真实值要结合压测（响应时间、吞吐）调优。宁可"少量连接快速周转"，不要"大量连接排队等待"

    - **读写分离一定能解决瓶颈吗？什么场景不行？**
        - 读写分离只解决读多写少场景。如果瓶颈在写（写入量大、单库写入上限），读写分离无效——此时需要分库分表或分布式数据库；如果业务强一致性要求高（写后立即读、金融类），主从延迟会导致读从库读到旧数据，需要读走主库或引入同步手段（共享存储一写多读架构可降低延迟）。先定位瓶颈在"读"还是"写"，再选方案

    - **缓存和数据库的一致性怎么保证？**
        - 没有完美的强一致方案，常见做法：*先更新数据库，再删除缓存*（Cache Aside 模式）——而不是先更新缓存（并发下容易脏数据）。删除缓存后下一次读会 miss 并回源重建，天然兜底。极端一致要求下可引入 binlog 订阅（Canal）异步刷新缓存或监听变更。接受"最终一致"，把不一致窗口压缩到最小

## 🤔 如何设计高并发的 Web 服务架构？
- **高并发架构的本质：用"水平扩展"换吞吐，用"削峰填谷"扛峰值。核心三件事：无状态化（能加机器就扛得住）、分层削峰（把瞬时峰值摊平）、单点加速（让每个请求更快）。和高可用架构不同——高可用解决"挂了怎么办"（冗余+恢复），高并发解决"来得多怎么办"（吞吐+性能）。**

    - **第一原则**：无状态化 + 水平扩展（高并发的根基）
        - **应用层无状态**：请求不依赖本地存储，任何节点都能处理任何请求——这是"加机器就能扛"的前提
        - **状态外置**：`Session` 放 `Redis`（或 `JWT` 无状态）、文件放 `OSS`、配置放配置中心
        - **水平扩展能力 = 架构的"弹性上限"**：用压测验证近似线性扩展（追求"加 2 倍节点扛 2 倍流量"的目标，实际受有状态组件、热点、GC、网络影响，接近线性已属优秀）
        - 有状态组件（DB、Redis）是水平扩展的卡脖子点——所以核心思路是把状态全部外置，让应用层彻底无状态

    - **第二层**：入口分流（流量先分层打散）
        - **CDN**：静态资源（图片、JS、CSS）就近缓存，挡掉大量重复请求
        - **L4（LVS/NLB）→ L7（Nginx/ALB）分层负载均衡**：L4 扛连接、L7 做路由，各自水平扩展
        - **多机房流量调度（GSLB/智能 DNS）**：流量按地域/运营商分流

    - **第三层**：缓存（让绝大多数请求不落库）
        - **多级缓存**：浏览器缓存 → `CDN` → 本地缓存（`Caffeine/Guava`）→ 分布式缓存（`Redis`）
        - **命中率是核心指标**：缓存命中率高，数据库压力成比例下降（前提是缓存数据正确）
        - **缓存与数据库一致性（Cache Aside）**：先更新数据库，再删除缓存——而不是先更新缓存（并发下容易脏数据）；删缓存后下次读 miss 回源重建，天然兜底；极端一致要求可 `binlog` 订阅（Canal）异步刷新
        - **缓存三防**：穿透（布隆过滤器/空值）、击穿（互斥重建/永不过期异步刷新）、雪崩（随机过期/多级缓存）
        - **热点 `key` 专项**：读热点（单 key 打爆 Redis 单分片）——本地缓存 + 热点识别；写热点（秒杀库存）——Redis 原子操作 + MQ 削峰

    - **第四层**：异步化（削峰填谷）
        - **`MQ` 削峰**：非实时写操作（日志、统计、消息通知）先入队，后端按能力消费——把瞬时洪峰变成平稳水流
        - **异步落库**：读多写少场景，写请求异步合并批量落库
        - **注意**：强一致/写后立即读的流量不能异步，需同步处理

    - **第五层**：限流与保护（防止被峰值打垮）
        - **令牌桶限流（网关/应用层）**：控制进入系统的请求速率，超限快速失败（返回"拥挤"）；也可在服务网格（Istio/Envoy）层统一做限流/熔断
        /重试
        - **熔断**：下游故障时快速失败，保护本服务线程/连接资源
        - **降级**：非核心功能（推荐、搜索联想）在压力下自动关闭，保核心链路
        - **超限请求怎么处理（两种语义要分清）** ：拒绝（直接丢弃，快速失败）还是排队（延迟放行，类似漏桶）——按业务容忍度选；这和 `MQ` 削峰的"排队"不是一回事（一个是限流策略，一个是异步解耦）

    - **第六层**：数据库层（写瓶颈的最后解法）
        - **读写分离**：读走从库、写走主库（读多写少场景）——注意主从延迟：写后立即读的流量必须走主库
        - **分库分表**：写瓶颈/数据量大时水平拆分（按分片键），或改用分布式数据库（对应用透明）
        - **连接池**：应用侧合理池大小，避免连接打爆数据库

    - **第七层**：弹性伸缩（按需扩容）
        - **指标驱动扩缩**：`K8s HPA`（`CPU`/内存/自定义指标）、`KEDA`（按 `MQ` 队列深度/`Kafka lag` 事件驱动——本质是监控事件源并喂数据给 `HPA`，`0↔1` 由 `KEDA` 负责、`1↔N` 由 `HPA` 负责）
        - **`Serverless/Knative`**：事件驱动 + 从 0 冷启动 + 按量计费，适合流量波动大的场景
        - **容量规划**：压测摸清单节点容量，按峰值预留（预置到预估峰值的 80%，配合弹性兜底余量）
        - **扩容要有"提前量"**：弹性扩容有分钟级延迟，突发流量靠"预置容量 + 快速扩缩"双保险

    - **性能细节（让每个请求更快**）
        - **连接复用**：HTTP/2 多路复用、HTTP/3（QUIC，弱网场景收益明显）、连接池、长连接
        - **接口批量**：合并多次调用为一次批量接口（减少 RTT）
        - **压缩**：gzip/br 压缩响应体，减少带宽与传输时间
        - **协议选型**：内部服务间用 gRPC（二进制 + HTTP/2）比 JSON over HTTP/1.1 吞吐更高
        - **线程模型有两条路线**：① 非阻塞事件驱动（Netty/Reactor 回调式）；② 同步阻塞写法 + 虚拟线程（JDK 21+，线程廉价化，让开发者继续写 `thread-per-request` 代码，JVM 在阻塞点自动让出载体线程）—— 两者都能提升吞吐，路线不同

- **协助记忆**
    - 高并发 = 节假日的高速收费站。
        1. 车道要多（水平扩展），而且车不能都堵在一个窗口办手续——车过收费站不排队，直接 ETC 抬杆放行（缓存命中），少数没装 ETC 的才停车办证（落库）；
        2. 车流太大时匝道限流（限流），一辆辆放进去，别把站口挤爆；
        3. 大货车不赶时间，先分流到服务区歇着（MQ 异步），高峰期过了再走；
        4. 想要扛住国庆，收费站平时就要演练"开满所有车道"（压测+弹性伸缩）
    - 口诀（一句） ：无状态、加机器、缓存挡、异步削、限流保

- **进阶思考**
    - **水平扩展是"加机器就行"吗？什么会卡脖子？**
        - A：前提是无状态化——如果应用把 `Session` 存本地、文件存本地，加机器反而出乱子。真正的卡脖子点是有状态组件：数据库单库写入上限、`Redis` 单实例内存上限。所以高并发架构的本质工作，是把一切状态外置（Session→Redis、文件→OSS、数据→DB/分片），让应用层成为"纯计算资源"，才能无限加机器。写瓶颈最终要靠分库分表或分布式数据库解决

    - **`QPS`、`并发数`、`RT` 是什么关系？**
        - **`Little's Law`**：并发数 = `QPS` × `RT`（平均响应时间） 。例：`RT=100ms` 时，要扛 `1000 QPS`，需要约 `100` 个并发（1000 × 0.1）。注意：这里的"并发"指系统内平均在途请求数（in-flight），不等于压测工具的线程数 ——— 压测线程数 ≠ 在途请求数，规划线程池/连接池时勿直接套用。RT 越长，同样的 QPS 需要越多并发资源（线程/连接）——所以"降RT"既是用户体验优化，也是容量优化：RT 降一半，同等资源能扛 2 倍 QPS

    - **限流算法令牌桶和漏桶有什么区别？**
        - 令牌桶：以固定速率往桶里放令牌，请求要拿令牌才能过——允许突发（桶里攒的令牌可以一次性放行一波），适合大部分业务（允许短时突发）；漏桶：请求进桶，按固定速率流出——完全平滑，不允许突发，适合需要严格匀速的（如对下游 API的调用）。令牌桶是"允许偶尔冲一下"，漏桶是"永远匀速"


## 🤔 网站会监控哪些指标？	
- **网站监控的本质：分层监控 + 分级关注——从"用户感知"到"应用"到"基础设施"逐层覆盖，同时分清哪些是红线指标（可用性、错误率），哪些是辅助指标（资源水位）。核心方法论两个：RED（看应用服务）和 USE（看基础设施资源）。**

    - **第一层**：用户侧指标（用户实际感受到的）
        - **可用性（Uptime）** ：多地域拨测（HTTP 探测），红线指标——网站能不能打开
        - **Core Web Vitals（核心 Web 指标）** ：
            - **LCP（Largest Contentful Paint，最大内容绘制）**：页面主要内容加载速度，阈值 ≤ 2.5s 为良好
            - **INP（Interaction to Next Paint，交互到下一帧）**：页面交互响应速度，阈值 ≤ `200ms` 为良好。已取代 `FID` —— `FID` 只衡量"首次输入到开始处理"的延迟，INP 覆盖整个交互过程（输入延迟→事件处理→下一帧绘制），且统计的是最差的那次交互（交互较多时取第 98 百分位）
            - **CLS（Cumulative Layout Shift，累积布局偏移）**：页面视觉稳定性，阈值 ≤ 0.1 为良好

        - 前端 JS 错误率：页面报错比例，通过前端埋点（window.onerror）采集

    - **第二层**：应用层指标（RED 方法）
        - **Rate（速率/QPS）** ：每秒请求数，衡量吞吐——按接口/服务维度
        - **Errors（错误率）** ：5xx 比例（服务端错误）、4xx 比例（客户端错误，关注异常升高如被刷）、业务失败率（下单失败率）
        - **Duration（耗时/RT）** ：响应时间，重点看分位数而非平均值——P50（中位数，一半请求慢于它）、P95（95% 请求快于它，代表大多数用户体验）、P99（99% 请求快于它，近似代表最差体验）——平均值会被长尾拉高，掩盖问题
        - **业务指标**：订单量、支付成功率、注册量等（技术指标是手段，业务指标是目的）

    - **第三层**：中间件层指标
        - **`Nginx`**：连接数（活跃/等待）、`QPS`、`upstream` 响应时间、`5xx/4xx` 数量
        - **`Redis`**：命中率（低命中率 = 缓存失效/穿透）、内存使用（接近 `maxmemory` 会淘汰）、连接数、慢命令
        - **`MQ`**：队列积压量（积压 = 消费者跟不上）、消费速率、消息堆积时间

    - **第四层**：数据库层指标
        - 连接数（接近 `max_connections` 会拒连）
        - 慢查询数量与耗时（慢查询日志）
        - 主从延迟（从库落后主库时间，延迟大 → 读旧数据）
        - `QPS/TPS`、缓冲池命中率（`InnoDB buffer pool hit ratio`）

    - **第五层**：基础设施层指标（USE 方法）
        - **`Utilization`（利用率）** ：CPU 使用率、内存使用率、磁盘使用率、带宽使用率
        - **`Saturation`（饱和度）** ：CPU 运行队列长度（load average 近似——它包含 D 态不可中断睡眠任务，受 IO 阻塞影响）、内存 swap 使用、磁盘 IO 等待（iowait/await）、TCP 连接队列溢出——饱和比利用率更能反映"快撑不住了"
        - **`Errors`（错误）** ：网卡丢包、磁盘 IO 错误、硬件告警（SMART）
        - **资源水位告警线**：如磁盘 80% 告警、90% 紧急——留出处理时间

    - **指标背后的设计原则**
        - **指标要可行动**：一个指标对应一个明确的处理动作（磁盘 90% → 清理/扩容）
        - **指标要有分级**：红线（可用性、错误率）优先告警，辅助指标（资源水位）低级别告警
        - **SLI/SLO**：把指标和承诺挂钩 ——— `SLI（Service Level Indicator）`是实测指标值（如"请求延迟 `P99` = `450ms`"），`SLO`（`Service Level Objective`）是目标（如"P99 < 500ms"或"99.9% 的请求延迟 < 500ms"，二选一，不要双重百分位嵌套）——监控告警围绕 SLO 设计，超 SLO 才真正需要打扰人
        - **可观测性三支柱**：Metrics（指标）+ Logs（日志）+ Traces（链路追踪）配合使用——指标发现异常、日志定位原因、链路定位调用关系（Profiling 作为第四信号正被推进）

- **协助记忆**
    - 监控网站 = 每年给网站做体检。
        1. 挂号能挂上、医生看得见人（可用性拨测）——— 人都不来医院就说明大门关了；
        2. 分科室查：内科查心脏（CPU）、血压（内存）、血常规（磁盘 IO），外科看伤口（错误率）；
        3. 心电图（RT 曲线）不能只看"平均心跳"，要看 P99 那个"最差的心跳间隔"——偶尔漏跳一次才是隐患；
        4. 体检报告要有"危急值"（红线指标）和"参考值"（资源水位）——危急值半夜也要叫醒你，参考值偏高只是提醒你该注意了
    - 口诀：可用性是红线，QPS/RT/错误率三件套，RED 看应用、USE 看资源

- **进阶思考**
    - **为什么看 RT 要看 P95/P99，而不是平均值？**
        - 平均值会被长尾拖累或掩盖。例子：100 个请求，99 个 10ms，1 个 10s——平均值约 110ms，看起来"还行"，但 P99 是 10s，说明有 1% 的用户体验极差。分位数能暴露"最差的那批用户体验"——P99 才是用户能感受到的"最坏情况"，优化目标是压低 P99 而不是平均值

    - **QPS、RT、错误率都正常，网站就一定没问题吗？**
        - 不一定，三个盲区：
            1. 业务指标：接口全绿但订单量暴跌（支付通道故障、业务逻辑 bug），技术指标反映不出来；
            2. 资源饱和度：QPS 没涨但 CPU 运行队列堆积（load 高），说明在排队，用户体感变慢；
            3. 未知的未知：没监控到的环节（第三方 API、DNS 故障）出问题，指标根本采集不到。
        - 监控要"技术 + 业务"结合，且要主动发现盲区（拨测、故障演练）

    - **指标告警一直响但查不出问题，怎么办？**
        - 大概率是告警设计问题：阈值太敏感（抖动就报警）、指标无关联信息（只有数字没有上下文）。改进：
            1. 阈值用分位数而不是瞬时值（如"P99 连续 5 分钟超阈值"）；
            2. 告警带上关联信息（错误日志片段、链路追踪链接）；
            3. 收敛告警（同类合并、抑制依赖告警）；
            4. 定期清理无效告警规则——告警疲劳是监控体系失效的头号原因


## 🤔 HTTP 长连接和短连接有什么区别及适用场景？	
- **核心区别一句话：短连接是"每次请求都新建 TCP 连接、用完即关"；长连接（Keep-Alive）是"同一条 TCP 连接上复用多个请求/响应"。区别的本质在于：建连成本（TCP 三次握手 + HTTPS 下还有 TLS 握手）和服务器连接资源占用之间的权衡。**

    - **短连接（HTTP/1.0 默认行为）**
        - **机制**：每个请求都新建 TCP 连接，请求/响应完成后立即关闭
        - **开销**：每次都要 TCP 三次握手（HTTPS 还要加 TLS 握手——比 TCP 握手更贵，通常 1-2 个额外 RTT + 加解密计算）
        - **优点**：连接用毕即释放，不长期占用服务器连接资源（文件描述符、内存）
        - **缺点**：频繁建连带来握手开销和高延迟；大量建连导致 TIME_WAIT 堆积（详见进阶思考）
        - **控制方式**：HTTP/1.0 用 `Connection: keep-alive` 显式开启；HTTP/1.1 默认开启无需声明，用 `Connection: close` 关闭

    - **长连接（Keep-Alive，HTTP/1.1 默认行为）**
        - **机制**：一次 TCP 连接建立后，可连续发送多个请求/响应，连接空闲超时或达到最大请求数才关闭
        - **开销**：只在首次建连时握手，后续请求省掉握手 RTT——高频交互场景延迟和开销显著降低
        - **优点**：省握手、低延迟、吞吐高
        - **缺点**：长期占用服务器连接资源——连接数上限受服务器 fd/内存限制，连接数打满时新请求进不来
        - **控制方式**：HTTP/1.1 默认开启；服务端通过 keepalive_timeout（空闲多久关闭）、keepalive_requests（最多复用多少次）、keepalive_time（单连接最长存活）三个维度控制

    - **关键点**：长连接不是"永不断开"
        - **服务端有 `keepalive_timeout`（如 Nginx 默认 75s）**：连接空闲超时后关闭，释放资源
        - **keepalive_requests**：连接上最多处理的请求数（Nginx 1.19.10+ 默认 1000，旧版默认 100），防单连接无限占用
        - **keepalive_time**：单连接最长存活时间（Nginx 1.19.10+，默认 1 小时）
        - 中间设备（防火墙、负载均衡）可能静默断开空闲连接——客户端要能处理"连接被重置"（重试机制）

    - **反向代理场景的 `keepalive`（`upstream` 连接复用）**
        - 浏览器 ↔ Nginx 之外，Nginx ↔ 后端也要配：upstream 块里 keepalive 32;——注意语义：它表示每个 worker 进程最多保留 32 条"空闲"上游连接（LRU 淘汰），并不限制 worker 向上游打开的总连接数（官方文档明确）
        - 生效前提：需配合 proxy_http_version 1.1; + proxy_set_header Connection "";（清空 Connection 头）
        - 效果：Nginx 到后端的 TCP 连接复用，避免每个请求都向后端重新建连（后端连接数压力大时收益明显）
        - 版本说明：Nginx 1.29.7+ 的 upstream keepalive 默认启用（默认 keepalive 32 local，每 worker），1.27.x 及更早需手动配置

    - **`HTTP/2` 与 `HTTP/3` 的演进**
        - **`HTTP/2`**：在同一 TCP 连接上多路复用多个并发请求，解决的是 HTTP/1.1 同连接串行请求的应用层队头阻塞——但 TCP 层队头阻塞仍在（丢包会阻塞整条 TCP 连接），本质是更彻底的长连接
        - **`HTTP/3（QUIC）`** ：基于 UDP，用 Connection ID 标识连接（可跨 IP/端口迁移）、0-RTT 建连（有重放攻击风险，需应用层防护）、多流独立消除了 TCP 层队头阻塞；连接模型是 QUIC 长连接而非 TCP 连接

    - **适用场景对比**
        - **短连接适用**：请求频率低、交互间隔长、并发不高——用完即释放，不浪费资源。例：低频 API 调用、客户端与服务端偶发通信、一次性的查询请求
        - **长连接适用**：请求密集、高频交互、低延迟要求——省握手成本。例：网页加载（页面 + 静态资源 + 接口多次请求）、频繁调用的 API
        - **关键判断**：请求频率 × 建连成本 > 连接资源占用成本 → 用长连接；反之用短连接
        - **注意**：`WebSocket` 是经 `HTTP Upgrade` 建立的独立全双工连接，机制上不同于 `HTTP keep-alive` 的请求复用——它是另一类长连接，不属于本问题讨论的 `keep-alive`

- **协助记忆**
    - 短连接 = 每次有事都要重新拨号（拨号 = TCP 握手），说完就挂，下次再拨
    - 长连接 = 电话不挂断，一件事接一件事说。
        1. 偶尔才联系一次的（低频），挂断重拨无所谓——短连接合适；
        2. 天天要打几十通的（高频），每次都重拨烦死了——长连接合适。但电话一直占着线（长连接占资源），别人就打不进来了，所以要有"通话超时 自动挂断"（keepalive_timeout）
    - 口诀：高频长连省握手，低频短连省资源，长连管好超时数，短连小心 TIME_WAIT

- **进阶思考**
    - **为什么 `HTTPS` 下长连接的收益更大？**
        - 因为 TLS 握手比 TCP 握手贵得多——TCP 三次握手只要 1 个 RTT，TLS 1.2 完整握手要 2 个 RTT（TLS 1.3 也要 1-RTT），加上证书验证的加解密计算。短连接下每次请求都重复这套成本；长连接只在首次建连时付一次，后续请求全部省掉。所以 HTTPS 场景（尤其移动端弱网）长连接收益显著

    - **`TIME_WAIT` 是谁产生的？堆积了会有什么后果？**
        - 谁先主动关闭（先发 FIN）谁产生 `TIME_WAIT`，保持约 2MSL（60s 左右），目的是让迟到的包在网络中消亡、防止新连接收到旧包。短连接下服务端/中间件通常先关闭（响应无 Content-Length 时靠关闭连接标识消息结束）——服务端 TIME_WAIT 堆积是经典问题，但服务端监听端口固定，TIME_WAIT 不阻塞入站新连接，主要消耗连接表项/内存；客户端侧 TIME_WAIT 才消耗本地临时端口（默认约 3 万，极端耗尽报 `Cannot assign requested address`）。
        - 缓解：
            1. 优先用连接池/长连接（从根源减少建连）；
            2. 客户端侧可开 tcp_tw_reuse（依赖 tcp_timestamps，NAT 环境有隐患，且内核 4.12+ 复用条件收紧；tcp_tw_recycle 已在 4.12 移除）；
            3. 扩大 net.ipv4.ip_local_port_range

    - **长连接是不是越多越好？**
        - 不是。长连接虽然省握手，但每一条都占用服务器资源（fd、内存、内核连接表）。连接数超过服务器上限，新请求直接拒绝。所以生产 要控三件事：
            1. 服务端 `keepalive_timeout` 不能太长（空闲连接尽快释放）；
            2. `keepalive_requests/keepalive_time` 限制单连接复用次数与最长存活；
            3. 连接数监控告警（接近 `max` 时扩容或调参）。"长连接 + 合理超时 + 连接数监控"才是正确姿势


## 🤔 HTTPS 中 CA 证书在客户端还是在服务端？作用是什么？

- **这个问题有一个常见歧义，要先拆开："CA 证书"其实指两个不同的东西——CA 的根证书（信任锚）  和 CA 签发的服务器证书。一句话回答：服务器证书在服务端，CA 根证书在客户端（浏览器/操作系统的信任库） 。两者的作用合在一起，完成 HTTPS 的"身份验证"：证明"我连的这个服务器确实是它声明的那个域名"。**

    - **两个证书要分清**
        - **服务器证书（叶子证书，在服务端）** ：由 `CA` 签发，绑定"域名 + 公钥"，存放在 Web 服务器（Nginx 配置的 ssl_certificate），TLS 握手时发给客户端
        - **`CA` 根证书（信任锚，在客户端）** ：`CA` 自签的根证书，存放在客户端的信任库（浏览器/操作系统证书库——Chrome 用系统信任库，Firefox 用自带的 NSS 信任库），客户端用它验证服务器证书的签名——信任链的起点

    - **证书链（三层）**
        - 根 `CA` 证书 → 中间 `CA` 证书 → 服务器证书（叶子）
        - **服务端在握手时发送**：服务器证书 + 中间 CA 证书（链拼接） ——— 规范与实践均推荐不发送根证书（客户端信任库里已有，无需传输；发送了客户端也会忽略）
        - **客户端验证流程**：用本地信任的根证书公钥 → 验证中间 CA 的签名 → 用中间 CA 的公钥 → 验证服务器证书的签名 → 再检查域名匹配（SAN，由浏览器应用层执行）、有效期、（可选）吊销状态

    - **TLS 握手中的位置与"验证"和"密钥协商"的分工**
        1. `ClientHello` / `ServerHello` ——— `TLS 1.3` 中 `ECDHE` 密钥协商参数（`key_share`）在这两条消息里就已交换
        2. 服务器发送 Certificate 消息（服务器证书 + 中间 CA 证书）
        3. 客户端用本地信任库的 `CA` 根证书验证证书链，再验证 `CertificateVerify` 签名（证明服务器持有证书对应私钥）——失败则报"证书不受信任/连接不安全"
        4. 注意：证书验证与密钥协商是两回事 ——— `TLS 1.3` 中密钥协商先于证书验证完成，证书公钥不参与密钥协商，只用于验证持有私钥（"用证书公钥加密协商密钥"只是已废弃的 `TLS 1.2 RSA` 密钥交换模式）

    - **双向 `TLS（mTLS）`的例外**
        - 默认 `HTTPS` 只验证服务器身份（单向）
        - **mTLS（双向认证）**：服务器也要求客户端出示客户端证书——此时客户端证书存在客户端（用户证书/设备证书），服务器用自己的信任库验证客户端
        - **适用**：企业内网、服务间调用（服务网格 mTLS）、高安全要求场景

    - **几个常见场景澄清**
        - **自签名证书**：服务器证书没有 `CA` 签名（或自己签）——客户端信任库里没有对应根证书，验证失败；除非手动把自签证书加入客户端信任库（适合内网测试，不适合公网）
        - **企业内网 `CA`**：公司自建 `CA` 签服务器证书——必须把企业 `CA` 根证书分发并安装到所有客户端的信任库，否则客户端不信任（这是"CA 证书要装到客户端"的典型运维场景）
        - **证书过期/吊销**：证书验证含有效期检查；吊销状态浏览器默认不实时执行（Chrome 用 CRLSets、Firefox 用 CRLite 替代，OCSP 装订由服务端提供）

    - **作用总结（各司其职）**
        - **服务器证书（服务端）** ：身份凭证——声明"我是这个域名"，携带公钥供验证；配合私钥（ssl_certificate_key）证明持有
        - **CA 根证书（客户端）** ：验证依据——提供可信的验签锚点，让客户端能判断"这张服务器证书是不是由可信机构签发、有没有被篡改"
        - 合起来完成 HTTPS 三大保障之一：身份验证（防冒充） ——防止中间人伪造服务器

- **协助记忆**
    - 服务器证书 = 对方的护照（服务端随身携带，证明身份
    - CA 根证书 = 你手里那本**"可信发证机关名录"**（客户端持有，用来核验护照上的签发机关）。过海关时：对方递上护照（服务器证书）→ 你翻开名录找到签发机关（CA）→ 对照护照上的防伪印章（数字签名）→ 章对得上，人才放行（验证通过才开始正常通信）。如果对方拿的护照签发机关不在名录里（自签名/不受信任的 CA），直接拒绝
    - 口诀：服务端拿证书、客户端存信任、握手验签名、链上见真伪

- **进阶思考**
    - **为什么服务端不发根证书，只发中间证书？**
        - 因为客户端信任库里已经有根证书——再发一遍既浪费带宽，又没必要（根证书是信任锚，如果靠服务器"自证"根证书，就失去信任意义了）。客户端用本地已有的根证书去验证中间证书和服务器证书的签名，形成"本地锚 + 链式验证"。这也解释了为什么企业自建 CA 必须预先把根证书装进客户端——客户端信你，才认你签的证书

    - **服务器证书和私钥的关系？私钥泄露会怎样？**
        - 证书是"公开的身份凭证"（可以公开，任何客户端都能拿到），私钥是"持有身份的证明"（必须保密）。TLS 握手靠"持有对应私钥"来证明你就是证书的主人（CertificateVerify 签名验证）。私钥泄露 = 身份被冒用：攻击者拿到私钥后，可以用你的证书伪装成你的服务器做中间人。所以私钥要：权限收紧（chmod 600）、不随代码仓库分发、定期轮换

    - **客户端信任库里的根证书会不会被攻击者利用？**
        - 信任库本身是"信任锚"，一旦被污染，攻击者可以为任意域名签发"受信任"的证书。历史上有两类真实案例：
            1. 信任锚被攻破 —— DigiNotar 原本是受信任的根 CA，被入侵后签发了 500+ 欺诈证书（被用于伊朗 Gmail 用户的中间人攻击），随后各浏览器/OS 将其根从信任库移除、公司破产；
            2. 恶意根证书被安装——联想 `Superfish`、`Dell eDellRoot` 事件，厂商在设备里预装带固定私钥的根证书，导致 HTTPS 可被解密。所以"随便装证书到信任库"是高风险操作——信任锚被污染，整个 HTTPS 信任体系都失效。浏览器侧的封禁机制：Chrome 用 CRLSets 紧急封禁被入侵 CA 签发的中间/叶子证书，Firefox 用 CRLite 压缩吊销数据；有问题的根证书则由信任库更新移除

- **扩展信息**   
数字证书家族辨析（服务器证书 / 客户端证书 / 代码签名证书）  
网上常见的"服务器证书、客户端证书"说法，容易让人以为是两种完全不同的证书——实际上它们都是同一种 X.509 数字证书，区别在于"用途"（Extended Key Usage，EKU 扩展密钥用法）和"持有/验证角色"不同。可以这样理解：证书的"结构"只有一种，靠 EKU 字段声明"这张证书是拿来干什么的"。
    - **先理清"数字证书"这个统称**
        - 数字证书（Digital Certificate）通常即 X.509 证书（"数字证书"是通用概念，X.509 是其中主流实现）：一个包含公钥、主体信息（是谁/哪个域名）、有效期、CA 数字签名的数据结构
        - 服务器证书、客户端证书、代码签名证书、邮件签名证书……都是数字证书的具体用途形态——结构相同（X.509），EKU 不同
        - 公开信任的数字证书由同一套 PKI（公钥基础设施）支撑：证书签发、验证、吊销与信任锚的完整生命周期管理——这套体系回答的是"这张证书是谁签的、是否可信"
    - **服务器证书 vs 客户端证书（不是两种类型，是两种用途）**
        - **服务器证书**：绑定域名/IP，由服务器持有并在 TLS 握手中出示，客户端验证它——EKU 通常声明 serverAuth（服务器身份验证）。作用：证明"我是这个域名"
        - **客户端证书**：绑定个人/设备/服务身份，由客户端持有并在 mTLS（双向认证）中出示，服务器验证它——EKU 通常声明 `clientAuth`（客户端身份验证）。作用：证明"我是这个被授权的用户/设备"
        - **一句话**：同一个"身份证模板"（X.509），一张印着"这是服务器"（serverAuth），一张印着"这是客户端"（clientAuth）—— 机制同源，但签发渠道（公共 CA vs 企业/私有 CA）和验证方向（谁验谁）相反
        - **注意细节**：EKU 缺失时证书用途不受限（老证书常见）；现代服务器证书 EKU 常同时含 serverAuth 与 clientAuth
    - **代码签名证书（Code Signing）**——给软件"盖章"
        - **用途**：给软件/代码签名，证明"这份代码确实来自某发布者、且未被篡改"——EKU 声明 `codeSigning`
        - **典型场景**：Windows 安装包/驱动、macOS 应用、移动 App（注意：移动端是平台专有签名体系，机制与桌面代码签名证书不同）
        - **用户双击安装时，系统验证**：签名者身份是否可信 + 代码自签名后是否被改过——防的是"木马冒充正规软件"和"安装包被篡改"
        - **强约束**：公开信任的代码签名证书，私钥必须生成、存储、使用于硬件加密模块（HSM/USB token），不允许存在普通磁盘上——这是 **CA/B Forum** 基线要求的强制项
        - **Windows 弹"未知发布者"不代表没签名**：未签名、签名无效，或签名者声誉未建立（如标准证书的新签名者）都会触发——只有特定高级别证书才能立即建立 `SmartScreen` 声誉
        - **`macOS` 不只是签名**：`Developer ID` 签名之外，分发软件还须经 `Apple` 公证（notarization） ，Gatekeeper 才会放行——"签名 + 公证"两步
        - **与 TLS 证书的区别（容易混淆的点）**：TLS 服务器证书验证的是"网站是不是真的"（防假冒网站、中间人）；代码签名证书验证的是"软件是不是真的"（防假冒软件、被篡改）——一个保护"你在访问谁"，一个保护"你在运行什么"
    - **其他常见证书用途（顺带认识）**
      - **邮件签名证书（EKU emailProtection）**：给邮件数字签名（S/MIME），证明发件人身份 + 邮件未被篡改
      - **文档签名证书**：给 PDF/文档签名（如 Adobe 文档签名）
      - **时间戳证书**：给签名"盖时间章"，证明"在这个时间点签名有效"
      - 这些和服务器证书一样，都是 X.509 数字证书 + 对应 EKU 用途的组合
    - **怎么快速看一张证书的用途？**
      - **命令行查看证书 EKU：**
        ```bash
        openssl x509 -in cert.pem -noout -text | grep -A 1 ExtendedKeyUsage
        # 输出如：TLS Web Server Authentication（serverAuth）/ TLS Web Client Authentication（clientAuth）...
        ```
      - 浏览器地址栏锁图标 → 证书信息，也能看到"增强型密钥用法"

## 🤔 如何进行压力测试？有哪些指标？如何判断系统是否达到性能瓶颈？
- **压测的实战心法：选对工具 + 会读输出 + 会设计场景。理论（梯度加压、拐点、分位数）前面已讲，这里全部落到工具实操——每个工具给"最常用一条命令"、参数怎么理解、输出怎么看。**

    - **工具选型速览（先知道什么时候用哪个）**
        - **`ab（Apache Bench）`**：最轻量，单条 `URL` 快速摸底，装 `apache2-utils` 就有——适合"快速看看这接口能扛多少"
        - **`wrk / wrk2`**：高并发压测主力，单进程多线程 + `epoll`，单机能打满高吞吐服务——适合"往死里压"
        - **`k6`**：脚本化（JS）、场景丰富（爬坡/混合场景）、输出规范、可进 CI——适合"正式的压测工程"
        - **`JMeter`**：功能最全（GUI + 复杂场景 + 断言 + 分布式），较重——适合"复杂业务流程压测"
        - **`hey / bombardier / vegeta`**：轻量替代品（hey 是 Go 版 ab 替代）
        - **云压测**：大流量（几十万 QPS 以上）、多地域——单机工具打不满时用

    - **ab 实战（最轻量，快速摸底）**
        - **安装**：`CentOS/RHEL yum install -y httpd-tools；Ubuntu/Debian apt install -y apache2-utils`
        - **最常用一条命令**： `ab -n 10000 -c 100 -k http://127.0.0.1:8080/api/user`
        - **参数解读**：
            - `-n` 总请求数、`-c` 并发数（一次同时发多少个）、`-k` 启用 `keep-alive` 连接复用（默认每次新建连接，压的是短连接）、`-H` 自定义请求头（如 `-H "Authorization: Bearer xxx"`）、`-p POST` 数据文件（配合 `-T` 指定 `Content-Type`，如 `-p body.json -T application/json`）、`-t` 按时间压（如 `-t 60` 压 `60` 秒，内部隐含 `-n 50000`）

        - **输出怎么看（关键几行）：**
            - **`Requests per second`：`QPS`**
            - **`Time per request`**：有两行，含义正好相反，最容易看反——第一行 (`mean`) 是单请求平均耗时（`RT` 均值，公式 `timetaken/done`）；第二行带 `across all concurrent requests` 是总耗时 ÷ 请求数（吞吐视角，约等于 `1000/QPS`）。很多人把第二行的小数值当成 `RT`，得出严重偏低的结论——记住：第一行才是单请求耗时
            - **`Percentage of the requests served within a certain time (ms)`**：底部分位数表（50%/90%/99%）——先看分位数与错误率，QPS 作参考
            - **`Failed requests`**：错误数

        - **局限**：单 `URL`、不支持复杂场景、请求行始终是 `HTTP/1.0`（`-k` 只是加 `Connection: Keep-Alive` 头启用连接复用，并不切换协议版本）

    - **wrk 实战（高并发主力）**
        - **安装**：源码编译 `make`（依赖 `gcc` + `libssl-dev`）；或用包管理器
        - **最常用一条命令**：`wrk -t8 -c400 -d30s --latency http://127.0.0.1:8080/api/user`
        - **参数解读**：`-t` 线程数（建议 ≤ `CPU` 核数）、`-c` 连接数（总连接，均分到各线程）、`-d` 时长（`30s/2m`）、`--latency` 打印延迟分位数、`-H` 请求头、`-s Lua` 脚本（自定义请求/鉴权）
        - **输出怎么看**：
            ```ini
            Thread Stats   Avg      Stdev     Max   +/- Stdev
            Latency     5.23ms    3.10ms  88.91ms   92.33%
            Req/Sec    12.41k     1.20k   16.00k    85.12%
            Requests/sec: 98615.33
            ```
        - `Requests/sec`：`QPS`
        - **`Thread Stats` 里 `Latency` 行**：`Avg` 平均延迟、`Stdev` 标准差、`Max` 最大延迟（默认输出没有分位数，加 `--latency` 后底部出现 `50%/75%/90%/99%` 分位数表）
        - **`Req/Sec` 行**：每个线程的吞吐（12.41k × 8 线程 ≈ 98k，和总量对得上）

        - **`Lua` 脚本示例（自定义请求头/POST body）：**
            ```lua
            wrk.method = "POST"
            wrk.headers["Content-Type"] = "application/json"
            wrk.body = '{"page":1}'
            ```

    - **`k6` 实战（脚本化正式压测）**
        - **安装**：官方二进制 / `Docker`（`docker run grafana/k6`）
        - **最小脚本 `loadtest.js`**：
            ```js
            import http from 'k6/http';
            import { check } from 'k6';
            export const options = {
            vus: 100,            // 虚拟用户数（并发）
            duration: '30s',     // 时长
            thresholds: { http_req_failed: ['rate<0.01'] }  // 断言：错误率 < 1%
            };
            export default function () {
                const res = http.get('http://127.0.0.1:8080/api/user');
                check(res, { 'status is 200': (r) => r.status === 200 });
            }
            ```

        - **执行**：`k6 run loadtest.js`
        - **输出怎么看（关键几行）**：
            - **`http_req_duration`**：响应时间——默认含 `avg`、`min`、`med`、`max`、`p(90)`、`p(95)`；需要 `p(99)` 要在 `options` 里加 `summaryTrendStats: ['p(99)']`
            - **`http_reqs`**：总请求数；`http_req_failed`：失败率；`iterations`：完成迭代次数

        - **进阶**：`ramping-vus` 做爬坡（每 `10s` 加 `100 VU`）、多阶段场景、混合接口

    - **`JMeter` 实战（复杂流程）**
        - **概念三件套**：`Thread Group`（并发线程数、循环次数）→ `Sampler`（`HTTP` 请求：`URL`、方法、`body`）→ `Listener`（查看结果：聚合报告 `Aggregate Report`）
        - **`GUI` 建好测试计划后，生产用命令行跑**： `jmeter -n -t plan.jmx -l result.jtl -e -o report_dir`
            - `-n` 非 `GUI` 模式、`-t` 测试计划文件、`-l` 原始结果日志、`-e` 生成 `HTML` 报告、`-o` 报告输出目录
        - **聚合报告关键列**：`Samples`（请求数）、`Average`（平均耗时）、`Error%`（错误率）、`p90/p95/p99`（需配置 `Percentiles`）
        - **分布式压测**：一个 `Master` 调度 + 多个 `Slave` 执行（跨机器扩容）

    - **实战要点**：本机快速起一个测试服务
        - **临时起个 HTTP 服务练手**： `python3 -m http.server 8080`   # 静态文件服务，目录下放个文件
        - 或压测一个真实接口前，先用 `curl -v` 确认请求能通、响应格式符合预期（避免压了半天压的是 404）

- **协助记忆**
    - 压测 = 火锅店开业前的"试营业压力测试"。
        1. 先来 10 桌（低并发），看看顺不顺，再加到 50 桌、100 桌（梯度加压）；
        2. 看翻台率（QPS）：每多 10 桌翻台率还在涨，说明没到顶；加到某个数翻台率不涨反降——这就是瓶颈点（拐点） 
        3. 客人等位时间（RT）突然从 5 分钟变 1 小时，说明店里排队了（排队积压）；
        4. 还要找出"谁先撑不住"：后厨炒不过来（CPU）、服务员跑不过来（线程池）、灶台不够（连接池）、收银机卡住（DB）——哪一环先满，哪一环就是短板
    - 口诀：梯度加压找拐点，QPS/RT/错误率三看，资源打满定短板

- **进阶思考**
    - **`ab` 和 `wrk` 压同一个接口，数字差很多，谁是对的？**
        - 可能是连接模型差异：`ab` 默认不开 `keep-alive（-k）`，每次请求新建连接，压的是"短连接开销 + 业务"；wrk 默认长连接复用，压的是"纯业务处理能力"。真实线上一般有 keep-alive，wrk 的数字更接近生产；但两者都要看分位数和错误率，别只比一个 QPS 数字。如果并发、时长等条件相同、差异还大，检查压测机资源是否打满

    - **压测工具打不满目标，怎么判断是工具的问题还是服务的问题？**
        - 看压测机自身资源：压测机 `CPU` 打满、或报端口耗尽（`Cannot assign requested address`）——工具到顶了，换更强压测机/分布式/云压测；压测机资源充足但 `QPS` 上不去——服务侧瓶颈，继续查系统资源与锁

    - **为什么 k6 输出直接给 p(90)/p(95)，ab 只有分位数表？**
      - `k6` 把"分位数统计"内置为默认指标（`http_req_duration` 自带 `p` 系列），`ab` 只在底部打印分位数表、不进入统计行——本质都提供，但 `k6` 更"工程化"：还能用 thresholds 断言（如错误率 < 1% 才算通过），直接当 CI 门禁。这也解释了为什么"正式压测"推荐 k6/JMeter，ab/wrk 适合快速摸底

- **扩展信息**  
`P95`、`P99` 到底是什么？（分位数/百分位数）前面很多回答反复出现 `P50`/`P95`/`P99`——它们叫分位数（Percentile，百分位数），是统计学里描述"数据分布"的指标。一句话定义：把一组数据从小到大排序，第 `n` 百分位 = "有 `n%` 的数据小于等于它"的那个值。用在延迟上：`P99` = `99%` 的请求耗时不高于这个值（也意味着约 1% 的请求比它慢）。
    - **常见档位及含义（以响应时间为例）**
        - **`P50`（中位数）** ：一半请求比它快、一半比它慢——"典型体验"（但典型不代表多数用户感受）
        - **`P90` / `P95`**：`90%/95%` 的请求比它快——"大多数用户体验"，一般业务看 `P95`
        - **`P99`**：`99%` 的请求比它快——"尾部体验"，代表最慢的那 `1%` 用户；`SLO` 常承诺 `P99`（如"P99 < 500ms"）
        - **`P999`**：`99.9%` 的请求比它快——极端长尾，常用于高敏感场景（支付、实时系统）
        - **`Max`**：最慢的单个请求——不稳定，受偶发抖动影响大，一般不当指标用
        - 档位越高越能暴露"长尾问题"，但也越受单次抖动影响（P999 往往被一次 GC 停顿或网络抖动拉爆）
    - **为什么不用平均值？（经典长尾例子）**
        - `100` 个请求：`98` 个 `10ms`，`2` 个 `10s`
        - `平均值 = (98×10ms + 2×10s) / 100 = 209.8ms ≈ 210ms` —— 看起来"还行"？
        - `P99`（排在第 `99` 位）= `10s` —— 真相：有 1% 的用户体验极差
        - **`结论`**：平均值被极端值"平均掉"了，掩盖了最差的那批体验；分位数能还原"分布的形状"。反过来也有"平均值高但中位数（P50）低"的情况（少数请求极慢拉高平均）——注意极慢请求会同时拉高平均值和高分位数（P99/P999），低的只是中位数，所以平均值 + 分位数一起看才完整
    - **怎么算的？（排序即可，不用背公式）**
        - 把 N 个请求的耗时从小到大排成一列，第 n 百分位 ≈ 第 ceil(n/100 × N) 个位置的值（精确实现有插值差异——如 numpy/Excel 的 R-7 插值——理解思路即可）
        - 例：100 个请求，P99 = 排在第 99 位那个请求的耗时（99% 的请求 ≤ 它）
        - 注意：样本量太少时 P99 没意义——只有 20 个请求，P99 就是"第 20 个"（最慢那个），和 Max 没区别
    - **什么场景看什么档位**
        - **用户体验优化**：P95/P99（用户能感知的"慢"）
        - **SLO/告警承诺**：P99（对外承诺"99% 请求快于 X"）
        - **容量规划/压测**：P99 比平均值更能反映真实承载能力（压测时看 P99 拐点）
        - **高敏感交易**：P999
        - **健康巡检**：P50 + P99 组合（P50 看常态，P99 看异常尾巴）
    - **在工具/监控里怎么看**
        - **`ab`**：输出底部 Percentage of the requests served within a certain time 分位数表
        - **`wrk`**：加 --latency 参数，输出底部 50%/75%/90%/99% 分位数表
        - **`k6`**：http_req_duration 默认含 avg/min/med/max/p(90)/p(95)，p(99) 需在 options 配置 summaryTrendStats:
        ['p(99)']（注意：会覆盖默认列表，要写上想保留的全部项）
        - **`Prometheus`**：histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) 从直方图算 P99——这是估算，精度取决于直方图 bucket 划分（bucket 越细越准）；新指标可优先用原生直方图（native histograms） —— 自适应 bucket，免手动设计；另注意分位数不能跨实例直接取平均（无加法性），应先聚合直方图再算
        - **`日志/APM`**：很多 APM 直接展示 P95/P99 曲线
    - **常见坑（面试加分）**
        - 样本量太小：P99 ≈ Max，没有意义
        - 只盯平均值：掩盖长尾
        - 只盯 P99：被单次抖动（GC、网络）干扰，建议 P50 + P99 一起看
        - 直方图 bucket 太粗：Prometheus 估算偏差大
        - 分位数不可跨实例求平均：应聚合直方图后统一计算
        - 不同工具的分位数算法有差异（插值方式），跨工具对比时注意口径

---

> 作者: [0x5c0f](https://blog.0x5c0f.cc)  
> URL: https://blog.0x5c0f.cc/posts/other/%E8%BF%90%E7%BB%B4%E5%B8%B8%E8%A7%81%E9%A2%98-%E7%BD%91%E7%AB%99%E7%BB%B4%E6%8A%A4/  

