运维常见题-网站维护

目录
本文所引用的核心资料与数据,均由 DeepSeek V4 Flash 辅助生成。为确保内容的可靠性,笔者已对大部分关键论点进行了人工复核与校验。但鉴于大模型的固有局限,本文仍可能存在认知偏差或未尽准确之处。若您在阅读中发现存疑或矛盾之处,欢迎反馈讨论,笔者将及时核实与修正。

🤔 简述 HTTP/1.0、1.1、2、3 区别?

  • HTTP 协议经历了四代演进,每一代解决前一代的核心痛点:连接复用、并发能力、传输效率。

    • HTTP/1.0(1996,RFC 1945)

      • 特点:每个请求/响应使用一个独立的 TCP 连接,请求完成后连接立即关闭(短连接)
      • 问题:一次页面加载需要多个资源(HTMLCSSJS、图片),每个资源都要新建 TCP 连接,TCP 握手(尤其 HTTPSTLS 握手)开销巨大
      • 其他:无 Host 头(同一 IP 上无法在 HTTP 层区分多个域名,即虚拟主机);缓存控制简陋(仅有 Expires/Pragma/Last-Modified,无 Cache-Control/ETag
    • HTTP/1.11997 首版 RFC 20681999 定稿 RFC 2616,现为 RFC 9110/9111/9112

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

      • 二进制分帧:把 HTTP 消息拆成二进制帧(HEADERSDATA 等),替代 HTTP/1.1 的纯文本格式
      • 多路复用(Multiplexing :多个请求/响应可以在同一个 TCP 连接上并行交错传输,解决了应用层的队头阻塞
      • 头部压缩(HPACK :压缩首部(请求和响应两侧),减少重复头部传输
      • 服务器推送(Server Push :规范层面仍保留(RFC 9113 §8.4),但 Chrome 106(2022-09)Firefox(2022)已移除实现、Safari 从未实现,实际未普及;HTTP/3 无此功能,业界替代是 103 Early HintsRFC 8297
      • 流优先级RFC 7540 的依赖-权重方案已被 RFC 9218Extensible Prioritization2022)取代(原方案"并不成功")
      • 局限:虽然解决了应用层队头阻塞,但 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
      • 头部压缩用 QPACKRFC 9204)而非 HPACK(应对乱序到达的首部)
      • 部署现状:W3Techs 2026-08 数据显示约 40% 的网站已支持 HTTP/3
    • 四代对比速览

      • 1.0:短连接,一请求一连接
      • 1.1:持久连接,但有队头阻塞
      • 2:多路复用(解决应用层队头阻塞),基于 TCP
      • 3:多路复用 + 解决传输层队头阻塞,基于 UDP/QUIC
  • 协助记忆

    • 一代一痛点:1.0 连接费(短连接)→ 1.1 连接省(持久连接)但排队(队头阻塞)→ 2 并行(多路复用)但堵在 TCP3 换路(QUIC/UDP)彻底不堵
    • 队头阻塞有两个层面:应用层(HTTP/1.1FIFO)和传输层(TCP 包丢失重传) ——— HTTP/2 解决前者,HTTP/3 解决后者
  • 进阶思考

    • 为什么 HTTP/2 多路复用后还要升级到 HTTP/3

      • HTTP/2 解决的是应用层队头阻塞——多个请求可以在一个 TCP 连接上并行,不需要按序等待。但 TCP 本身有传输层队头阻塞:TCP 保证数据按序交付,如果某一个 TCP 段丢失,接收方必须等待该段重传才能继续处理后续数据,即使这些数据属于完全不同的请求流。在丢包率高的弱网环境(移动网络)这个问题尤其严重。HTTP/3QUICUDP)解决了这个问题——每个流独立处理,一个流的丢包不影响其他流
    • HTTP/3 用了 UDP,可靠性和顺序性怎么保证?

      • QUICUDP 之上自己实现了可靠传输和有序交付——它有类似 TCP 的序号、确认、重传机制,但作用域是"流"而不是整个连接。每个流独立编号、独立确认、独立重传,所以一个流的丢包只重传该流的数据,不影响其他流。这也是 QUIC 解决队头阻塞的本质:把 TCP 的"全局有序"变成 QUIC 的"流内有序"

🤔 HTTP 有哪些常见的状态码?

  • 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:方法强制改为 GETSHOULD use GETRFC 9110 §15.4.4)——常用于表单提交后重定向到结果页
      • 304 Not Modified:资源未修改,可使用缓存(配合 If-Modified-Since / ETag
      • 307 Temporary Redirect:临时重定向,且保留请求方法和请求体
      • 308 Permanent Redirect:永久重定向,且保留请求方法和请求体
      • 方法语义完整图谱301/302MAY 改方法)→ 303SHOULDGET)→ 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 504502 是上游答了但答错(无效响应)、503 是自己没空(过载)、504 是上游没答(超时)

  • 进阶思考

    • 301308 到底有什么区别?什么时候用哪个?

      • 核心区别是请求方法是否保留。301(和 302)在重定向时,很多浏览器和客户端会把 POST 请求改为 GET 请求(RFC 使用 MAY,历史行为普遍存在);303 明确要求改用 GET308(和 307)明确要求保留原方法和请求体。实践中:永久迁移且希望客户端用新 URL 重新发起 GET 请求,用 301API 场景希望 POST/PUT 的请求体和方法原样转发到新地址,用 308。现代 WebAPI 重定向推荐 308/307 以保证语义一致
    • 反向代理场景,502503504 分别对应什么排查方向?

      • 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 通常是 hostsDNS Client 缓存 → DNS 服务器,Linuxnsswitch.conf 默认 files dns 通常 hosts 优先)
      • 客户端向递归 DNS 服务器发起递归查询;递归解析器对根 DNS → 顶级域 DNS.com)→ 权威 DNSexample.comNS)的逐级查询称为迭代查询(浏览器本身不逐级问根服务器)
      • 细节:现代浏览器和系统可能启用 DNS-over-HTTPSDoHRFC 8484),DNS 查询走 HTTPS 加密通道
    • 第三步TCP 连接(三次握手)

      • 拿到 IP 后,浏览器与服务器建立 TCP 连接:SYNSYN-ACKACKRFC 9293
      • 涉及 TCP 相关优化:Fast OpenTFO)、连接复用(keep-alive);若启用 HTTP/3TCP+TLS 合并为 QUIC 一次握手
    • 第四步TLS 握手(HTTPS 专属)

      • HTTPS 下进行 TLS 握手(以 TLS 1.3 为主,现行规范 RFC 9846):

        • 客户端发送 ClientHello(支持的加密套件、TLS 版本、key_share 密钥共享)
        • 服务器返回 ServerHello(含密钥共享)、证书;证书在加密消息中发送(EncryptedExtensionsCertificateCertificateVerifyFinished
        • 客户端验证证书(信任链、域名匹配、有效期)
        • 双方确认,握手完成,开始加密通信
      • 注: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-TypeCache-ControlSet-Cookie 等)、响应体(HTML
      • 浏览器判断状态码2xx 正常渲染,3xx 跟随重定向(重新走流程),4xx/5xx 显示错误页
    • 第八步:浏览器渲染

      • 解析 HTML 构建 DOM
      • 解析 CSS 构建 CSSOM
      • DOM + CSSOM 合并生成渲染树(Render Tree) (注:Render TreeWebKit/Blink 的实现术语,非统一 Web 标准定义,GeckoFrame tree
      • 布局(Layout/Reflow) :计算每个元素的几何位置
      • 绘制(Paint) :绘制到屏幕
      • 合成(Composite) :把各层合并呈现 ——— transform/opacity 动画只触发合成,不触发重排重绘
      • 脚本与样式对渲染的影响<script>parser-blocking(阻塞 DOM 解析进而阻塞渲染,除非 async/defer<script type="module"> 默认 defer),但现代浏览器有预加载扫描器提前并行下载脚本(下载并行、执行阻塞);CSSrender-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 三次握手(1RTT);HTTPS 除了 TCP 还要 TLS 握手 ——— TLS 1.2 需要 2RTTTLS 1.3 需要 1RTT(首次),加上 TCP 总共 2~3RTT 才能发出第一个请求。RTT 越大(物理距离越远),差距越明显。优化手段:TLS 1.3 会话恢复(0-RTT,需权衡重放风险)、HTTP/3QUICTCP+TLS 合并为一次握手)、连接复用与 TFO。在 HTTP/3 或连接复用场景下 HTTPSHTTP 差距已不明显,但全新连接下 HTTPS 仍多约 1RTT
    • 输入域名后浏览器直接显示"无法访问此网站",最可能是哪一步出了问题?

      • 按经验上最常见的顺序:一是 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/htmlapplication/json
      • Accept-Encoding:客户端支持的压缩算法(gzipbrzstd —— 2024-2025 主流浏览器已支持)
      • Accept-Language:客户端语言偏好
      • Content-Type / Content-Length:请求体类型和长度(POST/PUT 时;注意这属于"表示头/内容头",同一字段在请求和响应中含义一致)
      • Authorization:认证凭据(Bearer tokenBasic
      • Cookie:客户端携带的 cookieRFC 6265 定义,注意不是 RFC 9110
      • Referer:来源页面 URL(注意拼写是 Referer 而非 Referrer
      • Origin:跨域请求的来源(协议+域名+端口,CORS 用)
      • X-Forwarded-For:经过代理时的原始客户端 IP(非标准头,事实标准;标准替代为 ForwardedRFC 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:跨源隔离相关头
  • 协助记忆

    • 请求头记一组HostUAAccept 家族、CookieAuthorization——“你是谁、要什么、带什么”
    • 响应头记一组Content-TypeSet-CookieLocationCache-ControlETag ——— “给你什么、让你存什么、跳去哪”
    • 排查缓存问题看这四个Cache-ControlETagLast-ModifiedExpires
    • 区分两个"内容"Content-Type(媒体类型)≠ Content-Length(字节长度)
  • 进阶思考

    • CookieAuthorization 都能传递身份信息,有什么区别?

      • 机制不同。Authorization 是请求头,每次请求由客户端代码显式携带(如 Bearer token),无状态、常用于 APICookie 是浏览器自动管理的状态机制——服务器 Set-Cookie 后,浏览器自动在后续请求中带上对应域名的 cookie,有状态、常用于 Web 会话。Cookie 会自动附加到同域请求(不受代码控制),Authorization 需要代码显式添加。安全上:Authorization 适合 API tokenCookie 需配合 Secure/HttpOnly/SameSite 防窃取
    • Nginx 反代场景,如何保证后端能看到真实客户端 IP

      • Nginx 在转发请求时把客户端真实 IP 写入 X-Forwarded-ForX-Real-IP 头,后端应用从这些头读取。但 X-Forwarded-For 是可伪造的——客户端可以直接构造这个头。安全做法:在 Nginx 层用 set_real_ip_from + real_ip_header 处理,或在信任的代理边界重写 X-Forwarded-For(不要直接信任客户端传入的值)。这是反代场景的常见安全坑

🤔 网站显示中文乱码会是什么原因?

  • 中文乱码的根本原因是编码不一致——网页的字节是按 A 编码写入的,但浏览器用 B 编码解读,导致字节序列被错误解码。排查方向就是找出"写入编码"和"读取编码"哪里对不上。

  • 编码机制速览(先理解再排查)

    • UTF-8:现代标准,兼容 ASCII,中文字符占 3 字节,全栈默认。2025-2026UTF-8 已占全网超 98%,且 Encoding 标准已把 UTF-8 定为新协议的强制编码
    • GBK / GB2312:中文传统编码,中文字符占 2 字节,旧系统常见(注意 GBKGB2312 的超集,现行国标是 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 响应头 charsetmeta 声明冲突

    • HTTP 响应头 Content-Type: text/html; charset=UTF-8HTMLmeta 声明不一致时,HTTP 头的优先级更高
    • 排查curl -I <url> 查看响应头 charsetcurl -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,旧的 utf8utf8mb3)已废弃、未来大版本移除
  • 常见原因四:编码被二次转换

    • 数据经过多个环节时,某个环节做了一次错误的编码转换
    • 是否可逆取决于原始字节是否被改写:纯显示层乱码(字节仍是原始 UTF-8,只是被按 GBK 显示)可逆 ——— 著名例证是"锟斤拷" (UTF-8 替换符被按 GBK 显示);只有经真实转码(如 iconv 转错后落库)才不可逆
  • 常见原因五:页面完全没有编码声明

    • HTTP 头无 charsetHTML 内也无 meta 声明时,浏览器按默认(现代浏览器默认 UTF-8)尝试解码,非 UTF-8 字节就会乱码
    • 这比"用户改了浏览器设置"常见得多——排查时先确认页面是否有编码声明,而不是去查浏览器设置
  • 协助记忆

    • 一句话:乱码 = 写的时候用 A 编码,读的时候用 B 编码,对不上
    • 读取侧优先级(从高到低) :HTTPcharsetBOMmeta 声明 → 默认 UTF-8
    • 写入侧检查:文件实际编码(iconv 双向试转)、数据库/连接字符集
    • 排查命令:curl -I 看响应头、iconv -f X -t Y 文件 试转换、chardet 检测
    • utf8mb4 记住:MySQLemoji 必须用 utf8mb4 而非 utf8
  • 进阶思考

    • HTTP 响应头 charsetHTML 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(连接时声明的客户端字符集)解释应用发来的字节——如果连接用的还是 latin1GBK,写入时就错了。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_connecttime_namelookup
        • TLS 握手耗时 = time_appconnecttime_connect
        • 服务器处理时间 = time_starttransfertime_appconnect(近似)
      • TTFB(time_starttransfer) :从 curl 启动到收到第一个字节的累计时间,包含 DNS 查询 + TCP 握手 + TLS 握手 + 服务器处理(MDN 定义)

      • 判断方法DNS 差值长 → DNS 问题;TCP 差值长 → 网络问题;DNS/TCP/TLS 各阶段差值都正常但 TTFB 仍长 → 服务器处理慢;Total 长但 TTFB 正常 → 响应体传输慢(带宽/大文件)

    • 第二步:按阶段定位

      • DNSdig + time = 2 example.com 看解析耗时,dig @8.8.8.8 example.com 对比公共 DNS;检查本地 DNS 服务器、递归解析器、上游权威、DNSSEC;考虑 DoH/DoT。注意:智能 DNS/CDN 解决的是"解析到哪个节点"的调度准确性,不直接加速解析耗时本身
      • 网络慢pingRTTmtr 看中间链路是否有丢包或高延迟节点;跨地域访问考虑 CDN2025 新坑:HTTP/3UDP,部分网络对 UDPQoS 限速/防火墙拦截——如果 time_connect 长但 ping 正常,可怀疑 UDP 被限速
      • 服务器响应慢(TTFB 高) :进入服务器端排查(第三步)
      • 传输慢(TotalTTFB 长) :响应体太大、带宽不足、压缩未开(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/freeCPU、内存是否打满
        • iostat 看磁盘 IO 是否瓶颈
        • 连接堆积用 ss -s(汇总统计)或 ss -ant state established | wc -l 看连接数(注意 ss -lntp 只显示监听中的 socket,看不到 ESTABLISHED 堆积;-lntpSend-Q 列可看 backlog 溢出)
    • 第四步:前端/浏览器侧(如果服务器很快但用户觉得慢)

      • Chrome DevToolsNetwork 面板:看每个资源的加载时间,Waterfall(瀑布图)能看出哪些资源是瓶颈
      • Core Web Vitals2025 年核心指标)LCP(最大内容绘制)、INP(交互延迟,2024 年取代 FID)、CLS(布局偏移)——用 LighthouseRUMCrUX 字段数据)审计
      • 常见前端问题:页面引用了太多大资源、图片未优化、未用 CDN、缓存策略未配置
      • 注意HTTP/2 时代"JS/CSS 合并文件"已不必要(多路复用下合并反而有害),现代实践是 bundling + code splitting + tree-shaking + 按需加载
      • 补充103 Early Hints 可提前 TTFBServer-Timing 响应头可暴露服务端耗时
  • 协助记忆

    • 分层定位口诀:先 curl 计时做差分层,再按层深挖 ——— DNS 慢查解析,TCP 慢查网络,TTFB 慢查服务器,Total 慢查传输
    • curl 变量是累计值,算阶段要"做差"TCP = connectnamelookupTLS = appconnectconnect,服务器 = starttransferappconnect
    • 排查顺序:网络层 → 服务器层 → 数据库层 → 缓存层 → 前端层,别跳过
  • 进阶思考

    • TTFB 高但服务器 CPU/内存都正常,可能是什么原因?

      • TTFB 包含网络 RTT + 服务器处理。如果 CPU/内存正常,可能的隐藏原因:一是数据库慢查询——请求卡在等数据库返回,但数据库 CPU 不高(如缺索引全表扫描、锁等待);二是外部依赖慢——应用调第三方 API,等外部响应;三是连接池/线程池打满——请求排队等待可用连接;四是磁盘 IO——日志或缓存写盘慢。排查:看应用日志确认请求在等什么,必要时用 APM(链路追踪)定位耗时分布
    • 用户分布广(全国/全球),如何区分是"网站慢"还是"用户网络慢"?

      • 用多点拨测——在不同地域的机器上执行同样的 curl 计时(云厂商都有多地拨测工具)。如果所有地域 TTFB 都高 → 服务器端问题;如果只有某些地域高 → 网络/CDN 节点问题(该地域到服务器链路差,或 CDN 在该地域节点少)。这是"网站慢"与"网络慢"的经典区分方法

🤔 QPS、TPS、PV、UV 是什么?怎么统计?

  • QPS/TPS 是实时性能指标(衡量系统处理能力),PV/UV 是业务流量指标(衡量用户访问量)。两者维度不同,但常放在一起讨论。

  • QPSQueries Per Second,每秒查询数)

    • 含义:系统每秒处理的请求/查询数量,衡量系统吞吐能力

    • 注意QPS 在不同语境含义不同 ——— Web 场景指每秒 HTTP 请求数(严格说这是 RPSRequests Per Second),数据库场景指每秒 SQL 查询数。中文语境常混用,面试和讨论中要明确语境

    • 统计方法

      • 监控系统Prometheus + nginx-prometheus-exporter(从 nginx stub_status 取数,nginx_http_requests_total 速率为 QPS)或应用埋点
      • Nginx access log:按时间窗口统计日志条数
      • 压测工具abwrkJMeter 直接报 QPS
    • 相关概念:峰值 QPS(扩容依据,行业更常用最大 5 分钟窗口均值或 TP99,而非秒级瞬时毛刺值)vs 平均 QPS(容量规划参考)

  • TPSTransactions Per Second,每秒事务数)

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

    • 含义:页面被浏览的次数。每次刷新/打开页面都算一次 PV,同一用户重复浏览累加

    • 统计方法

      • Nginx access log:统计 HTML 页面请求数(需过滤静态资源、排除非 2xx、过滤爬虫)
      • 前端埋点 / RUM(真实用户监测)
    • 爬虫污染2025AI 爬虫(GPTBotClaudeBot 等)激增,严重污染 PV/UV 统计,行业标准做法是 UA 白名单过滤

    • 注意 PVQPS 不能直接换算:1PV(页面加载)通常产生 1HTML + 数个静态资源/接口请求(1 PV5~20 个请求);容量规划估算:峰值 QPS ≈(日均 PV × 页面平均请求数 × 峰值系数)/ 86400

  • UVUnique Visitor,独立访客数)

    • 含义:去重后的独立访客数,同一用户只算一次

    • 统计方法(去重口径,不同口径结果不同)

      • 基于 Cookie / 第一方标识:给访客分配唯一 ID 去重。2025 年隐私环境下的主流已演变为"第一方 Cookie + 登录 ID + 设备指纹"的混合识别(GA4 等基于 first-party 标识去重)——— 因为第三方 CookieSafari/Firefox 默认拦截、GDPR/个保法限制下受限(Chrome 2024 年放弃强制淘汰第三方 Cookie2025 年终止 Privacy Sandbox 计划)
      • 基于 IP:按 IP 去重(不准确 —— NAT 后多人同 IP1 个;同一人换 IP 算多个)
      • 基于用户登录 ID:最准确,但只能统计登录用户
    • 注意:UV 是"人"的维度,PV 是"次"的维度。PV/UV 比值 = 人均浏览页数 ——— 不能简单解读为"内容吸引",比值高也可能因导航差反复点击、长文分页;需结合跳出率、停留时长解读

  • 四个指标的维度对比

    • QPS:系统处理能力(实时、按请求数)
    • TPS:业务事务处理能力(实时、按事务数)
    • PV:页面访问量(累计、按次数)
    • UV:独立访客数(累计、按人去重,与 QPS 无换算关系)
  • 协助记忆

    • QPS 是"每秒能扛多少请求",TPS 是"每秒能完成多少业务",PV 是"被看了多少次",UV 是"有多少人来看"
    • QPS/TPS 看系统强不强,PV/UV 看业务火不火
    • PV/UV 比值:人均看几页,需结合跳出率解读
  • 进阶思考

    • 如何从 Nginx access log 统计 QPSPV

      • combined 格式时间戳如 10/Feb/2025:14:23:45$4 是完整时间戳。按分钟统计:awk '{print $4}' access.log | cut -d: -f2-3 | sort | uniq -c(必须 sortuniq,多 worker 写日志时同分钟行不连续,不 sort 会重复计数);按秒看峰值:cut -d: -f2-4 | sort | uniq -c | sort -rn | head。统计 PV:过滤静态资源 + 非 2xx + 爬虫 ——— grep -E '\.(html|htm)'URLquery string 或动态路由站点会失效,更可靠的是按 URL 类型/路径前缀区分或用 GoAccessELK 等分析工具。QPS 更推荐用监控系统(nginx-prometheus-exporter)而非日志
    • UV 统计中"基于 Cookie“和"基于 IP“哪个更准确?为什么行业多用 Cookie

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

    • ·

      • Apache HTTP Server 自带的基准测试工具,单进程模型(-cfork 子进程,非线程),无 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,阈值/SLOCI 集成,2025 年事实上的新一代主流
      • Locust(活跃,Microsoft 赞助):纯 Python 写场景,gevent 协程 + Web UI + 分布式,适合高并发用户模拟
      • VegetaGo,恒定速率 + UNIX 组合式 CLI + Go 库,内置 Prometheus exporter
      • heyGo 单文件,自称 “ApacheBench (ab) replacement",HTTP/2、限速、CSV 输出,小而够用
      • oha(活跃)Rust + tokio,实时 TUIHTTP/2/3(实验)、burst--latency-correction(同样修复协调遗漏)
      • 高分位延迟的正确性wrk2 / Vegeta / oha 都处理协调遗漏,abwrk 不处理
    • APMApplication Performance Monitoring,应用性能监控)

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

    一句话选型:快速冒烟测试用 ab;命令行脚本化压测用 wrk/wrk2heyCI 常态化压测首选 k6Python 团队用 Locust;多协议/团队协作用 JMeter;自托管链路追踪用 Jaeger;全栈 APM 自托管用 SkyWalking

