高并发与高可用技术经验

高并发与高可用技术经验

    • 如何读这份文档
    • [1. 核心心智模型](#1. 核心心智模型)
      • [1.1 高并发在抢什么](#1.1 高并发在抢什么)
      • [1.2 延迟、吞吐、并发的关系](#1.2 延迟、吞吐、并发的关系)
      • [1.3 同步调用链上的「放大」](#1.3 同步调用链上的「放大」)
      • [1.4 高可用 ≠ 高并发,但经常一起做](#1.4 高可用 ≠ 高并发,但经常一起做)
    • [2. 易误解概念澄清(必读)](#2. 易误解概念澄清(必读))
      • [2.1 「端口是 65535,所以最多同时 65535 个请求?」------错](#2.1 「端口是 65535,所以最多同时 65535 个请求?」——错)
      • [2.2 「nofile=65535 就是最多 65535 连接」------不精确](#2.2 「nofile=65535 就是最多 65535 连接」——不精确)
      • [2.3 「连接数 = 并发请求数 = QPS」------三者不同](#2.3 「连接数 = 并发请求数 = QPS」——三者不同)
      • [2.4 「把 Tomcat max-threads 调到 2000,性能就 ×10」------错](#2.4 「把 Tomcat max-threads 调到 2000,性能就 ×10」——错)
      • [2.5 「超时设大一点更稳定」------往往更不稳定](#2.5 「超时设大一点更稳定」——往往更不稳定)
      • [2.6 「CPU 只有 20%,说明系统很空」------不一定](#2.6 「CPU 只有 20%,说明系统很空」——不一定)
      • [2.7 「Redis / DB 连接池开越大越好」------错](#2.7 「Redis / DB 连接池开越大越好」——错)
      • [2.8 「限流从不触发才说明阈值合理」------错](#2.8 「限流从不触发才说明阈值合理」——错)
      • [2.9 「多实例了就不会挂」------不充分](#2.9 「多实例了就不会挂」——不充分)
      • [2.10 「重试能提高可用性」------看情况](#2.10 「重试能提高可用性」——看情况)
      • [2.11 「监听 8080,所以本机只能有一个服务用 8080」------对;但和并发无关](#2.11 「监听 8080,所以本机只能有一个服务用 8080」——对;但和并发无关)
      • [2.12 「带宽 1Gbps,所以 QPS 上限很好算」------只算了一维](#2.12 「带宽 1Gbps,所以 QPS 上限很好算」——只算了一维)
      • [2.13 「Keep-Alive / 连接池会让一台机器无限并发」------错](#2.13 「Keep-Alive / 连接池会让一台机器无限并发」——错)
      • [2.14 「backlog / accept-count 调很大就能抗住尖峰」------危险](#2.14 「backlog / accept-count 调很大就能抗住尖峰」——危险)
      • [2.15 一张对照表(贴墙上)](#2.15 一张对照表(贴墙上))
    • [3. 多层防护架构(通用 L0~L7)](#3. 多层防护架构(通用 L0~L7))
    • [4. 操作系统与机器规格](#4. 操作系统与机器规格)
      • [4.1 规格怎么想](#4.1 规格怎么想)
      • [4.2 文件句柄 nofile------怎么设、怎么验](#4.2 文件句柄 nofile——怎么设、怎么验)
      • [4.3 网络 sysctl------每项干什么、怎么设](#4.3 网络 sysctl——每项干什么、怎么设)
      • [4.4 JVM------怎么设(Java 17 可执行步骤)](#4.4 JVM——怎么设(Java 17 可执行步骤))
        • [步骤 1:先算机器给 JVM 多少内存](#步骤 1:先算机器给 JVM 多少内存)
        • [步骤 2:选 GC(Java 17)](#步骤 2:选 GC(Java 17))
        • [步骤 3:必开的诊断项(建议默认带上)](#步骤 3:必开的诊断项(建议默认带上))
        • [步骤 4:用指标判断「堆小了还是大了」](#步骤 4:用指标判断「堆小了还是大了」)
        • [步骤 5:和「线程打满」一起看(避免误判)](#步骤 5:和「线程打满」一起看(避免误判))
        • [4.4.1 一张「填空式」JVM 决策表(可直接抄到运行文档)](#4.4.1 一张「填空式」JVM 决策表(可直接抄到运行文档))
    • [5. Nginx / 入口层](#5. Nginx / 入口层)
      • [5.1 职责边界](#5.1 职责边界)
      • [5.2 配置骨架(学习用)](#5.2 配置骨架(学习用))
      • [5.3 知识点清单](#5.3 知识点清单)
      • [5.4 Nginx 限流与连接------怎么设](#5.4 Nginx 限流与连接——怎么设)
      • [5.5 边缘还可以做什么(进阶)](#5.5 边缘还可以做什么(进阶))
    • [6. 应用入口线程模型(Tomcat / Spring Boot)](#6. 应用入口线程模型(Tomcat / Spring Boot))
      • [6.1 为什么「调大 max-threads」不是根治](#6.1 为什么「调大 max-threads」不是根治)
      • [6.2 推荐基线(先抄这个,再按 6.3 算)](#6.2 推荐基线(先抄这个,再按 6.3 算))
      • [6.3 Tomcat 线程------怎么算、怎么调](#6.3 Tomcat 线程——怎么算、怎么调)
      • [6.4 线程池通用规范](#6.4 线程池通用规范)
    • [7. 超时分层对齐(极易错)](#7. 超时分层对齐(极易错))
      • [7.1 对齐关系(先抄结构,再填数字)](#7.1 对齐关系(先抄结构,再填数字))
      • [7.2 超时------怎么设(填空法)](#7.2 超时——怎么设(填空法))
    • [8. 出站 HTTP:超时、连接池、舱壁](#8. 出站 HTTP:超时、连接池、舱壁)
      • [8.1 三个能力缺一不可](#8.1 三个能力缺一不可)
      • [8.2 强制规范(任何项目通用)](#8.2 强制规范(任何项目通用))
      • [8.3 配置示例与「怎么设」](#8.3 配置示例与「怎么设」)
      • [8.4 舱壁 vs 熔断(易混)](#8.4 舱壁 vs 熔断(易混))
    • [9. 限流、熔断、系统保护(Sentinel 等)](#9. 限流、熔断、系统保护(Sentinel 等))
      • [9.1 规则类型速查](#9.1 规则类型速查)
      • [9.2 资源怎么划分](#9.2 资源怎么划分)
      • [9.3 熔断参数阅读示例](#9.3 熔断参数阅读示例)
      • [9.3.1 限流 / 熔断------怎么设(起步法)](#9.3.1 限流 / 熔断——怎么设(起步法))
      • [9.4 降级策略(产品可接受)](#9.4 降级策略(产品可接受))
      • [9.5 更多流量治理手段](#9.5 更多流量治理手段)
    • [10. 缓存](#10. 缓存)
      • [10.1 何时缓存](#10.1 何时缓存)
      • [10.2 单飞加载(防击穿)](#10.2 单飞加载(防击穿))
      • [10.3 三大经典问题](#10.3 三大经典问题)
      • [10.4 进阶](#10.4 进阶)
      • [10.5 Redis 连接池------怎么设](#10.5 Redis 连接池——怎么设)
    • [11. 数据库与预计算](#11. 数据库与预计算)
      • [11.1 连接池------怎么设](#11.1 连接池——怎么设)
      • [11.2 查询与模型](#11.2 查询与模型)
      • [11.3 异步削峰(写与通知)](#11.3 异步削峰(写与通知))
    • [12. 业务 API 设计与读路径改造](#12. 业务 API 设计与读路径改造)
    • [13. 前端 / 客户端(L0)](#13. 前端 / 客户端(L0))
    • [14. 多实例、网关与弹性](#14. 多实例、网关与弹性)
    • [15. 安全与鉴权对容量的影响](#15. 安全与鉴权对容量的影响)
    • [16. 可观测、压测与应急](#16. 可观测、压测与应急)
      • [16.1 黄金信号](#16.1 黄金信号)
      • [16.2 必打日志 / 指标](#16.2 必打日志 / 指标)
      • [16.3 应急步骤(通用)](#16.3 应急步骤(通用))
      • [16.4 压测清单](#16.4 压测清单)
      • [16.5 混沌与 SLO(进阶)](#16.5 混沌与 SLO(进阶))
    • [17. 完整改造清单(自学自检用)](#17. 完整改造清单(自学自检用))
      • [A. 架构](#A. 架构)
      • [B. 入口与网络](#B. 入口与网络)
      • [C. 应用运行时](#C. 应用运行时)
      • [D. 出站与韧性](#D. 出站与韧性)
      • [E. 数据](#E. 数据)
      • [F. 客户端](#F. 客户端)
      • [G. 运维闭环](#G. 运维闭环)
    • [18. 分阶段落地路线图](#18. 分阶段落地路线图)
    • [19. 验收门禁(通用)](#19. 验收门禁(通用))
    • [20. 知识点速查表](#20. 知识点速查表)
    • [21. 常见误区(对照自查)](#21. 常见误区(对照自查))

如何读这份文档

  1. 先建立心智模型:第 1 章(抢什么资源、容量公式)。
  2. 立刻看易误解概念:第 2 章(端口 65535、线程≠QPS 等,避免后面越学越偏)。
  3. 再按流量方向往里走:多层架构 → OS/JVM(含「怎么设」)→ Nginx → Tomcat → 超时 → 出站 → 限流熔断 → 缓存/DB → 前端 → 多实例 → 可观测。
  4. 最后用清单与验收:改造清单、路线图、验收门禁。
  5. 配置怎么用 :凡写「怎么设」的小节,都按 选起点 → 算关系 → 看指标 → 压测校准 四步走;示例数字不是标准答案。

一句话总纲:

高并发不是「把线程调大」,而是:入口削峰 → 本服务资源有界 → 依赖被关进笼子 → 读多走缓存/预计算 → 故障快速失败并降级 → 全程可观测。


1. 核心心智模型

1.1 高并发在抢什么

任何一次请求,最终都在消耗几类有界资源:

资源 典型上限来源 打满后的现象
工作线程 Tomcat max-threads、业务线程池 新请求排队或拒绝,像「服务死了」
连接 Nginx worker_connections、TCP 端口、HTTP 连接池 Too many open files、建连失败
队列 accept-count、线程池 queue、MQ 尾延迟爆炸,然后超时雪崩
下游配额 第三方 QPS、DB 连接池、Redis 连接 等待超时,错误率飙升
CPU / 内存 / 带宽 机器规格、GC、序列化 吞吐下降或 Full GC

知识点: CPU 不高也可能已经「挂了」------大量线程阻塞在 Socket.read / getConnection 时,吞吐接近 0,但 CPU 很闲。

1.2 延迟、吞吐、并发的关系

text 复制代码
可持续 QPS ≈ 有效并发数 / 平均响应时间(秒)

例:出站舱壁并发 = 64,下游平均 0.5s
→ 理想吞吐 ≈ 64 / 0.5 = 128 QPS
实际再乘安全水位 0.3~0.5(GC、锁、抖动)
概念 含义 调参误区
并发 同一时刻「在飞」的请求数 盲目加大并发 ≠ 更高 QPS
吞吐 (QPS) 单位时间完成数 延迟上升时,同样并发下 QPS 下降
延迟 (P95/P99) 用户体感与超时预算 只看平均值会掩盖长尾

知识点: 提升吞吐只有三条路------缩短平均耗时 、提高有效并发(在资源够的前提下) 、减少无效请求(缓存/合并/限流)。

1.3 同步调用链上的「放大」

text 复制代码
1 个页面打开
  × N 个组件并行请求
  × 每个请求扇出 M 个下游
  = 入口 QPS × N × M 的下游压力

大屏、门户、管理后台「一屏多卡」最容易踩这个坑。

知识点: 先消灭放大(合并、缓存、预聚合),再谈加机器。

1.4 高可用 ≠ 高并发,但经常一起做

高并发 高可用
目标 扛住流量与延迟 故障时服务仍可用(可降级)
手段 限流、缓存、扩容、异步 多实例、熔断、降级、隔离、多活
关系 没隔离的高并发更容易雪崩 高可用设计本身也提高稳态并发能力

2. 易误解概念澄清(必读)

这一章专门拆「听起来像对、用起来全错」的说法。自学时建议每条都能用自己的话复述一遍。

2.1 「端口是 65535,所以最多同时 65535 个请求?」------错

端口号是 16 位整数(0~65535),但这不等于「整机只能同时接 65535 个连接」。

要分清两件事:

角色 端口怎么用 并发上限主要卡在哪
服务端监听 只占用 1 个 (或少数几个)端口,如 :8080 文件句柄 nofile、内存、应用线程/连接配置、CPU
客户端出站 每个到「同一目标 IP:端口」的连接,通常要占一个本机临时端口 临时端口范围 + TIME_WAIT + nofile

一条 TCP 连接的身份是五元组:

text 复制代码
(协议, 本机IP, 本机端口, 对端IP, 对端端口)
  • 一万个浏览器连你的 0.0.0.0:8080:服务端都是 同一个监听端口 8080 ,靠「对端 IP+端口」区分连接,不是「一连接占一个 8080」。
  • 你的应用作为客户端去打同一个第三方 1.2.3.4:443:本机临时端口会消耗;默认 ip_local_port_range 大约几万个,再叠加 TIME_WAIT,出站并发才可能碰到端口墙。
  • 打不同目标 IP/端口时,临时端口可以复用同一本地端口号(五元组仍唯一),所以「65535」更不是全局并发天花板。

正确记忆:

  • 入站高并发:优先看 nofile、Nginx worker_connections、Tomcat max-connections、机器内存。
  • 出站高并发:才额外担心临时端口与连接复用(HTTP keepalive / 连接池)。

2.2 「nofile=65535 就是最多 65535 连接」------不精确

nofile 限制的是进程能打开的文件描述符数量。每个 TCP socket 通常占 1 个 FD,此外还有:

  • 监听 socket、管道、日志文件、jar、加载的 so
  • 每个线程、stdout 等也会占 FD

所以:

text 复制代码
可支撑连接数 ≈ nofile - 非 socket 占用(往往留 10%~20% 余量)

且 Nginx 还要满足:worker_processes × worker_connections 不超过进程 nofile(以及系统级限制)。

2.3 「连接数 = 并发请求数 = QPS」------三者不同

概念 含义 举例
连接 (Connection) TCP/HTTP 通道是否还开着 Keep-Alive 下,1 个连接可串行很多请求
并发请求 (In-flight) 此刻正在处理、尚未返回的请求 Tomcat 工作线程大致对应「正在处理」的数
QPS 每秒完成多少请求 200 并发 × 平均 50ms → 约 4000 QPS
text 复制代码
QPS ≈ 并发请求数 / 平均耗时(秒)

1 万个 Keep-Alive 空闲连接,可能只有 50 个请求在飞 → QPS 由「在飞」决定,不是由「连接总数」决定。

误导用法: 看到「支持十万连接」就以为有十万 QPS------若每个请求 1 秒且只有 200 线程,上限仍约 200 QPS。

2.4 「把 Tomcat max-threads 调到 2000,性能就 ×10」------错

线程多了:

  • 每个线程约占用栈内存(常见默认约 1MB 量级,视 -Xss)
  • 上下文切换变多
  • 更致命:若线程都阻塞在慢第三方,2000 个慢请求 = 拖死整站,比 200 个死得更彻底

吞吐仍受 QPS ≈ 有效并发 / 耗时 约束;不降耗时、不加舱壁,加线程只是加「同时卡死的人数」。

2.5 「超时设大一点更稳定」------往往更不稳定

超时从 3s 调到 30s:

  • 单个故障请求占用线程/连接的时间 ×10
  • 同样流量下,池更容易被打满
  • 用户侧更晚失败,重试更容易叠成雪崩

正确做法: 超时按 SLA 设短;靠缓存、降级、熔断在失败时快速返回。

2.6 「CPU 只有 20%,说明系统很空」------不一定

阻塞型服务常见:

  • 线程全卡在 Socket.read / 等 DB 连接 → CPU 很低,接口全超时
  • 要看:繁忙线程、队列长度、连接池等待、P99、GC、FD

2.7 「Redis / DB 连接池开越大越好」------错

池过大:

  • 数据库/Redis 服务器被过多会话拖慢
  • 应用侧线程更容易「一起冲进去」放大故障

池是限流器,不是越大越快。见第 10、11 章「怎么设」。

2.8 「限流从不触发才说明阈值合理」------错

从不触发的限流 = 没装安全气囊。

合理限流应在压测尖峰或故障重试时真的触发,并返回明确 429,保护核心容量。

2.9 「多实例了就不会挂」------不充分

若:

  • 会话粘在单机内存
  • 限流按「单机很大」却用集群共享同一套过大阈值
  • 所有实例同步打同一个慢依赖且无舱壁

则多实例只是「一起挂」或「轮流挂」。高可用要靠:无状态 + 隔离 + 熔断降级 + 健康检查。

2.10 「重试能提高可用性」------看情况

安全 危险
幂等 GET、有限次、指数退避+抖动 非幂等写、齐刷重试、超时很长还重试

下游正在恢复时,无节制重试 = 第二次打死它。

2.11 「监听 8080,所以本机只能有一个服务用 8080」------对;但和并发无关

端口冲突只约束「谁绑定这个端口」。

绑定成功后,该端口上的并发连接数与「65535」无直接相等关系(见 2.1)。

2.12 「带宽 1Gbps,所以 QPS 上限很好算」------只算了一维

text 复制代码
粗算:QPS_带宽 ≈ 带宽 / 平均响应体大小

实际还可能先撞上:CPU、GC、线程、DB、下游 QPS、锁。带宽只是天花板之一。

2.13 「Keep-Alive / 连接池会让一台机器无限并发」------错

连接复用能减少握手和临时端口消耗,但同时在飞的请求 仍受:线程池、舱壁、max-requests、CPU、下游配额限制。

复用解决的是「建连成本」,不是「处理能力」。

2.14 「backlog / accept-count 调很大就能抗住尖峰」------危险

队列只是把失败变成「晚一点失败」:队列越长,P99 越差,超时越多,重试越多。

尖峰应靠:限流快速拒绝 + 扩容 + 缓存,而不是无限排队。

2.15 一张对照表(贴墙上)

误传 更准确的说法
65535 端口 = 65535 并发 入站共享监听端口;出站才消耗临时端口
连接数 = QPS QPS ≈ 在飞请求 / 耗时
线程越大越快 线程是并发上限,耗时不降则吞吐不升,故障时更惨
超时越大越稳 超时越长占用越久,更容易雪崩
CPU 低 = 空闲 可能是 IO 堵死
池越大越好 池是保护阀,要按下游能力设
限流别触发 限流要在尖峰时能保护
多实例 = 高可用 还要隔离、无状态、降级
连接复用 = 无限能力 只减建连成本,在飞仍有界
队列调很大抗尖峰 队列过长会拖死尾延迟

3. 多层防护架构(通用 L0~L7)

按流量从外到内,建议建立 8 层(可分阶段上线):

text 复制代码
L0  客户端:降频、合并、退避、降级 UI
L1  边缘 / Nginx:连接与速率限流、超时、静态缓存、TLS
L2  网关:路由鉴权、粗粒度限流(Sentinel gw-flow 等)
L3  应用入口:Tomcat/Netty 线程、accept 队列、慢请求隔离
L4  业务读路径:缓存、预聚合、批量、去重、异步削峰
L5  出站依赖:超时 + 连接池 + 舱壁(Bulkhead)
L6  韧性:限流 / 熔断 / 系统保护规则 + 产品降级
L7  基础设施:Redis/DB/MQ 池、多实例、资源规格、容器弹性

原则:

  • 越靠外越粗、越便宜;越靠内越细、越贵。
  • 慢依赖必须在 L5/L6 被关进笼子,绝不能占满 L3 入口线程。
  • 每一层超时要对齐(见第 6 章)。

4. 操作系统与机器规格

4.1 规格怎么想

角色 思考点
反向代理 主要吃网络与少量 CPU;连接数要够
网关 鉴权 + 限流热点;建议多实例
业务 API 出站舱壁需要线程与内存;勿与重计算混部
缓存 内存按 key 估算 × 1.5;尽量独立
数据库 / OLAP 按查询模式单独评估,别和应用抢盘

怎么定规格(实用): 先用压测得出「单实例稳态 QPS 与 P95」,再按峰值流量 ÷(单实例稳态 × 0.5~0.7 安全水位)得到实例数;CPU 长期 >70% 或 GC/线程已触顶再加核加内存,而不是先盲买大机器。

4.2 文件句柄 nofile------怎么设、怎么验

要解决的问题: 连接/文件太多时出现 Too many open files,新连接失败。

推荐起点:

ini 复制代码
# /etc/security/limits.d/app.conf  (或 systemd: LimitNOFILE=65535)
* soft nofile 65535
* hard nofile 65535
场景 建议
普通业务 API 65535 通常够用
Nginx / 网关高连接 与 worker_processes × worker_connections 对齐,常设 65535 或更高(如 1048576,需系统允许)
容器 查 ulimit -n 与 cgroup;只改宿主机 limits 而容器内仍是 1024 等于没改

怎么验证生效:

bash 复制代码
# 当前 shell
ulimit -n

# 看已启动的 Java 进程(把 <pid> 换成实际)
cat /proc/<pid>/limits | grep 'open files'

怎么估算要不要再加大:

text 复制代码
预留 FD ≈ 预期峰值连接 × 1.2  +  日志/jar 等(可先按 2000 估)
若 Nginx: worker_processes × worker_connections  <  nofile(每个 worker 进程各自一份)

4.3 网络 sysctl------每项干什么、怎么设

conf 复制代码
# /etc/sysctl.d/99-net-app.conf
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.core.netdev_max_backlog = 16384
vm.swappiness = 10

应用:sudo sysctl --system 或 sysctl -p。

参数 什么时候改 怎么定
somaxconn 突发建连被拒、accept 队列满 ≥ 应用 listen backlog / Nginx 相关队列;常见 1024→4096
tcp_max_syn_backlog SYN 洪水或突发握手 与 somaxconn 同量级或更大
ip_local_port_range 出站连同一目标太多、报端口耗尽 扩大范围(如 1024 65535);更优先用连接池复用
tcp_tw_reuse TIME_WAIT 多、出站建连慢 作客户端的机器可开;按内核版本文档理解语义
swappiness 偶发延迟尖刺、有 swap 业务机常设 1~10,尽量别靠 swap 撑内存

验证出站端口是否吃紧:

bash 复制代码
ss -s
# 或统计 TIME_WAIT
ss -ant | awk '{print $1}' | sort | uniq -c

若 TIME_WAIT / 出站连接接近临时端口容量 → 先加连接复用,再考虑调 range。

4.4 JVM------怎么设(Java 17 可执行步骤)

目标不是背参数,而是填一张内存账单,再选出堆大小与 GC。

步骤 1:先算机器给 JVM 多少内存

假设物理机或容器内存为 M(例如 16GB):

text 复制代码
留给操作系统 / 页面缓存     ≈ 1~2GB(容器可少些,但别为 0)
堆外(直接内存、线程栈、Code Cache、元空间等)≈ 1~3GB(Netty/OkHttp 多则偏大)
可给堆 -Xmx                 ≈ M - 上述预留
容器/机器内存 常见堆起点(单 Java 进程) 说明
4GB -Xms1g -Xmx1g 或 2g 小服务;堆外也要留
8GB -Xms2g -Xmx2g~4g 中等 API
16GB -Xms4g -Xmx4g~8g 常见业务节点
32GB+ 先 8g~16g,用监控决定 堆不是越大越好

硬规则:

  1. -Xms 与 -Xmx 设成一样,避免运行时扩堆带来的抖动。
  2. 堆 + 堆外 + 系统 < 物理内存,否则会 swap 或被 OOM Killer 杀掉。
  3. 同一机器多个 Java 进程:把内存账单按进程拆开,禁止每个都按「整机 70%」设堆。
步骤 2:选 GC(Java 17)
选择 何时用 怎么开
G1(默认推荐起点) 堆几 GB~十几 GB,通用延迟 -XX:+UseG1GC(17 常已默认)-XX:MaxGCPauseMillis=200
ZGC P99 极敏感、堆较大、已会压测对比 -XX:+UseZGC(按发行版文档)
Parallel 批处理吞吐优先、可接受较长停顿 一般不用于在线大屏/API

MaxGCPauseMillis=200 的含义: 给 G1 的目标停顿,不是保证;设太小(如 20)可能导致频繁 GC、吞吐变差。在线接口从 200ms 目标起步,看 GC 日志再调。

步骤 3:必开的诊断项(建议默认带上)
text 复制代码
-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/logs/heapdump/
-Xlog:gc*:file=/data/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=50M

有 Netty / 大量直接内存时再加(按版本二选一或组合,以你 JDK 文档为准):

text 复制代码
-XX:MaxDirectMemorySize=1g

线程特别多时关注栈:默认 -Xss 约 1MB,线程数 × Xss 会吃掉堆外;不要无故把 Tomcat 调到几千。

步骤 4:用指标判断「堆小了还是大了」
观察 判断 动作
Young GC 很频繁,且占用明显 CPU时间;Old 持续上涨最终 Full GC 堆偏小或泄漏 先查泄漏;确认无泄漏再适度加大堆
单次停顿经常 > 超时预算(如经常 300ms+ 而接口超时 1s) 堆可能偏大或 GC 不合适 略减堆,或评估 ZGC;优化分配
堆占用长期 < 40%,GC 很轻松 堆可能浪费 可减小堆,把内存还给 OS/页面缓存/同机其它进程
进程 RSS 接近机器内存,偶发卡住 可能堆外+堆打满或 swap 查直接内存、线程数、是否 swap

看 GC 日志是否健康(自学抓手):

  • 停顿时间是否经常接近或超过 MaxGCPauseMillis
  • Full GC / 长暂停是否与接口超时时间对齐出现
  • 分配速率是否异常(短时间大量对象)
步骤 5:和「线程打满」一起看(避免误判)
text 复制代码
CPU 低 + 接口超时  → 先 jstack / 看繁忙线程,不要先加堆
CPU 高 + GC 日志停顿长 → 再调堆 / GC
CPU 高 + GC 很少 → 可能是业务计算或序列化,不是堆大小问题
bash 复制代码
# 示例:看线程在干什么(把 pid 换掉)
jstack <pid> | head
# 或用 arthas thread / dashboard(若环境允许)
4.4.1 一张「填空式」JVM 决策表(可直接抄到运行文档)
项 你的值 填写说明
容器/机器内存 M ___ GB
同机 Java 进程数 ___
本进程 -Xmx ___ GB ≈ (M − 系统预留) / 进程数,再留堆外
-Xms 与 Xmx 相同
GC G1 / ZGC 默认 G1
MaxDirectMemorySize ___ 有 Netty 时建议显式设
HeapDump 路径 /data/logs/heapdump/ 目录要可写、有盘空间
GC 日志路径 /data/logs/gc.log 上线后至少看一周

例(16GB 机器、单 Java、偏 IO 的 API):

text 复制代码
系统+页缓存 2g + 堆外约 2g + 堆 4g = 8g 量级占用,仍有余量
→ -Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 ...
压测后若 GC 轻松且堆占用低,可试 3g;若频繁 Full GC 且无泄漏,再试 6g。

5. Nginx / 入口层

5.1 职责边界

做 不做
TLS、静态资源、反向代理 细粒度业务鉴权细节
连接数 / 请求速率限流 替代业务级 per-API 限流
upstream 超时与谨慎重试 对非幂等写无限重试
访问日志(含上游耗时) 替代 Redis 业务缓存

5.2 配置骨架(学习用)

nginx 复制代码
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 10240;
    multi_accept on;
    use epoll;   # Linux
}

http {
    sendfile on;
    keepalive_timeout  65;
    keepalive_requests 1000;

    limit_req_zone  $binary_remote_addr zone=api_per_ip:20m rate=20r/s;
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

    upstream app_backend {
        least_conn;
        server 10.x.x.11:8080 max_fails=3 fail_timeout=10s;
        server 10.x.x.12:8080 max_fails=3 fail_timeout=10s;
        keepalive 64;
    }

    log_format main_timed '$remote_addr [$time_local] "$request" '
                          'status=$status ut=$upstream_response_time '
                          'rt=$request_time upstream=$upstream_addr';

    server {
        listen 443 ssl http2;
        client_max_body_size 16m;

        location /api/ {
            limit_req  zone=api_per_ip burst=40 nodelay;
            limit_conn conn_per_ip 50;

            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_connect_timeout 3s;
            proxy_send_timeout    15s;
            proxy_read_timeout    15s;

            # 仅幂等 GET 谨慎开启;POST 默认不要重试
            proxy_next_upstream error timeout http_502 http_503;
            proxy_next_upstream_tries 2;

            proxy_pass http://app_backend;
        }

        location / {
            root /data/www/app;
            try_files $uri $uri/ /index.html;
            expires 7d;
        }
    }
}

限流友好响应:

nginx 复制代码
limit_req_status 429;
limit_conn_status 429;
error_page 429 = @too_many;
location @too_many {
    default_type application/json;
    return 429 '{"code":429,"msg":"请求过于频繁,请稍后重试"}';
}

5.3 知识点清单

配置项 含义 调参注意
limit_req 令牌桶限速 burst 过小误杀首屏并发;过大削峰失效
limit_conn 并发连接上限 防单客户端占满
least_conn 选连接少的上游 适合长短请求混杂
keepalive + Connection "" 复用到上游的连接 显著降低握手
proxy_read_timeout 等上游响应 必须与后端超时对齐
proxy_next_upstream 失败换机 禁止对非幂等写盲目重试
$upstream_response_time 上游耗时 区分 Nginx 排队 vs 后端慢
HTTP/2 多路复用 减轻浏览器连接数限制

5.4 Nginx 限流与连接------怎么设

worker_connections:

text 复制代码
单机理论连接能力 ≈ worker_processes × worker_connections
且每个 worker 的连接数 < 该进程 nofile

worker_processes auto 时约等于 CPU 核数。例:4 核 × 10240 ≈ 4 万连接量级(理论),还要被内存和后端能力挡住。

limit_req(速率):

  1. 估单 IP 正常峰值(大屏一页并发打开可能短时几十 QPS)。
  2. rate 设为「正常峰值以上一点」,burst 设为「首屏毛刺」。
  3. 例:rate=20r/s + burst=40 → 平滑 20/s,允许短突发到约 60。
  4. 压测时看是否误杀:若正常首屏大量 429 → 加大 burst 或按 path 拆分限流带。

limit_conn(并发连接): 防单客户端占满;API 常见单 IP 20~100,按是否经 NAT(多人共出口 IP)调整。

proxy_*_timeout: 直接填第 7 章表格里的值,与后端 callTimeout 对齐。

upstream keepalive: 到后端的空闲长连接数,常见 32~128;配合 proxy_http_version 1.1 + Connection ""。

5.5 边缘还可以做什么(进阶)

  • CDN / 静态与 API 分离
  • TLS 会话复用、证书链优化
  • gzip/brotli(注意 CPU;已压缩内容勿再压)
  • L4 SLB + 深度健康检查(不只端口通)

6. 应用入口线程模型(Tomcat / Spring Boot)

6.1 为什么「调大 max-threads」不是根治

  • 每个阻塞在下游 read 的请求占 1 个工作线程。
  • 200 → 800:更多请求一起卡死,内存与连接压力更大。
  • 正确做法:限制「能进入出站等待」的并发,并让超时快速失败。

6.2 推荐基线(先抄这个,再按 6.3 算)

yaml 复制代码
server:
  tomcat:
    threads:
      max: 200
      min-spare: 20
    accept-count: 200
    max-connections: 10000
    connection-timeout: 5000
    keep-alive-timeout: 15000
    max-keep-alive-requests: 100
    accesslog:
      enabled: true
      pattern: '%h %t "%r" %s %D %b'
参数 作用 怎么设(实用)
threads.max 同时「正在处理」的请求上限 见 6.3;无舱壁时它≈能同时堵在下游的人数
min-spare 空闲保活线程 约为 max 的 10%~20%
accept-count 线程满后 TCP/请求排队长度 与 max 同量级或略小;宁肯快拒绝,勿排几千
max-connections NIO 连接总数(含 keep-alive 空闲) 远大于 threads.max;受 nofile 约束
connection-timeout 连接建立/等待超时(ms) 常见 3~5s
keep-alive-timeout 空闲连接保持 与 Nginx/客户端习惯对齐,常见 10~30s
%D 访问日志耗时 ms 用来找慢接口,建议打开

6.3 Tomcat 线程------怎么算、怎么调

核心公式(再背一次):

text 复制代码
目标 QPS ≈ threads.max / 平均响应时间(秒)     (理想情况)
安全 QPS ≈ 上面结果 × 0.3~0.5

步骤:

  1. 量平均耗时:压测或线上 P50/P95(例如平均 200ms=0.2s)。
  2. 定目标 QPS:单实例要扛多少(例如 500 QPS)。
  3. 反推线程: threads ≈ 目标QPS × 耗时 → 500 × 0.2 = 100,再留余量 → 从 150~200 做起点。
  4. 加上游舱壁约束: 若出站舱壁只有 32,把 threads.max 调到 800 没有意义------多出来的线程仍会堵在拿舱壁许可或更快把无舱壁路径打爆。
  5. 看栈内存: 约 threads.max × Xss(如 200×1MB≈200MB)要算进堆外账单。
调参信号 动作
繁忙线程长期 ≈ max,队列也满,P99 暴涨 先降耗时(缓存/超时/舱壁),再考虑略增线程或扩实例
繁忙线程只有 max 的 20%,CPU 很低 线程不是瓶颈,别加线程
下游故障时繁忙线程顶满 加舱壁/熔断/缩短超时,不要加线程

6.4 线程池通用规范

规则 原因
池必须有界 无界队列 / CachedThreadPool 会在故障时 OOM 或打爆机器
拒绝策略慎用 CallerRuns 会把压力打回调用方线程(如 Tomcat)
出站池用 Abort 池满立刻失败,保护入口
禁止默认公共 ForkJoin 扛 IO CompletableFuture 默认池不适合大量阻塞 IO
池要命名 便于 jstack 与监控归因

进阶: Java 21 虚拟线程适合大量阻塞 IO,但仍要对下游并发与连接池设界,否则会打爆依赖。


7. 超时分层对齐(极易错)

7.1 对齐关系(先抄结构,再填数字)

text 复制代码
客户端 timeout          例如 15s
Nginx proxy_read        例如 15s
Gateway 路由超时        ≤ Nginx
业务整体超时            ≤ Gateway
出站 callTimeout        例如 8~10s(第三方)
舱壁 awaitTimeout       略大于 callTimeout(例如 12s)
DB / Redis timeout      更短(例如 1~3s)

7.2 超时------怎么设(填空法)

  1. 定用户可接受上限 T_user(例如大屏卡片 15s 还不出就该失败)。
  2. 留给网络与排队 10%~20%(Nginx/网关抖动)。
  3. 剩余给业务 ;业务里若调第三方,第三方 callTimeout 必须明显小于业务剩余时间。
  4. DB/Redis 用更短超时,避免慢查询占满线程。
  5. 全链路写进一张表,上线前对一下,禁止「前端 5s、后端 30s」这种错位。
层级 你的值 填写提示
前端 ___ s = 产品可接受上限
Nginx proxy_read_timeout ___ s ≈ 前端,或略小
出站 callTimeout ___ s ≤ 前端的 50%~70%(给聚合留时间)
Redis/DB ___ ms 通常 1000~3000ms
知识点 说明
外层 ≥ 内层 或明确短于内层并接受截断
错位后果 一端已超时关闭,另一端仍占线程
预算分配 总超时要留给「排队 + 业务 + 最慢依赖」
宁短勿长 短超时 + 降级,优于长超时把池打满

8. 出站 HTTP:超时、连接池、舱壁

8.1 三个能力缺一不可

能力 作用
超时 connect / read / write / call,避免永久卡死
连接池 复用连接;限制全局与单主机并发
舱壁 Bulkhead 独立线程池或信号量;池满快速失败,保护入口线程

8.2 强制规范(任何项目通用)

  1. 禁止无超时的 RestTemplate / HttpURLConnection / 随意 new 客户端。
  2. 统一出口(一个 Outbound 客户端或封装),禁止业务自行创建。
  3. 拒绝策略:Abort,不要用会打回调用方的策略扛出站。
  4. 超时日志必须带 METHOD + URL/path,否则无法运维。
  5. 按依赖拆舱壁(厂家 A / B / 内部服务),互不拖死。

8.3 配置示例与「怎么设」

yaml 复制代码
# 概念示例:按你们组件属性名映射即可
outbound:
  http:
    connect-timeout-ms: 3000
    read-timeout-ms: 8000
    write-timeout-ms: 8000
    call-timeout-ms: 10000
    max-idle-connections: 32
    keep-alive-minutes: 5
    max-requests: 64
    max-requests-per-host: 16
    bulkhead-core-size: 16
    bulkhead-max-size: 32
    bulkhead-queue-capacity: 64
    bulkhead-await-timeout-ms: 12000
参数 怎么定
connect-timeout-ms 建连;内网常见 1~3s,公网第三方可 3s
read / call-timeout 按第 7 章预算;先看下游 P95,再加一点余量,不要按偶发最大值设
max-requests 全局同时进行的 HTTP 调用上限;≈ 舱壁 max 或略大
max-requests-per-host 单下游主机并发;保护对方也保护自己,常见 8~32
bulkhead-max-size 「最多允许多少请求同时等这个依赖」;用 目标出站QPS × 耗时 估算
bulkhead-queue-capacity 等待许可的队列;宜小,满了 Abort
bulkhead-await-timeout-ms 略大于 call-timeout,避免许可等到了调用却已无意义

舱壁大小例算: 某第三方平均 0.5s,希望最多占用 50 QPS 出站 → 并发 ≈ 50×0.5=25 → bulkhead-max-size 从 24~32 起。

硬约束: bulkhead-max-size 应明显小于 tomcat.threads.max,否则舱壁形同虚设。

现象 动作
舱壁拒绝增多,Tomcat 仍健康 隔离在生效;业务受不了 → 加缓存/扩实例,或与对方扩容后再略增舱壁
Tomcat 打满、舱壁却很空 还有出站没走统一客户端,或慢在 DB
临时端口 / TIME_WAIT 飙升 加大连接复用;禁止每次 new HTTP 客户端

下游 P95 很长时:优先缓存 / 预聚合 / 异步,而不是无限加大超时。

8.4 舱壁 vs 熔断(易混)

舱壁 Bulkhead 熔断 Circuit Breaker
目的 限制同时占用的资源 发现故障后短时快速失败
手段 线程池 / 信号量 错误率 / 慢调用比例
故障时 池满立即拒绝 打开后一段时间内直接拒绝
关系 不能互相替代,应叠加 同上

9. 限流、熔断、系统保护(Sentinel 等)

9.1 规则类型速查

类型 控制什么 典型用途
流控 flow QPS / 并发线程数 入口削峰
网关流控 gw-flow 按 route / api Gateway 粗限流
熔断 degrade 慢调用比例 / 异常比例 / 异常数 不稳定依赖
系统 system LOAD / CPU / 入口 QPS / 线程 整机兜底
热点 param-flow 带参数限流 按租户、部门、大屏 ID

9.2 资源怎么划分

资源粒度 例子 用途
路由 / 服务 service-a 网关粗限流
整机 system 规则 动态 API 很多时的兜底
业务 API 单接口 QPS 热点接口单独收紧
下游 host tp:{host} 按厂家隔离熔断
出站逻辑名 outbound:vendor-x 与舱壁配合

9.3 熔断参数阅读示例

json 复制代码
{
  "resource": "tp:依赖host",
  "grade": 0,
  "count": 0.5,
  "timeWindow": 30,
  "minRequestAmount": 20,
  "statIntervalMs": 10000,
  "slowRatioThreshold": 0.5
}

含义(以具体 Sentinel 版本文档为准):

统计窗口内请求数达标且慢调用占比过高 → 熔断一段时间 → 期间走降级,不再打下游。

9.3.1 限流 / 熔断------怎么设(起步法)

网关 / 接口 QPS 限流:

  1. 压测得出单实例稳态 QPS(错误率可接受时的最大值)。
  2. 限流阈值 ≈ 稳态 × 0.7~0.8(留余量);多实例若用「单机限流」则每实例各自按此设,集群总能力 ≈ 阈值 × 实例数。
  3. 热点大屏 API 单独更严;整机再用 system 规则兜底。
  4. 验收: 压测尖峰时限流必须能触发并返回明确码,而不是「从不触发」。

下游熔断:

  1. 先定义「慢」:例如超过 1s(或超过你的 callTimeout 的 50%)算慢调用。
  2. minRequestAmount:窗口内至少多少请求才统计(太小会误熔断),常见 10~20。
  3. 慢调用 / 异常比例阈值从 0.5(50%)起,timeWindow 熔断时长 10~30s。
  4. 熔断打开时必须有降级返回(缓存/空数据/友好文案),否则只是换一种 500。

9.4 降级策略(产品可接受)

场景 可降级返回
统计数字 最近成功缓存 + 「非实时」标记
列表 空列表 + 「数据源繁忙」
地图 / 点位 上一帧 / 降采样
登录鉴权 不可降级,单独保护、阈值宽松

9.5 更多流量治理手段

  • 按租户 / 用户多维限流
  • 优先级:核心读 > 报表导出 > 后台任务
  • 与第三方合同 QPS 对齐的本地令牌桶
  • 过载保护(load shedding):负载高时主动丢低优先级
  • 重试:仅幂等 + 抖动退避;写接口要幂等键

知识点: 限流保护的是自己 ;熔断保护的是依赖故障时的自己。


10. 缓存

10.1 何时缓存

数据类型 建议 TTL 说明
统计 KPI / 排行 30s~5min 轮询友好
字典 / 组织树 5~30min 变更少
第三方原始结果 30s~2min key 含业务维度
强实时 不缓存或 1~3s 单独限流

10.2 单飞加载(防击穿)

text 复制代码
getOrLoad(key, ttl):
  v = redis.get(key)
  if v != null: return v
  with lock(key):          // 同 key 只放行 1 个打源站
    v = redis.get(key)
    if v != null: return v
    v = callSource()       // 走出站舱壁
    redis.setex(key, ttl, v)
    return v

10.3 三大经典问题

问题 现象 手段
击穿 热点 key 过期瞬间打穿 单飞锁 / 逻辑过期
穿透 非法参数一直打库 空值短缓存 + 参数校验 + 布隆
雪崩 大量 key 同时过期 TTL 加随机抖动 + 多级缓存

10.4 进阶

  • 多级缓存:Caffeine(本地短 TTL)+ Redis
  • 预热:发布 / 重启后灌热点
  • 逻辑过期:先返回旧值,异步刷新
  • 热点拆分:超热 key / 大 value 拆分,避免 Redis 单核热点
  • 批量:mget / pipeline 减 RTT
  • 一致性:Cache-Aside / 延迟双删等,按能否脏读选型
  • Redis 挂了要能降级:有限等待,避免整个应用堵在 Redis

10.5 Redis 连接池------怎么设

yaml 复制代码
spring:
  data:
    redis:
      timeout: 2000ms
      lettuce:
        pool:
          max-active: 64
          max-idle: 16
          min-idle: 8
          max-wait: 1000ms
参数 怎么定
timeout 单次命令超时;常见 1~3s。太长会让线程堵在 Redis
max-active 起点:与 Tomcat 繁忙线程同量级或略小(如 32~64),再压测
max-wait 必须有限(如 1s)。池满时快速失败,便于降级
min-idle / max-idle 保持少量热连接;idle 不要大于 active

调参信号: 经常等连接超时 → 先查 Redis 慢命令/网络,再略增 max-active;Redis CPU 打满 → 减连接、治热点 key,而不是只加池。

知识点: 缓存是读多场景的第一优化手段;Redis 挂了要有降级路径。


11. 数据库与预计算

11.1 连接池------怎么设

yaml 复制代码
spring:
  datasource:
    hikari:
      maximum-pool-size: 30
      minimum-idle: 10
      connection-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1800000

公式:

text 复制代码
pool_size ≈ CPU核数 × (1 + 平均等待时间 / 平均计算时间)
场景 起点举例(再压测)
8 核、SQL 较快 10~20
8 核、SQL 经常等锁/IO 20~40
多实例 所有实例池之和 < DB max_connections − 运维预留

例:DB 最大 500 连接,预留 100,3 实例 → 每实例理论上限约 133,实际常设 20~40(连接过多 DB 可能更慢)。

参数 怎么定
connection-timeout 拿连接等待上限,常见 3s;满了应失败而非无限等
max-lifetime 小于 DB 端空闲断开时间,避免半死连接
minimum-idle 略小于 maximum,保持热连接
知识点 说明
池过大 数据库上下文切换与锁争用更严重,可能更慢
池耗尽 表现像线程卡死(堵在 getConnection)
事务边界 事务内禁止调第三方 HTTP

11.2 查询与模型

  • 慢 SQL、缺索引必须先治
  • 深分页改游标 / seek;count(*) 要缓存或避免
  • 批量读写,消灭 N+1
  • 语句超时、连接泄漏检测
  • 读写分离:接受副本延迟,或关键读打主
  • 统计类主路径走预聚合 / 物化视图 / ADS,禁止运行时全市扇出下游

11.3 异步削峰(写与通知)

适合:下单、审计、通知、批量导入、非即时报表。

手段:Kafka / RocketMQ / RabbitMQ 等;消费端同样要限流与重试幂等。

知识点: 能异步的别同步扛;能预计算的别实时算。


12. 业务 API 设计与读路径改造

手段 说明
BFF / 聚合接口 一卡一聚合,减少前端扇出
服务端去重 相同参数短时共用结果
批量 API 多 id 一次查
字段裁剪 禁止「一个接口塞全量树」
大结果异步化 导出 / 大包走任务,不占同步线程
明确 429/503 带 Retry-After,让客户端有序退避
CQRS 读写模型分离,读走优化视图

13. 前端 / 客户端(L0)

措施 说明
轮询间隔 统计类宜 ≥ 30s;强实时单独通道
可见性 页签不可见时暂停轮询
首屏分批 关键优先,次要延后
失败退避 429/503 指数退避 + 抖动
合并请求 能合则合
超时对齐 与网关 / 后端一致
降级 UI 骨架屏 / 旧数据 / 「暂不可用」
长连接 WebSocket / SSE 替代高频短轮询(按场景)

知识点: 客户端齐刷重试 = 人为制造第二波高峰。


14. 多实例、网关与弹性

text 复制代码
LB / Nginx / Gateway
        │
   ┌────┴────┐
   │ inst-1  │  inst-2  │  ...
   └─────────┘
项 建议
最少实例 生产关键路径 ≥ 2
会话 Token 无状态(JWT/Redis),勿粘单机内存会话
限流核算 集群限流,或「单机阈值 × 实例数」心算清楚
发布 滚动发布;观察超时率与线程池
容器 CPU/内存 request·limit;HPA(CPU/QPS/自定义);PDB 防同时踢光
健康检查 区分存活与就绪;依赖全挂时不要假装 Ready

15. 安全与鉴权对容量的影响

  • Token / 会话校验走本地或 Redis,避免每请求打用户库
  • 鉴权结果可短缓存
  • WAF / 网关防刷与业务限流分层,避免误杀合法突发
  • 加解密尽量本地完成,勿同步远程加解密成为新瓶颈

16. 可观测、压测与应急

16.1 黄金信号

信号 含义
流量 QPS 是否突增 / 被限流
错误率 4xx/5xx、业务错误码
延迟分位 P50 / P95 / P99
饱和度 线程、连接池、队列、CPU、FD

16.2 必打日志 / 指标

信号 告警建议
出站超时(带 path) 同 path 短时超 N 次
舱壁 active / queue queue 持续接近容量
入口繁忙线程 busy ≈ max 持续
网关 / Nginx 429、5xx 突增
限流 / 熔断次数 突增
Redis / DB 池等待 等待 > 0 持续

有条件加上:全链路 Trace,一次页面打开能看到最慢一跳。

16.3 应急步骤(通用)

  1. 是否线程 / 连接打满?(jstack、池指标)
  2. 卡在哪一跳?(入口排队 vs DB vs 出站)
  3. 止血:降前端频率、收紧限流、手动熔断、下线非关键卡片
  4. 恢复后观察超时率,再逐步放流量
  5. 复盘:缺缓存?扇出?超时过长?无舱壁?

16.4 压测清单

场景 目的
首屏多用户并发 入口与缓存
单热点 API 打满 限流是否生效
下游故意 delay 舱壁 + 熔断,入口不被打满
杀一个实例 failover 与限流余量
Redis / 单依赖宕机 降级是否可接受
尖峰 + 齐刷重试 客户端退避是否有效

16.5 混沌与 SLO(进阶)

  • 为关键链路定 SLO / 错误预算
  • 定期注入:慢依赖、挂依赖、杀实例、网络丢包
  • 验证「文档里的降级」在真实故障时是否真的触发

17. 完整改造清单(自学自检用)

把「还可以设置 / 配置 / 改造」的事项收成一张表,按层勾选:

A. 架构

  • 读写分离 / CQRS
  • 预聚合 / 物化视图
  • MQ 异步削峰(写/通知)
  • 分库分表或分片(真有量再上)
  • 多活 / 单元化(高可用进阶)

B. 入口与网络

  • OS:nofile、somaxconn、端口范围
  • Nginx:限连、限速、超时对齐、keepalive、上游耗时日志
  • 静态 CDN / HTTP/2
  • LB 健康检查与多实例

C. 应用运行时

  • JVM / GC / 堆与堆外
  • Tomcat(或其它容器)线程与 accept 队列有界
  • 业务线程池命名、有界、拒绝策略正确
  • 异步日志
  • 序列化体积治理

D. 出站与韧性

  • 统一 HTTP 客户端:超时 + 池 + 舱壁
  • 按依赖拆隔离
  • 网关 / 应用限流
  • 熔断 + 产品降级
  • 系统保护规则(整机兜底)
  • 幂等与谨慎重试

E. 数据

  • Redis 池与超时;多级缓存;击穿/穿透/雪崩治理
  • DB 池、慢 SQL、事务边界
  • 批量与分页改造

F. 客户端

  • 轮询降频、合并、退避、超时对齐、降级 UI

G. 运维闭环

  • 黄金指标 + 出站 path 日志
  • 压测基线与故障演练
  • 季度复核阈值

18. 分阶段落地路线图

阶段 内容 产出
P0 止血 出站统一超时 + 连接池 + 舱壁;禁无超时客户端;超时日志带 URL 慢依赖挂不死整站
P1 入口 Nginx 限流/超时对齐;网关粗限流;入口线程合理上限 削峰、明确 429
P2 读优化 热点缓存 + 单飞;统计预聚合;接口合并 QPS 与时延下降一个数量级
P3 熔断降级 按 host/资源熔断;产品级降级 故障自动隔离
P4 稳态 大盘、压测基线、应急演练、弹性扩缩 可运营
P5 进阶 MQ 削峰、多级缓存、读写分离、SLO/混沌 长期演进

19. 验收门禁(通用)

  1. 人为将某依赖 delay 很长:入口繁忙线程上升但被舱壁/队列上限兜住,无关接口仍可用。
  2. 超时日志带 path,可在短时间定位接口。
  3. 超限返回明确 429/503,而不是整站无响应。
  4. 读多场景缓存命中率在高峰可接受(如统计类 > 80%)。
  5. 双实例杀一台,核心功能仍可用(允许降级)。
  6. 压测报告中有:基线 QPS、P95、饱和度曲线、故障注入结果。

20. 知识点速查表

知识点 一句话
有界资源 线程、连接、队列、池都必须有上限
容量公式 QPS ≈ 并发 / 耗时;先降耗时再加并发
端口 65535 只是端口号空间;入站共享监听口,≠ 全局并发上限
连接 ≠ 在飞 ≠ QPS Keep-Alive 下空闲连接很多,真正干活的是在飞请求
放大效应 一页 × N 组件 × M 下游
线程模型 入口线程阻塞在 IO = 吞吐塌方
JVM 堆 先做内存账单;Xms=Xmx;用 GC 日志判断大小,而不是猜
超时对齐 全链路超时预算一致,避免错位占坑
连接池 HTTP/DB/Redis 皆有界 + 有限等待;多实例要算总和
舱壁 限制「同时依赖某下游」的并发,且应小于 Tomcat 线程
熔断 故障后短时快速失败,给系统喘息
限流 保护自己,不是保护别人;尖峰时应能触发
缓存 读多场景第一优化;注意击穿/穿透/雪崩
预聚合 重统计禁止运行时扇出
拒绝 Abort 池满立刻失败,避免拖死调用方
幂等重试 只对安全的读重试;写要幂等键
可观测 没有 path 的超时日志 ≈ 没有运维能力
降级 高可用的最后一公里:产品可接受比「全挂」重要

21. 常见误区(对照自查)

概念级误导(65535 端口、连接≠QPS 等)见 第 2 章;这里是落地时的短清单。

误区 正确方向
线程不够就加大 Tomcat 先超时、舱壁、减耗时;按 6.3 公式算
超时调很大「更稳定」 更大超时 = 更长占用 = 更容易雪崩
只看 CPU 同时看线程、池、队列、FD、P99
堆抄 -Xmx4g 却不知为何 按 4.4 填内存账单,用 GC 日志校准
缓存了就万事大吉 还有击穿、穿透、雪崩、一致性
限流调到「从不触发」 限流要在压测尖峰时真的能保护
多实例但粘会话 无状态或外置会话
重试能救命 齐刷重试会二次打死恢复中的依赖
配置抄默认值 默认值不是你的容量模型
DB 池按单实例开很大 先算「实例数 × 池」是否打爆 DB

相关推荐
楚识科技1 小时前
XML配置OCR接入实战:自定义OCR模板从字段定义到API调用全流程
xml·java·ocr
小蒜学长2 小时前
基于Java的公司采购系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·公司采购系统
lee_tianbai2 小时前
Java SSM 电影票预定系统|完整前后端项目,开箱即用(源码分享)
java·开发语言·数据库
小坏讲微服务2 小时前
Spring Boot 4 新特性全解析:从上手到生产实战
java·spring boot·后端·架构·springboot4
砚底藏山河2 小时前
python量化入门:多周期数据对齐统一时间轴
java·数据库·python·金融·maven
集智飞行2 小时前
解决mavros2 ros2版本cpu占用高的问题
java·服务器·前端
鱼宵3 小时前
LangChain4j 结构化输出:让模型吐出 Java 对象,JSON 不再手写解析
java·开发语言·json·langchain4j
HSunR3 小时前
ruoyi 若依 自定义注解 参数校验
java·前端·数据库