🤔 简述 HTTP 和 HTTPS 区别?

  • HTTPHTTPS 的核心区别:HTTPS 是在 HTTPTCP 之间插入了一层 TLS(旧称 SSL)加密层,保证传输安全。HTTP 是明文传输,HTTPS 是加密传输。

    • HTTP(HyperText Transfer Protocol)

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

      • 加密传输:在 HTTPTCP 之间加了一层 TLSTransport Layer Security,旧称 SSL) ,数据加密后传输

      • 默认端口 443

      • 提供三方面安全能力

        • 机密性:内容加密,中间人抓包只能看到密文(但 SNI 域名与流量元数据仍可见,这正是 ECH 要解决的——见下文)
        • 完整性AEAD 认证加密(TLS 1.2 及以前用 MAC)保证数据未被篡改
        • 身份认证:通过服务器证书验证服务器身份,防止中间人冒充
      • 证书机制:服务器需要部署 CA(证书颁发机构)签发的证书,包含公钥和服务器身份信息,客户端验证证书的信任链

    • HTTPS 的建立流程(简版)

      • TCP 三次握手 → TLS 握手(协商加密套件、交换密钥、验证证书)→ 加密的 HTTP 通信
      • TLS 1.3 首次完整握手 1-RTTPSK 会话恢复通常仍为 1-RTT;开启 early data0-RTT)可零往返发送幂等请求,但有重放风险、无前向保密,规范要求默认不启用(部分 CDNQUIC 0-RTT 会开启)
    • 性能差异

      • HTTPSHTTP 多一次 TLS 握手开销(首次连接多 1~2RTTTLS 1.31-RTTTLS 1.22-RTT
      • 现代优化(TLS 1.3、会话恢复、HTTP/3 + QUIC 0-RTT)已让差异很小
    • 应用现状(截至 2026 年)

      • HTTPS 已是绝对主流:主流浏览器对 HTTP 站点显示"不安全"警告,搜索引擎降权 HTTP 站点;TLS 1.32018 发布以来已广泛部署,TLS 1.2 是主要回退版本
      • 免费证书普及:Let's Encrypt 提供免费自动化证书(certbot),HTTPS 部署成本几乎为零
      • HSTS 强制浏览器只走 HTTPS ——— 注意其生效前提是首次请求已走 HTTPS(或加入 preload list),否则第一个请求仍可能被 SSL-strip 降级
      • ECHEncrypted Client Hello20263 月标准化为 RFC 9849) :加密整个 ClientHello(含 SNI 域名),配合 DoH 防域名泄露——解决"抓包只见密文但域名仍可见"的最后一块短板。Chrome/Firefox 已默认启用,OpenSSL 4.0nginx 已支持
      • 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 基于 QUICQUIC 在设计上强制内置 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 复制(已过时,不推荐)

      • 各服务器之间互相复制 SessionTomcat 集群两种实现:DeltaManagerall-to-all,每台机器保存全量 Session)和 BackupManager(只复制到一台备份节点,主备模式)
      • 细节:成员发现/心跳走 multicast(默认 228.0.0.4:45564),会话数据复制走 TCP ——— 大集群下 all-to-all TCP 复制 + multicast 心跳的网络开销剧增
      • 已基本被淘汰,适合小集群
    • 实现方式二Session Sticky(粘性会话,治标不治本)

      • 负载均衡器把同一个用户的请求始终转发到同一台服务器(基于 IP hashCookieURL 参数)
      • 优点:实现简单,Session 仍可存在单台机器内存
      • 缺点:只是"绕开"了问题而不是解决——某台服务器宕机,粘在上面的用户全部掉线;扩容/缩容会破坏 hash 映射(用一致性哈希可大幅减少重映射,如 Nginx hash ... consistent);无法做到真正的负载均衡(热点用户集中在一台机器)
      • 定位:不推荐作为唯一会话方案(云原生场景如 K8s ingress/ALBsticky 仍是常规实践,但通常与集中共享叠加使用)
    • 实现方式三:集中式 Session 存储(主流方案)

      • Session 从各服务器内存中抽出来,存到一个集中存储(Redis / Memcached
      • 所有服务器从同一个地方读写 Session,天然共享
      • Redis 方案(最常用):支持 TTL 过期,正好匹配 Session 的过期机制;读写快、支持集群
      • 框架支持:JavaSpring Session(支持 Redis/JDBC,切换不改应用代码)、Hazelcast/Infinispan(社区扩展)、云托管 Redis
      • 优点:服务器无状态、支持水平扩展、单台服务器宕机不影响其他机器
      • 注意点:Redis 是新的单点——本身需要高可用(主从、哨兵、集群);Session 是热数据,Redis 内存开销需要考虑
    • 实现方式四:客户端存储 / 无状态 Token(趋势方案)

      • 不把会话状态存在服务器,而是放到客户端

        • JWT(JSON Web Token) :签名后的 token 存在客户端(localStorage/Cookie),服务器验证签名即可,无需存储 Session。注意"无法主动失效"是简化说法——严格可用黑名单/jti/版本号实现服务端撤销,但代价是重新引入状态
        • 签名/加密 Cookie 直接存会话数据(如 Rails cookie_storeFlaskDjango signed_cookies):注意这是"消除共享需求"而非"共享"——数据在 Cookie 里服务端已无状态,不应再叫 Session;受 Cookie4KB 大小限制
      • 优点:服务器完全无状态、天然支持分布式、不需要额外 Session 存储

      • 缺点:JWT 主动失效难、token 泄露后有攻击窗口、体积比 Session ID

      • 适用:API/前后端分离场景主流

    • 2026 年演进补充

      • OIDC/SSO(企业认证标准,基于 JWT)成为多应用统一登录的主流方案
      • Passkeys/WebAuthn(无密码认证)兴起,减少对传统 Session/Cookie 的依赖
      • 第三方 Cookie 逐步淘汰对依赖 CookieSession/Sticky 方案有冲击;SameSite 默认 Lax 影响跨站 Cookie 会话
    • 方案对比速览

      • Session 复制:已淘汰(同步开销大)
      • Sticky:临时过渡(宕机掉线、不均衡)
      • Redis 集中存储:主流(服务器无状态、支持扩展)
      • JWT 无状态:趋势(完全无状态、但难失效)
  • 协助记忆

    • Session 共享的本质:把"存在单台机器内存里的会话"搬到"所有机器都能访问的地方"
    • 一条主线Session 存储位置从"服务器内存"→“集中存储(Redis)"→“客户端(JWT)",越来越无状态
    • 选型:传统 Web 应用用 Redis 集中存储(Spring Session);前后端分离/APIJWT
  • 进阶思考

    • RedisSessionJWT 怎么选?

      • 看业务需求。需要主动失效(登出、踢人、封禁)、需要服务端可控(管理在线用户)、传统服务端渲染应用 → RedisSession(服务端可随时删 Session);需要跨域/多端无缝(小程序、AppWeb 共享登录)、追求服务端零存储、API 场景 → JWT。混合方案也常见:JWT 做认证 + Redis 做登出黑名单
    • Session StickySession 共享是二选一吗?

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

🤔 Tomcat 8005、8009、8080 端口分别作用是什么?

  • Tomcat 的三个经典端口各有分工:8005Shutdown(关闭)端口、8009AJP 端口(与 Apache httpd 集成)、8080HTTP 端口(对外服务)。都在 conf/server.xml 中配置。

    • 8005Shutdown(关闭)端口

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

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

      • 作用Tomcat 对外提供 HTTP 服务的默认端口
      • 配置<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
      • redirectPort="8443":当应用配置了 SSL 要求(web.xmlsecurity-constrainttransport-guarantee=CONFIDENTIAL)时,HTTP 请求自动重定向到 8443HTTPS 端口)
      • 默认 8080 而非 80Unix1024 以下端口需 root 权限而 Tomcat 默认非 root 运行(Windows 无此限制),兼为避免与既有 Web 服务冲突
    • Tomcat 10/11 的变化(2020 年后)

      • 最大变化javax.servletjakarta.servlet 包名迁移(Tomcat 10 起),老应用需改包名才能运行
      • 三个端口的机制与默认配置不变8005/8080 默认启用、8009 默认注释
  • 协助记忆

    • 三个端口一句话:8005Tomcat(管理)、8009ApacheAJP 集成)、8080 给用户(HTTP 服务)
    • 80808443 重定向:HTTP 强制跳 HTTPS 时用(redirectPort
    • 安全三查:8005 别绑外网(禁用前先配 CATALINA_PID)、8009 别裸奔(Ghostcat)、8080 按需开放
  • 进阶思考

    • 8005 端口关闭命令的安全性如何保障?

      • 8005SHUTDOWN 默认字符串是固定的 SHUTDOWN,一旦端口可达任何人都能关掉 Tomcat。保障手段:确保监听 127.0.0.1(默认);防火墙限制本机访问;修改 shutdown 字符串;最彻底是 port="-1" 禁用 ——— 但禁用后必须设置 CATALINA_PID 或改用 jsvc,否则 catalina.sh stop 无法优雅停止(它默认走 8005SHUTDOWN
    • 为什么现在很多架构不用 8009 AJP 了?

      • 一是 Ghostcat 漏洞(CVE-2020-1938)暴露了安全风险;二是现代前端架构变了 ——— Nginx 已成为主流反向代理,直接用 proxy_passHTTP(8080)就能完成转发,而且 Nginx 根本不支持 AJP;三是 AJPHTTP/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)追求低延迟用 ZGCJDK 11 实验、15 生产可用)
      • GC 日志-Xlog:gc* 启用 GC 日志,用于排查 GC 停顿
      • 常见误区:盲目调大 -Xmx 不一定提升性能,堆过大反而增加 GC 停顿
    • 第二层:连接器(Connector)优化

      • 连接器模式Tomcat 8.5/9 默认 NIO(装了 tomcat-native 时自动用 APR);Tomcat 11 起默认纯 Java NIOBIO8.5 已移除

      • 线程池参数(Executor

        • maxThreads:最大工作线程数,默认 200。不是越大越好——线程过多导致上下文切换开销增大;建议结合压测确定,一般 200~500
        • minSpareThreads:保底存活线程数(Connector 属性默认 10Executor 上默认 25);注意是"保底水位”,线程按需增长到该水位,并非启动即全部创建
        • acceptCount:等待队列长度,默认 100。高并发时调大(如 500~1000
        • maxConnections:最大连接数,NIO 默认 8192
      • 连接超时connectionTimeout 文档默认 60000ms,但随 Tomcat 发行的默认 server.xml 显式配置为 20000ms ——— 如果自定义 server.xml 未写该属性,实际生效的是 60000mskeepAliveTimeout 控制长连接存活时间

      • 压缩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 200acceptCount 100maxConnections 8192(NIO)
  • 进阶思考

    • Tomcat 线程数(maxThreads)是不是越大越好?

      • 不是。线程是"资源"而非"能力"——每个线程占用栈内存,线程过多导致内存占用大、CPU 上下文切换开销大(频繁切换反而降低吞吐)。正确做法:用压测工具(JMeter/k6)逐步加压,观察吞吐量拐点,在吞吐不再增长的点设置 maxThreads。通常 200~500 是常见范围,但具体要压测确定。还要配合 maxConnectionsacceptCount 一起看——三者共同决定了并发处理能力
    • Tomcat 10/11 有哪些值得关注的性能相关变化?

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

🤔 简述 LVS 的三种模式及其工作原理?

  • LVSLinux Virtual Server)是 Linux 内核内置的负载均衡方案,工作在内核空间(第四层,基于 IP + 端口),性能高。LVS 有三种工作模式:NATDRDirect Routing)、TUNIP 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 也会改写)把请求转发给 RSRS 处理完直接把响应绕过调度器返回客户端
      • 报文流转:请求 → 调度器 → RS;响应 → RS → 客户端(不经过调度器)
      • 关键配置VIP 同时配置在调度器和所有 RS 上 ——— RSVIP 配置在 lo 接口上,并抑制 ARP 响应(arp_ignore=1arp_announce=2
      • 特点
        • 优点:响应不经过调度器,调度器只处理请求流量,性能高,生产环境最常用
        • 缺点RS 和调度器必须在同一二层网络(同网段);RS 端口必须与 VIP 端口相同;RS 需额外配置 VIPARP 抑制
        • 适用:大规模、高流量场景(Web 集群主流)
    • 模式三TUN 模式(VS/TUNIP 隧道)

      • 原理:调度器把请求封装在 IP 隧道(IP-in-IP,也支持 GRE/SIT/GUE)中转发给 RSRS 解封装后处理,响应直接返回客户端
      • 报文流转:请求 → 调度器(封装)→ 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 ipiptunl0 up);封装带来开销和 MTU 问题;配置复杂度高
      • 适用:异地多活、RS 分布在多个机房的场景
    • 三种模式对比速览

      • NAT:请求和响应都过调度器(调度器瓶颈),唯一支持端口映射
      • DR:请求过调度器,响应直接回(同二层网络,最常用),RS 端口必须等于 VIP 端口
      • TUN:请求隧道封装,响应直接回(可跨地域),RS 端口必须等于 VIP 端口
    • 2026 年生态现状

      • ipvs 至今在内核 6.x 中活跃维护;kube-proxyIPVS 模式是 K8s 大规模集群的常用方案
      • 演进方向:DPVSDPDKipvs)、Katran/CiliumXDP/eBPF 负载均衡)、ECMP+BGP 替代 Keepalived/VRRP
  • 协助记忆

    • 一句话区分NAT 是"快递员两头跑"(进出发都经调度器),DR 是"只送件不取件"(请求经调度器、响应直达),TUN 是"打包寄送"(隧道封装跨地域)
    • DR 两个关键VIPlo 接口 + 抑制 ARPTUN 同样要)
    • 选型口诀:同网段高流量用 DR,跨地域用 TUN,简单场景用 NAT
  • 进阶思考

    • DR 模式为什么要抑制 RSARP 响应?不抑制会怎样?

      • VIP 同时配置在调度器和所有 RS 上。如果 RS 不抑制 ARP,局域网设备对 VIP 发起 ARP 请求时所有 RS 都会响应(ARP 是广播的),导致 MAC 地址混乱——有的请求被转发到 RS,有的被 RS 直接抢答。抑制 ARParp_ignore=1arp_announce=2)让只有调度器响应 VIPARPRSVIP 只用于接收转发请求,不对外宣告
    • LVSNginx 负载均衡怎么选?

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

🤔 LVS 支持哪些调度算法?

  • LVSipvs)支持的调度算法分为静态和动态两大类:静态算法不考虑后端实时负载,动态算法根据后端当前负载/连接数做决策。截至 2026 年,ipvs 共有 14 个调度算法(RR/WRR/DH/SH/MH + LC/WLC/SED/NQ/LBLC/LBLCR/FO/OVF/TWOS)。

  • 静态调度算法(不考虑后端实时状态)

    • RRRound Robin,轮询) :请求依次分发到每个 RS,轮流来
    • WRRWeighted Round Robin,加权轮询) :按权重比例分发,权重高的 RS 分到更多请求
    • DHDestination Hashing,目标地址哈希) :按请求的目标 IP 哈希,相同目标 IP 的请求始终分发到同一台 RS(用于缓存场景)
    • SHSource Hashing,源地址哈希) :按客户端源 IP 哈希,相同来源的请求到同一台 RS(天然实现会话保持)。注意"始终"是简化说法——RS 增删/权重变化会重建哈希表,且 SHsh-portIP+端口哈希)和 sh-fallback(故障回退)两个 flag
    • MHMaglev HashingMaglev 哈希,Linux 4.18 合入) :基于源 IP 的一致性哈希(Google Maglev 论文,NSDI'16),表容量按权重分配,支持 mh-port/mh-fallback flag。天然适合会话保持,RS 变化时受影响连接少
  • 动态调度算法(根据后端实时负载决策)

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

    • 通用 Web 集群、无特殊需求:WLCipvsadm 默认)
    • 后端性能差异大:WRR(静态权重明确)
    • 需要会话保持:SH / MH(源地址哈希,RS 变化影响小)或配合持久性(persistence
    • Cache/缓存集群:DHLBLC(目标地址哈希保证缓存命中)
    • 防止单台过载:OVF
    • 高可用故障转移倾向:FO
  • 协助记忆

    • 静态五兄弟:RRWRRDHSHMH(“轮询、加权轮询、目标哈希、源哈希、Maglev 哈希”)
    • 动态九兄弟:LCWLCSEDNQLBLCLBLCRFOOVFTWOS
    • 默认是 WLCipvsadm 缺省) ,大多数场景够用;有特殊需求(会话保持、缓存命中)再换
  • 进阶思考

    • SH(源地址哈希)和持久性(persistence)都能做会话保持,有什么区别?

      • SH 是基于 IP 哈希的确定性分配 ——— 同一源 IP 按哈希到同一台 RS,不依赖连接状态;但 NAT 后面的多个用户共享一个 IP 会被分到同一台(负载不均);且 RS 宕机时哈希到它的用户受影响(可通过 sh-fallback 缓解)。持久性(persistence)是基于连接跟踪的超时绑定——在一定时间窗口(默认 300 秒)内把同一来源的请求绑定到同一台 RS,超时重新分配。持久性更灵活(支持故障转移、负载更均衡),是更推荐的会话保持方式
    • 加权算法(WRR/WLC)的权重怎么确定?

      • 权重反映 RS 的处理能力差异——通常按 CPU 核数、内存大小或压测结果定。例如两台服务器,一台 8 核一台 4 核,权重可设为 2:1。注意:权重是相对值不是绝对值;权重相同(如 1:1)就退化为普通轮询/最少连接。权重要根据实际压测调整,不能拍脑袋
  • CookieSession 都是 Web 应用中记录用户状态的机制,核心区别:Cookie 存在客户端(浏览器),Session 存在服务端。两者常配合使用 ——— Session 通过 Cookie 传递 Session ID 来识别用户。

    • Cookie(客户端状态)

      • 定义:由服务器通过 Set-Cookie 响应头下发的小段文本数据,浏览器保存在本地,后续请求自动通过 Cookie 请求头携带

      • 特点

        • 存储位置:浏览器(客户端)
        • 容量:约 4KB 上限(单个 Cookie4096 字节,浏览器实现惯例;注意 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 对应服务端存储的会话数据
        • 安全性:数据在服务端,客户端无法直接篡改
    • 联系SessionCookie 传递 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
      • 容量:约 4KBCookie)vs 无硬限制(Session
      • 安全性:可篡改(Cookie)vs 服务端可控(Session
      • 生命周期控制:客户端可控制 vs 服务端控制
      • 性能:每次请求都传输(Cookie)vs 服务端查询(Session
      • 跨域Cookie 同域限制 vs Session 数据本身无域概念(但 Session IDCookie 传递时仍受 Cookie 域限制)
    • 2026 年演进

      • SameSite 默认 Lax:自 Chrome 80(2020)/Firefox 86/Safari 13.1 起默认。精确语义:跨站子资源、fetch/XHRiframe 内请求默认不发送,但顶级导航 GET 请求仍携带(用户从别的站点击链接进入本站时会带 Cookie);且默认值在 Chromium 系稳定为 Lax,其他浏览器略有差异
      • 第三方 Cookie 逐步淘汰SafariITP2020 起)默认全面阻止;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
    • CookieSession 哪个更安全?

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

🤔 网站跨域报错 Access-Control-Allow-Origin 如何解决?

  • 跨域报错是浏览器同源策略(Same-Origin Policy)的保护机制:浏览器默认阻止网页脚本跨域读取响应。CORSCross-Origin Resource Sharing)是让服务器声明"允许哪些来源访问"的机制,报错 Access-Control-Allow-Origin 说明服务器没有正确声明允许的来源。

    • 先理解跨域和同源

      • 同源:协议 + 域名 + 端口都相同才算同源。http://a.comhttps://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-urlencodedmultipart/form-datatext/plainapplication/json 会触发预检)
      • 预检请求(Preflight) :非简单请求(如带 Authorization 头、Content-Type: application/jsonPUT/DELETE 方法)——— 浏览器先发 OPTIONS 请求探测,服务器必须正确响应 OPTIONS(且必须返回 2xx)并返回允许的方法/头,才继续发真实请求
      • 2024-2026 新机制 PNAPrivate Network AccessChrome2024 年底(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/apiNginx/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);三是 Nginxadd_header 默认只对部分 2xx/3xx 状态码添加(200/201/204/206/301/302/303/304/307/308),需加 always 参数才对所有响应码添加——且预检必须返回 2xxOPTIONS 返回 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; SecureCookie 会带上——实际攻击面比"任何恶意网站都能带上 Cookie“更小,但"带凭证 + 可读响应"的底线依然不能破
  • 扩展信息
    安全响应头是 Nginx 层可以统一配置的 Web 安全防护。核心是 CSPContent-Security-Policy,内容安全策略)——— 它告诉浏览器"页面允许加载哪些来源的资源”,CSP 是浏览器侧缓解 XSS 最重要、最有效的防御机制之一,但不能替代输入校验(Sanitization)与输出编码(Encoding)等后端/开发层面的安全措施。

    • CSP 是什么
      • CSP 通过响应头声明"允许加载什么",浏览器强制执行:不符合策略的资源(脚本、图片、样式、iframe)被阻止加载
      • 缓解 XSS 原理:即使攻击者注入 <script>CSP 如果没允许内联脚本/不可信来源,浏览器会拒绝执行
      • CSP 的威胁模型:缓解内容注入(XSS)与页面被恶意嵌入(clickjacking——注意这是 UI redress 而非"内容注入")
    • CSP 常用指令
      • default-src:大多数 fetch 类指令的兜底策略。但需注意:frame-ancestorsbase-uriform-actionsandbox 等指令不会继承 default-src(未指定即默认为无限制)。
      • script-src:脚本来源(最关键的指令)
      • style-src:样式来源
      • img-src:图片来源
      • connect-srcXHR/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 应与 noncehash 一起使用。在支持该特性的浏览器中,会忽略传统脚本白名单(如 'self'、主机白名单),改由受信任脚本继续加载其他脚本。)、'nonce-xxx'(每次响应随机生成)
    • CSP 配置案例
      • 基础案例(静态站)Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
      • 进阶案例(带 CDNAPIContent-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 语义一致)
        1
        2
        3
        4
        5
        
        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 站点
      • 策略一致性CSPframe-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
      • CSPX-Frame-Options 都能防点击劫持,用哪个?
        • frame-ancestors(CSP)是现代推荐、X-Frame-Options 是旧方案。frame-ancestors 更灵活(可精确指定多个来源),X-Frame-Options 只有 DENY/SAMEORIGIN 两档。两者同时存在时,CSP 优先(仅在 enforce 模式成立;X-Frame-Options 在响应含 frame-ancestors 时被忽略)。为兼容老浏览器可两者都配,但语义必须一致

🤔 简述 CDN 工作原理?

  • CDNContent Delivery Network,内容分发网络)的核心思想:把内容缓存到离用户更近的节点,让用户从"附近的服务器"而不是"远方的源站"获取内容。本质是"空间换时间"——用分布在全国/全球的缓存节点,换取更短的访问延迟。

    • CDN 的核心组件

      • 边缘节点(Edge Node / POPPoints of Presence :分布在各地区/运营商的缓存服务器,真正响应最终用户请求
      • 中心节点 / 源站(Origin :内容的原始来源(你的服务器)
      • GSLBGlobal 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 中心,边缘未命中常先查上层节点而非直接回源)
      1. 后续同地区用户请求同一内容,直接命中边缘节点缓存
    • CDN 缓存与回源的关键点

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

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

      • 已是标配(成熟多年)HTTP/3QUIC 2021 标准化、HTTP/3 2022 标准化,主流 CDN 2019 年已商用,如今是标配);CDN 证书托管/自动续期(用户侧边缘证书 + 源站回源证书是两个独立维度,可选 mTLS);基础边缘计算(Cloudflare Workers 2017Lambda@Edge 2017
      • 当前热点(2023-2026) :边缘 AI 推理(Workers AIGPU 边缘节点、AI Gateway)、边缘无服务器渲染/SSRVercel/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

      • 工作层级:传输层(OSI4 层),基于 IP + 端口转发,不解析应用层内容(需解析 TCP/UDP 头)

      • 代表产品LVSHAProxytcp 模式)、云厂商 NLBKatranMeta 开源,XDP/eBPF 转发面) ;F5 BIG-IP LTML4L7 一体化 · 设备(不能仅归 L4);Nginxstream 模块也能做 L4

      • 转发方式NATDRDSR,直接服务器返回——返回流量不经过 LB)、隧道(LVS 三模式)

      • 特点

        • 性能高:内核态或硬件转发,权威性能指标是 pps(每秒包数)而非并发连接数
        • 透明:看不到 HTTP 内容
        • 无法做URL 路由、内容改写、Cookie 会话保持(传统上基于源 IP/五元组哈希——注意标准术语是五元组:源/目的 IP+端口+协议,Katran 即用 5-tuple 一致性哈希)
      • TLS:传统软件 L4LVSnginx stream 透传、HAProxy tcp passthrough)只能透传,不能终止;但现代云 L4AWS/阿里云 NLB 等,2019 年起)支持 TLS 监听器/终止(多在网卡/硬件卸载,ACM 管理证书);另有灰色地带——L4 可只读 TLS ClientHelloSNI 做路由而不解密(nginx stream ssl_preread

      • 健康检查:传统 LVSTCP 端口探测;现代 L4NLB 等)同样支持 HTTP/HTTPS 健康检查与响应码匹配

      • 适用:高吞吐场景、TCP/UDP 通用协议、作为七层的入口

    • 七层负载均衡(L7

      • 工作层级:应用层(OSI7 层),解析 HTTP 内容

      • 代表产品NginxHAProxyhttp 模式)、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 终止吗?

      • 分情况。传统软件 L4LVSnginx stream 透传、HAProxy tcp passthrough)不能 ——— TLS 终止需要解密报文,L4 只转发加密 TCP 流,只能透传到后端解密。现代云 L4AWS/阿里云 NLB 等,2019 年起)支持 TLS 监听器,多在网卡/硬件上卸载加解密。所以"HTTPS 业务 + TLS 终止"传统选 L7Nginx/ALB),但现代架构也可以用支持 TLS 的云 L4 做入口
    • 四层负载均衡的性能优势有多大?

      • L4 权威指标是 pps(每秒包数)——— KatranXDP/eBPF 方案在 DPDK/网卡卸载下可达千万级 ppsL7Nginx)通常几万到几十万 QPS(受 HTTP 解析和 TLS 开销限制),数字随硬件/配置浮动。差距来自:L4 不解析应用层内容、L7 要解析 HTTP 报文且常做 TLS 终止。注意量纲:L4 的"百万级"通常是并发连接数/ppsL7 的"几万"是 QPS ——— 两者不是同一个量纲,对比要说清楚

🤔 网站报错 502 Bad Gateway 怎么解决?

  • 502 Bad Gateway 的含义:网关/反向代理(Nginx)成功收到了请求,但从上游(upstream)没有得到有效响应——即"请求到了 Nginx,但 Nginx 没能从后端拿到结果"。定位核心:先区分是 Nginx 自己产生的 502,还是后端返回的 502,再看 error log 定位。

    • 第一步:区分 502 是谁产生的

      • 后端自己返回 502Nginx 只是透传 ——— error log 无记录,access log$upstream_status=502(此时去后端日志查)
      • Nginx 产生 502error logupstream 相关记录(此时按下面的 error log 信息定位)
      • 判断方法tail -f /var/log/nginx/error.log 看有没有报错,再看 access logupstream_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. 后端端口不通(防火墙/安全组/监听地址)refusedtimeout

        • 检查:后端进程监听 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() failedupstream 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 正常返回 500Nginx 透传 500fpm 进程崩溃/连接中断 → Nginxprematurely closed 产生 502 ——— 看 fpmerror log 区分
      8. 连接被对端提前关闭upstream prematurely closed connection while reading response header from upstream

        • 最高频场景:后端进程崩溃/重启(PHP-FPM reloadJava OOM kill、容器 Pod 重启)
        • 一般 Nginx 会按 proxy_next_upstream(默认 error timeout)自动重连/换后端,无需人工 reload;若配置了 keepalive 且后端频繁重启,可调小 keepalive
      9. 后端连接数满 / 队列溢出refusedtimeout(取决于 tcp_abort_on_overflow)+ 后端日志报 accept queue 满——后端负载过高

      10. 资源耗尽:后端内存不足(OOM)、文件描述符耗尽——检查 dmesg | grep -i oomulimit -n

      11. 容器/K8s 场景(2026 年高频)Pod 重启/滚动更新、ServiceEndpointDNS 解析失败(no resolver defined to resolve)、网络策略未放行 Pod IP

    • 排查步骤(应急 → 根治)

      1. 先看 Nginx error log + access logtail -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)、fdulimit -nss -s
      6. 定位后修复:重启服务、调超时、调 fpm 参数、改权限、扩容
  • 协助记忆

    • 一句话502 = 网关替后端"背锅",后端没给它结果
    • 三步定位:先看 error log(区分透传)→ 再 curl 后端 → 最后查服务状态
    • 记忆口诀refused = 连接被拒(没监听或防火墙 REJECT);timeout = 网络丢包不通(DROP)或后端太慢;prematurely closed = 连接被对端掐断(后端重启/崩溃)
  • 进阶思考

    • 502503 有什么区别?

      • 502 是"网关拿到了请求、但上游没给出有效响应"(后端挂了、超时、端口不通、响应头无效);503 Service Unavailable 是"服务暂不可用"——通常是主动拒绝(负载过高限流、维护模式、健康检查不过被摘除)。Nginx 场景:upstream 全部无健康节点时可能返回 502503(取决于配置),两者都要查后端
    • 后端明明在跑,为什么还 502?

      • 最常见三个:① 监听地址不对——进程活着但监听 127.0.0.1Nginx 从别的 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 流量突增前兆;黑洞若发生在运营商侧,LBQPS 也会归零)
      • WAF 误拦:误封 IP、规则误触发导致流量被拦
    • 应用类

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

      • 缓存雪崩:缓存大批量过期,请求全部打到 DB ——— 初期特征是缓存命中率骤降 + DBQPS/负载飙升 + RT 飙升,对外吞吐下降是 DB 过载后的结果,不是判断特征
    • 资源类CPU 打满、内存 OOM、磁盘满

    • 数据库类:慢查询、锁等待、连接数打满

  • 快速排查命令

    1. 看监控大盘:各层 QPS、错误码、RT、缓存命中率曲线(先确认数据真实,再定位掉在哪一层)
    2. 看错误码分布:统计 access log 状态码(先 head -1 确认字段位置 —— $9 只在默认 combined 格式下是状态码,自定义 log_format 后要换字段序号)
      1
      2
      
      head -1 /var/log/nginx/access.log
      awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
    3. 看后端资源topvmstat 1free -hiostat -x 1
    4. 看连接ss -sss -lntp-proot
    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 默认拒绝返回 503limit_req_status 可改为 429),看到大量 503/429QPS 压线稳定,可确认。熔断同理:看熔断器状态指标(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)、(可选)吊销状态——浏览器默认不做实时吊销检查(ChromeCRLSetsFirefoxCRLiteOCSP2023 年起可选)
      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 PKIRSA 仍占绝大多数)
  • 协助记忆

    • 一句话:HTTPS = 先"锁匠换钥匙”(握手协商对称密钥),再"快递柜存件"(对称加密传输)
    • 三个保障:加密防偷看、认证标签防篡改、证书防冒充
    • 握手记忆:你好(ClientHello)→ 收到(ServerHello)→ 亮证件(证书)→ 我完了(ServerHelloDone)→ 验证件(验证证书)→ 换钥匙(密钥交换)→ 开锁(对称加密通信)
  • 进阶思考

    • 为什么说 RSA 密钥交换不安全,一定要 ECDHE

      • RSA 密钥交换下,客户端用服务器公钥加密预主密钥发给服务器——如果攻击者一直抓包记录流量,将来拿到服务器私钥,就能解密当年的预主密钥,从而还原所有历史会话。ECDHE 每次会话用临时密钥对,私钥泄露也无法回推历史会话——这就是前向保密。2026TLS 1.3 已强制 ECDHE
    • 证书过期了,客户端为什么拒绝访问?

      • 证书验证包含有效期检查——过期证书导致验证失败,浏览器显示"连接不安全"。这也解释了为什么证书有效期正在分阶段缩短到 47 天(2029 年前)后,自动化续期(ACME)是刚需:手动续期跟不上节奏,证书过期直接导致 HTTPS 大面积报错

🤔 如何设计高可用的 Web 服务架构?

  • 高可用的本质:消除单点 + 快速恢复。设计目标不是"不出故障",而是"出了故障用户无感知或感知最小"。可用性指标:99.9%(一年宕机 < 8.76 小时)、99.99%(< 52.6 分钟)——不可用时间每差 10 倍,对架构的冗余和自动化要求指数级上升。

    • 第一原则:无状态化(Stateless

      • 应用层无状态:每个请求不依赖本地存储,任何节点都能处理任何请求 ——— 这是水平扩展和故障转移的前提
      • Session 不存本地:外置到 Redis(或 JWT 无状态 token)
      • 有状态的组件(DB、Redis)单独部署,与应用解耦
    • 分层冗余:每一层都要有多副本

      • 接入层DNSIP / 多解析(智能 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 探测)
      • 超时控制:所有外部调用必须设置超时(连接超时 + 读超时),防止故障蔓延
      • 重试有界:重试要有次数上限和退避(指数退避 + 抖动),避免重试风暴放大故障
      • 熔断:下游故障时快速失败(断路器打开),保护调用方自身的线程/连接资源不被耗尽,同时给下游恢复窗口(如 SentinelResilience4j
      • 限流降级:流量超限时优先保核心业务(降级非核心功能),防止雪崩
      • 幂等设计:接口幂等(重试不产生副作用),配合重试机制
    • 数据层高可用

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

      • Redis 主从 + 哨兵自动故障转移,或 Redis Cluster
      • 防缓存雪崩:过期时间加随机抖动、多级缓存(本地 + 分布式)
      • 防缓存穿透:布隆过滤器、空值缓存
      • 防缓存击穿:热点 key 加锁重建、永不过期 + 异步更新
    • 弹性伸缩

      • 水平扩缩容:云上 ASAuto Scaling)、K8s HPA(按 CPU/内存/自定义指标)、KEDA(事件驱动)、Cluster Autoscaler/Karpenter(节点级)
      • 容量规划:压测摸清单节点容量,预留 buffer(如 70% 水位告警)
    • 可观测性

      • 监控:指标(QPS、RT、错误率、资源)、日志、链路追踪(Metrics/Logs/Traces 三支柱,Profiling 作为第四信号已由 OpenTelemetry 推进)
      • 告警:分级告警、值班响应
      • 故障演练(混沌工程) :主动注入故障(杀节点、断网络)验证架构韧性,如 Chaos MeshChaos 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 兜底扣减,需同时回补 DBRedis,且防重复回补(幂等)
    • 核心链路(一次秒杀请求)

      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 原子脚本,直接用 Redisget + decr 不行吗?

      • 不行。getdecr 是两个独立命令,高并发下两个线程可能同时读到库存=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 同时过期——过期时间加随机抖动、多级缓存
    • 第二层:读写分离(让主库专心写)

      • 一主多从:读流量走从库,写流量走主库
      • 主从复制有延迟 ——— 对一致性敏感(写后立即读)的流量必须走主库,其余读走从库
      • 中间件:应用层路由(读写分离框架)或代理层透明路由(ProxySQLMySQL RouterMaxScaleShardingSphere 等)
      • 共享存储一写多读架构(如 AuroraPolarDB:从库直接读共享存储,显著降低传统 binlog 异步复制的延迟,是"主从延迟"问题的重要解法
      • 适用前提:读多写少的业务(大部分互联网业务)
    • 第三层:连接治理(防止连接被打爆)

      • 连接池:应用侧连接池(HikariCP/Druid),池大小要合理——不是越大越好,过大反而增加上下文切换和锁竞争。经验公式(源自 PostgreSQLHikariCP wiki 转引):连接数 ≈ CPU 核数 × 2 + 有效磁盘数 ——— SSD 场景下趋近核数×2(SSD 越快阻塞越少),最终以压测为准
      • 数据库侧 max_connections 设置合理上限,防止连接数被打满
      • 排查连接打满show processlistMySQL 8 建议查 performance_schema/sys 库)、ss -lntp | grep 3306 看连接数
    • 第四层SQL 与索引优化(让每个请求更快)

      • 慢查询日志定位slow_query_log 开启,mysqldumpslow 分析
      • EXPLAIN 分析执行计划:确认走索引、避免全表扫描(MySQL 8.0.18+ 可用 EXPLAIN ANALYZE 看实际执行)
      • 常见索引失效:函数包裹索引列(MySQL 8.0 支持函数索引可解)、隐式类型转换、前导模糊查询 %xxOR 连接的列并非全部可走索引(OR 两侧都是索引列时可用 index_merge
      • 避免大事务:事务要短、批量操作分批、避免长事务持有锁
      • 避免深分页LIMIT 100000, 10 → 用游标/WHERE id > 上次最大值 方式
    • 第五层:异步化与削峰(减少对 DB 的瞬时冲击)

      • MQ 削峰:写操作(日志、统计、非实时数据)先入队,异步批量落库——注意:订单等对"写后立即可见"敏感的流量不能异步,需同步落库或异步后读主库兜底
      • 批量写入:合并多条 insert 为一条批量 insert,减少事务与网络开销
    • 第六层:分库分表(水平扩展,最后手段)

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

      • 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 ServerlessPolarDB Serverless)按负载自动扩缩,适合流量波动大的场景
  • 协助记忆

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

    • 连接池是越大越好吗?

      • 不是。连接池过大,空闲连接占用数据库内存,且线程切换、锁竞争反而加剧。业界经验公式:连接数CPU 核数 × 2 + 有效磁盘数(SSD 下趋近核数×2)——但真实值要结合压测(响应时间、吞吐)调优。宁可"少量连接快速周转",不要"大量连接排队等待"
    • 读写分离一定能解决瓶颈吗?什么场景不行?

      • 读写分离只解决读多写少场景。如果瓶颈在写(写入量大、单库写入上限),读写分离无效——此时需要分库分表或分布式数据库;如果业务强一致性要求高(写后立即读、金融类),主从延迟会导致读从库读到旧数据,需要读走主库或引入同步手段(共享存储一写多读架构可降低延迟)。先定位瓶颈在"读"还是"写",再选方案
    • 缓存和数据库的一致性怎么保证?

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

🤔 如何设计高并发的 Web 服务架构?

  • 高并发架构的本质:用"水平扩展"换吞吐,用"削峰填谷"扛峰值。核心三件事:无状态化(能加机器就扛得住)、分层削峰(把瞬时峰值摊平)、单点加速(让每个请求更快)。和高可用架构不同——高可用解决"挂了怎么办"(冗余+恢复),高并发解决"来得多怎么办"(吞吐+性能)。

    • 第一原则:无状态化 + 水平扩展(高并发的根基)

      • 应用层无状态:请求不依赖本地存储,任何节点都能处理任何请求——这是"加机器就能扛"的前提
      • 状态外置SessionRedis(或 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 HPACPU/内存/自定义指标)、KEDA(按 MQ 队列深度/Kafka lag 事件驱动——本质是监控事件源并喂数据给 HPA0↔1KEDA 负责、1↔NHPA 负责)
      • 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:连接数(活跃/等待)、QPSupstream 响应时间、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"),SLOService 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 小时)
      • 中间设备(防火墙、负载均衡)可能静默断开空闲连接——客户端要能处理"连接被重置"(重试机制)
    • 反向代理场景的 keepaliveupstream 连接复用)

      • 浏览器 ↔ 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/2HTTP/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.3ECDHE 密钥协商参数(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. 恶意根证书被安装——联想 SuperfishDell 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:
        1
        2
        
        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 6060 秒,内部隐含 -n 50000
      • 输出怎么看(关键几行):

        • Requests per secondQPS
        • 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 脚本(自定义请求/鉴权)

      • 输出怎么看

        1
        2
        3
        4
        
        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/secQPS

      • Thread StatsLatencyAvg 平均延迟、Stdev 标准差、Max 最大延迟(默认输出没有分位数,加 --latency 后底部出现 50%/75%/90%/99% 分位数表)

      • Req/Sec:每个线程的吞吐(12.41k × 8 线程 ≈ 98k,和总量对得上)

      • Lua 脚本示例(自定义请求头/POST body):

        1
        2
        3
        
        wrk.method = "POST"
        wrk.headers["Content-Type"] = "application/json"
        wrk.body = '{"page":1}'
    • k6 实战(脚本化正式压测)

      • 安装:官方二进制 / Dockerdocker run grafana/k6

      • 最小脚本 loadtest.js

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        
        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:响应时间——默认含 avgminmedmaxp(90)p(95);需要 p(99) 要在 options 里加 summaryTrendStats: ['p(99)']
        • http_reqs:总请求数;http_req_failed:失败率;iterations:完成迭代次数
      • 进阶ramping-vus 做爬坡(每 10s100 VU)、多阶段场景、混合接口

    • JMeter 实战(复杂流程)

      • 概念三件套Thread Group(并发线程数、循环次数)→ SamplerHTTP 请求:URL、方法、body)→ Listener(查看结果:聚合报告 Aggregate Report
      • GUI 建好测试计划后,生产用命令行跑jmeter -n -t plan.jmx -l result.jtl -e -o report_dir
        • -nGUI 模式、-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/错误率三看,资源打满定短板
  • 进阶思考

    • abwrk 压同一个接口,数字差很多,谁是对的?

      • 可能是连接模型差异: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 适合快速摸底
  • 扩展信息
    P95P99 到底是什么?(分位数/百分位数)前面很多回答反复出现 P50/P95/P99——它们叫分位数(Percentile,百分位数),是统计学里描述"数据分布"的指标。一句话定义:把一组数据从小到大排序,第 n 百分位 = “有 n% 的数据小于等于它"的那个值。用在延迟上:P99 = 99% 的请求耗时不高于这个值(也意味着约 1% 的请求比它慢)。

    • 常见档位及含义(以响应时间为例)
      • P50(中位数) :一半请求比它快、一半比它慢——“典型体验”(但典型不代表多数用户感受)
      • P90 / P9590%/95% 的请求比它快——“大多数用户体验”,一般业务看 P95
      • P9999% 的请求比它快——“尾部体验”,代表最慢的那 1% 用户;SLO 常承诺 P99(如"P99 < 500ms”)
      • P99999.9% 的请求比它快——极端长尾,常用于高敏感场景(支付、实时系统)
      • Max:最慢的单个请求——不稳定,受偶发抖动影响大,一般不当指标用
      • 档位越高越能暴露"长尾问题",但也越受单次抖动影响(P999 往往被一次 GC 停顿或网络抖动拉爆)
    • 为什么不用平均值?(经典长尾例子)
      • 100 个请求:9810ms210s
      • 平均值 = (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 估算偏差大
      • 分位数不可跨实例求平均:应聚合直方图后统一计算
      • 不同工具的分位数算法有差异(插值方式),跨工具对比时注意口径

目录