<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Java on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/java/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 19 Oct 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/java/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>C 端 IPTV 产品的 Java 高并发架构设计：高可用、高性能、零信任与缓存一致性</title>
      <link>https://ftmao.com/blog/posts/iptv-java-high-concurrency-architecture/</link>
      <pubDate>Mon, 19 Oct 2026 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/iptv-java-high-concurrency-architecture/</guid>
      <description>&lt;h1 id=&#34;c-端-iptv-产品的-java-高并发架构设计高可用高性能零信任与缓存一致性&#34;&gt;C 端 IPTV 产品的 Java 高并发架构设计：高可用、高性能、零信任与缓存一致性&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;过去几年我一直在做一个面向海外的 C 端 IPTV 产品（STB 机顶盒 + 移动端，多品牌多租户，跑在 AWS 上）。这篇文章是我对这套系统架构的复盘：高并发、高可用、高性能、高安全、缓存一致性这几件事我们分别是怎么做的、踩过什么坑。过程中我们落地了不少 Netflix 的开源组件和公开实践（Hystrix、EVCache、Zuul 的网关思想、混沌工程等），但每一个决策都是经过我们自己生产环境验证或修正的，不是照搬。文中方案细节来自真实的系统设计文档（Video Cloud 目标模型、vod-auth、CloudRouter、DRM、网盘整改等），脱敏后分享。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一先搞清楚对手c-端-iptv-的流量模型&#34;&gt;一、先搞清楚对手：C 端 IPTV 的流量模型&lt;/h2&gt;
&lt;p&gt;设计之前先看清流量的形状。这是我做这套系统第一天就逼自己回答的问题：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特征&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;架构含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;潮汐效应极强&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;晚 8 点 - 11 点是高峰，大促、球赛、爆款剧上线时流量成倍脉冲&lt;/td&gt;
					&lt;td&gt;弹性伸缩 + 削峰，按高峰容量做预案&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;读极多、写极少&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;EPG 节目单、Meta 元数据、Layout 下发占了 9 成以上流量&lt;/td&gt;
					&lt;td&gt;缓存优先，读路径尽量不打 DB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;播放鉴权是超级热点&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每次起播、切台都要鉴权取播放链接，QPS 随在线用户数线性涨&lt;/td&gt;
					&lt;td&gt;热点接口单独优化（缓存 + 短 TTL + 滑动续期）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;设备在线心跳&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;app 轮询 session，百万级设备定时上报&lt;/td&gt;
					&lt;td&gt;心跳服务独立部署，写路径异步化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;多品牌多租户&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同一套系统服务多个品牌（巴西、俄语区等）&lt;/td&gt;
					&lt;td&gt;分库隔离爆炸半径，account_id 前缀区分品牌&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个流量形状——读多写少、晚间高峰、播放路径是生命线——决定了后面所有的架构取舍。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二总体架构一张图看清全貌&#34;&gt;二、总体架构：一张图看清全貌&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 终端
        STB[&amp;#34;STB 机顶盒&amp;#34;]
        APP[&amp;#34;移动 App&amp;#34;]
        WEB[&amp;#34;Web / Portal&amp;#34;]
    end

    subgraph 接入层
        ELB[&amp;#34;AWS ELB&amp;#34;]
        APISIX[&amp;#34;APISIX 网关&amp;lt;br/&amp;gt;限流 Sentinel / WAF / 鉴权前置&amp;#34;]
    end

    subgraph 微服务层
        AUTH[&amp;#34;vod-auth&amp;lt;br/&amp;gt;登录注册 / 账号 / 子账号&amp;#34;]
        SESS[&amp;#34;common-session&amp;lt;br/&amp;gt;会话管理&amp;#34;]
        META[&amp;#34;vod-meta-api&amp;lt;br/&amp;gt;元数据 / Layout / EPG&amp;#34;]
        PROF[&amp;#34;profile&amp;lt;br/&amp;gt;观看历史 / 收藏 / 偏好&amp;#34;]
        CLOUD[&amp;#34;vod-cloud&amp;lt;br/&amp;gt;个人网盘&amp;#34;]
        ROUTER[&amp;#34;CloudRouter&amp;lt;br/&amp;gt;播放链接路由&amp;#34;]
        DRM[&amp;#34;drmlicense&amp;lt;br/&amp;gt;DRM 鉴权与 license 下发&amp;#34;]
    end

    subgraph 数据层
        MYSQL[&amp;#34;MySQL RDS&amp;lt;br/&amp;gt;按品牌分库&amp;#34;]
        REDIS[&amp;#34;Redis Cluster&amp;lt;br/&amp;gt;Session / 验证码 / Meta 缓存&amp;#34;]
        METAREDIS[&amp;#34;Meta 专用 Redis Cluster&amp;#34;]
        ES[&amp;#34;Elasticsearch&amp;lt;br/&amp;gt;检索字段&amp;#34;]
        KAFKA[&amp;#34;Kafka MSK&amp;lt;br/&amp;gt;Meta 同步 / 网盘同步&amp;#34;]
    end

    subgraph 内容侧
        STREAM[&amp;#34;Streaming 流媒体&amp;#34;]
        CDN[&amp;#34;CDN / Squid 边缘缓存&amp;#34;]
        CP[&amp;#34;cloudProxy&amp;lt;br/&amp;gt;第三方网盘适配&amp;#34;]
    end

    STB --&amp;gt; ELB
    APP --&amp;gt; ELB
    WEB --&amp;gt; ELB
    ELB --&amp;gt; APISIX
    APISIX --&amp;gt; AUTH
    APISIX --&amp;gt; SESS
    APISIX --&amp;gt; META
    APISIX --&amp;gt; PROF
    APISIX --&amp;gt; CLOUD
    APISIX --&amp;gt; ROUTER
    ROUTER --&amp;gt; DRM
    ROUTER --&amp;gt; STREAM
    STREAM --&amp;gt; CDN
    STREAM --&amp;gt; CP
    META --&amp;gt; METAREDIS
    META --&amp;gt; ES
    AUTH --&amp;gt; MYSQL
    AUTH --&amp;gt; REDIS
    SESS --&amp;gt; REDIS
    CLOUD --&amp;gt; KAFKA
    META --&amp;gt; KAFKA&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个关键决策，后面逐章展开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;接入层&lt;/strong&gt;：链路是 &lt;code&gt;app -&amp;gt; ELB -&amp;gt; APISIX -&amp;gt; vod 微服务&lt;/code&gt;，限流用 Sentinel，网关做鉴权前置和 WAF。网关选型时我认真研究过 Netflix Zuul 2 从同步阻塞改异步非阻塞的历程——结论是网关本身绝不能成为瓶颈。我们体量没那么大，APISIX（底层同样是 Nginx + Lua 的高性能异步模型）已经够用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务拆分&lt;/strong&gt;：按业务域拆（Auth / Profile / Meta / Cloud / Session），不是按技术层拆。领域拆分的完整过程在&lt;a href=&#34;https://ftmao.com/blog/blog/posts/iptv/&#34;&gt;视频系统 DDD 实战&lt;/a&gt;里写过。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据层&lt;/strong&gt;：MySQL 按品牌分库（&lt;code&gt;{brand}_account_db&lt;/code&gt; 等）；Redis 承载 Session、验证码、热点数据；Meta 用&lt;strong&gt;独立的 Redis Cluster&lt;/strong&gt; 全量缓存，与业务缓存物理隔离；检索字段入 ES。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步化&lt;/strong&gt;：Kafka（MSK）只干两件事——Meta 同步管道和网盘同步，不滥用。这是我给自己立的规矩：每加一个 MQ topic 都要回答「同步调用为什么不行」。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三高并发把流量挡在离用户最近的地方&#34;&gt;三、高并发：把流量挡在离用户最近的地方&lt;/h2&gt;
&lt;p&gt;高并发的核心心法就一句：&lt;strong&gt;每一层都尽量把请求消化掉，别让它穿透到下一层&lt;/strong&gt;。越靠近 DB 越贵。&lt;/p&gt;
&lt;h3 id=&#34;31-网关层第一道削峰闸&#34;&gt;3.1 网关层：第一道削峰闸&lt;/h3&gt;
&lt;p&gt;我们在网关层做了三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;限流&lt;/strong&gt;：APISIX 自带 limit-req / limit-count 插件做粗粒度限流，Sentinel 做服务级细粒度熔断限流（现网组合就是「ELK + Skywalking + Sentinel（APISIX）」）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鉴权前置&lt;/strong&gt;：明显非法的请求（无 token、签名错误、UA 黑名单）在网关直接 401/403，不放行到业务服务。被攻击时这一层挡掉的流量经常比业务层处理的还多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;静态配置下发走 CDN&lt;/strong&gt;：&lt;code&gt;/ui/cfg/v1&lt;/code&gt;、&lt;code&gt;/layout/v1&lt;/code&gt; 这类终端配置接口，内容变化频率低，直接走 CDN 缓存，源站只承担回源请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-热点接口播放鉴权链接的缓存设计&#34;&gt;3.2 热点接口：播放鉴权链接的缓存设计&lt;/h3&gt;
&lt;p&gt;CloudRouter（播放源路由服务）是我手里最热的路径：每次起播都要按 &lt;code&gt;logicStreamId&lt;/code&gt; 换真实的下载/播放链接。我做的设计非常直接：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;App 起播请求&amp;lt;br/&amp;gt;logicStreamId&amp;#34;] --&amp;gt; B{&amp;#34;查 Redis&amp;#34;}
    B -- &amp;#34;命中&amp;#34; --&amp;gt; C[&amp;#34;刷新 TTL 滑动续期&amp;lt;br/&amp;gt;直接返回链接&amp;#34;]
    B -- &amp;#34;未命中&amp;#34; --&amp;gt; D[&amp;#34;查 MySQL 两表&amp;lt;br/&amp;gt;logic_stream + file_source&amp;#34;]
    D --&amp;gt; E[&amp;#34;构建下载链接 DownloadBO&amp;lt;br/&amp;gt;link / fileSize / expired&amp;#34;]
    E --&amp;gt; F[&amp;#34;写回 Redis&amp;#34;]
    F --&amp;gt; G[&amp;#34;返回&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个细节值得说：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;命中即续期（滑动 TTL）&lt;/strong&gt;：热播内容的链接永不过期，冷内容自然淘汰——不需要人工预热。这个设计替我省掉了整个「热点内容运营」的麻烦。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存对象自带过期时间&lt;/strong&gt;：链接本身有 &lt;code&gt;expired&lt;/code&gt; 字段，客户端在链接到期前会重新请求，和缓存 TTL 是两层独立的过期机制，互不相信。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失败快速抛出&lt;/strong&gt;：查不到直接抛业务异常，不做 fallback 到第三方存储的慢路径——播放链路每一毫秒都值钱。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;33-多级缓存l1--l2&#34;&gt;3.3 多级缓存：L1 + L2&lt;/h3&gt;
&lt;p&gt;纯 Redis 方案在超热点 key 上依然会打满单个分片（新片上线当晚的 metaId 我真实遇到过）。为了解决这个问题我研究了 Netflix 的 EVCache——它的多副本加客户端本地缓存思路给了我启发，但我们体量用不上 EVCache 全套，于是我基于 Caffeine（L1 进程内缓存）+ Redis（L2）自己实现了一个两级缓存框架并开源，见&lt;a href=&#34;https://ftmao.com/blog/blog/posts/kecache-intro/&#34;&gt;我开发的 keCache：面向 Spring Boot 3 的 L1 + L2 两级缓存框架&lt;/a&gt;，要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L1 短 TTL（秒级），扛住瞬时洪峰；&lt;/li&gt;
&lt;li&gt;L1 失效消息走 Redis Pub/Sub 广播，保证多实例间 L1 不一致的窗口可控；&lt;/li&gt;
&lt;li&gt;序列化用 Kryo，比 JDK 序列化小 3-5 倍。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;34-写路径异步化mq-削峰&#34;&gt;3.4 写路径异步化：MQ 削峰&lt;/h3&gt;
&lt;p&gt;写少但集中的场景（新片上线批量导入 Meta、网盘大规模刷新）我全部走 Kafka：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;vod-sync-dispatcher&amp;lt;br/&amp;gt;每 30 秒轮询上游片源&amp;#34;] --&amp;gt; B[&amp;#34;Kafka MSK&amp;#34;]
    B --&amp;gt; C[&amp;#34;vod-imdb-meta-sync&amp;lt;br/&amp;gt;元数据入 Redis + ES&amp;#34;]
    D[&amp;#34;vod-cloud-sync-dispatcher&amp;#34;] --&amp;gt; B
    B --&amp;gt; E[&amp;#34;vod-cloud-sync-worker&amp;lt;br/&amp;gt;刷新网盘&amp;#34;]
    F[&amp;#34;http2Msk&amp;lt;br/&amp;gt;大数据 HTTP 回调转 MQ&amp;#34;] --&amp;gt; B
    B --&amp;gt; G[&amp;#34;偏好计算&amp;lt;br/&amp;gt;you may like&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;生产者-消费者模型 + MQ 缓冲，让上游脉冲（片源方一次推几万条 meta）被摊平成消费者能吃得动的匀速流。当时的 MQ 选型过程见&lt;a href=&#34;https://ftmao.com/blog/blog/posts/mq-comparison-rabbitmq-kafka-rocketmq/&#34;&gt;消息队列总结：RabbitMQ、Kafka、RocketMQ 与 Redis MQ&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另一个异步化的例子是我设计的转码调度：&lt;strong&gt;Quartz + DB 状态机&lt;/strong&gt;驱动，每个片源转出 1-3 个分辨率、每个分辨率一个任务，用「动态归一化权重调度算法」分配执行器（priority = 客户权重 / 活跃任务数，兼顾多客户公平与并发槽位控制），30 分钟轮询任务进度。慢不要紧，稳和公平才要紧——这是典型的「离线任务用状态机换可靠性」。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四高可用假设一切依赖都会挂&#34;&gt;四、高可用：假设一切依赖都会挂&lt;/h2&gt;
&lt;p&gt;我做高可用的两条心法，是从 Netflix 的工程实践里学来并在我们系统里反复验证过的：&lt;strong&gt;Design for failure&lt;/strong&gt;（为失败而设计）和 &lt;strong&gt;Blast radius&lt;/strong&gt;（控制爆炸半径）。落地到我们系统：&lt;/p&gt;
&lt;h3 id=&#34;41-熔断降级与兜底重试&#34;&gt;4.1 熔断、降级与兜底重试&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;熔断&lt;/strong&gt;：我们早期直接用 Netflix 开源的 Hystrix 做熔断隔离，Hystrix 停更后迁移到 Resilience4j（网关层用 Sentinel）。工具换了，规则不变：下游超时率/错误率超阈值就熔断，快速失败，绝不线程排队等死。Hystrix 的线程池隔离思想对我们影响很大——核心链路（登录、起播）和非核心链路（埋点上报）的资源池必须分开。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降级而非失败&lt;/strong&gt;：Meta 自动匹配失败时我定的策略是&lt;strong&gt;降级创建默认元数据&lt;/strong&gt;，不影响文件源状态——内容能播永远比信息完美重要。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兜底重试&lt;/strong&gt;：CloudAsset 的异常处理是我给全系统定的统一模式——&lt;strong&gt;3 次以内本轮调度重试，超过 3 次任务进失败状态，由独立的失败任务调度器兜底&lt;/strong&gt;。关键设计点：主流程不被重试拖死，失败任务有独立通道排队消化，互不阻塞。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    A[&amp;#34;任务执行&amp;#34;] --&amp;gt; B{&amp;#34;成功?&amp;#34;}
    B -- &amp;#34;是&amp;#34; --&amp;gt; C[&amp;#34;完成&amp;#34;]
    B -- &amp;#34;否, failCount+1&amp;#34;] --&amp;gt; D{&amp;#34;failCount ＞ 3?&amp;#34;}
    D -- &amp;#34;否&amp;#34; --&amp;gt; A
    D -- &amp;#34;是&amp;#34; --&amp;gt; E[&amp;#34;status = fail&amp;#34;]
    E --&amp;gt; F[&amp;#34;独立失败任务调度器&amp;lt;br/&amp;gt;定时扫描兜底重试&amp;#34;]
    F --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;后来我把这类「一定要跑到」的可靠执行逻辑抽成了通用组件开源，见&lt;a href=&#34;https://ftmao.com/blog/blog/posts/ke-reliable-executor/&#34;&gt;我开发的 KE Reliable Executor&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;42-多租户隔离分库就是隔舱&#34;&gt;4.2 多租户隔离：分库就是隔舱&lt;/h3&gt;
&lt;p&gt;多品牌共享一套代码，但我在数据层做了物理隔离：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 按品牌分库（&lt;code&gt;{brand}_account_db&lt;/code&gt;、&lt;code&gt;{brand}_profile_db&lt;/code&gt;），公共数据在 &lt;code&gt;vod_meta_db&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;account_id&lt;/code&gt; 前缀区分品牌（MFC:11、TVELA:12、WAKA:13），应用层强制路由；&lt;/li&gt;
&lt;li&gt;实体普遍带 &lt;code&gt;env&lt;/code&gt; / &lt;code&gt;vnoId&lt;/code&gt; 字段，&lt;code&gt;logicStreamId&lt;/code&gt; 内嵌环境标识。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;某品牌数据被打爆、误删、需要回滚时，影响面天然被限制在单库内。这就是水密隔舱——后来某品牌做数据订正时我真切体会到这个设计的价值。&lt;/p&gt;
&lt;h3 id=&#34;43-依赖解耦订阅模式&#34;&gt;4.3 依赖解耦：订阅模式&lt;/h3&gt;
&lt;p&gt;DRM 链路里我有个坚持：drmlicense 不直接依赖 Headend 和 drmencryptor 的在线接口，而是以&lt;strong&gt;订阅模式&lt;/strong&gt;收 &lt;code&gt;DrmContentInfoBO&lt;/code&gt; 和 &lt;code&gt;ContentKeyBO&lt;/code&gt;，自己组装 logicalStream 与 PSSH 的关系。上游挂了，license 下发用已订阅到的数据照常工作。原因是算术：&lt;strong&gt;同步调用是可用性的乘法，异步订阅是加法&lt;/strong&gt;——一个可用性 99.9% 的服务同步串依赖十个同样 99.9% 的服务，链路可用性就只剩 99% 了。&lt;/p&gt;
&lt;h3 id=&#34;44-混沌工程轻量版就够&#34;&gt;4.4 混沌工程：轻量版就够&lt;/h3&gt;
&lt;p&gt;Netflix 的 Chaos Monkey 是随机杀生产实例，逼所有服务习惯「邻居随时消失」。我评估后没敢直接上生产，但保留了这个思想的两个轻量实践：预发环境定期随机关停单个实例观察告警与自愈；每个季度做一次依赖演练（手动切断 Redis 从库、Kafka 单 broker），验证降级路径真的能用。我的原则：&lt;strong&gt;降级方案不演练就等于没有&lt;/strong&gt;——每次演练几乎都能发现某条降级路径已经悄悄失效。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五高性能把钱花在读路径上&#34;&gt;五、高性能：把钱花在读路径上&lt;/h2&gt;
&lt;p&gt;高性能章节不长，因为大部分事前面已经做了。补几个我做过的针对性的点：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;优化点&lt;/th&gt;
					&lt;th&gt;做法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Meta 全量入内存&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;元数据全量进专用 Redis Cluster，终端 &lt;code&gt;/root/v1&lt;/code&gt;、&lt;code&gt;/tree/v1&lt;/code&gt; 等接口零 DB 访问&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;检索走 ES&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;只有关键检索字段入 Elasticsearch，不全量双写&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;播放内容边缘缓存&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Streaming 前置 Squid + CDN，热点片源命中边缘节点，源站只承担回源（对应 Netflix Open Connect 的角色，我们用商用 CDN 实现）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;大表治理&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;观看历史表现网 2-3 亿行，是我一直盯着的最大风险点：每天定时任务删 90 天前历史 + 按账号双表拆分（设备账号/普通账号）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JVM 与连接池&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;G1/ZGC、HikariCP 连接池按「连接数 = (核数 × 2) + 磁盘数」起步压测调优，慢 SQL 进 Skywalking 告警&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;原则：&lt;strong&gt;性能优化的 80% 是让请求根本到不了慢的地方&lt;/strong&gt;，剩下 20% 才是调参数。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六缓存一致性与缓存高可用&#34;&gt;六、缓存一致性与缓存高可用&lt;/h2&gt;
&lt;p&gt;这是全文我最想细说的一章。缓存是性能的来源，也是我事故最多的地方。&lt;/p&gt;
&lt;h3 id=&#34;61-一致性策略先写-db后删缓存&#34;&gt;6.1 一致性策略：先写 DB，后删缓存&lt;/h3&gt;
&lt;p&gt;vod-auth 的账号数据我用的是标准的 &lt;strong&gt;Cache Aside + 写后删除&lt;/strong&gt;：先写 DB，成功后再删缓存，下次读时回源重建。为什么选「删除」而不是「更新」？我想清楚过：「先写 DB 再删缓存」只有在「读线程恰好踩着写间隙回源了旧值」的极端窗口才会短暂不一致，而「更新缓存」在并发双写下会乱序覆盖出脏数据。删除的失败模式是 miss（回源一次，便宜），更新的失败模式是脏数据（播错内容，贵）。&lt;/p&gt;
&lt;p&gt;对一致性要求更高的场景（套餐权益、付费状态），我在删除之上再加一层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;延迟双删&lt;/strong&gt;：写 DB → 删缓存 → 延迟 N 秒 → 再删一次，兜住间隙回源；&lt;/li&gt;
&lt;li&gt;或用 MQ 发缓存失效消息，由消费者负责删除 + 失败重试，把「删缓存失败」这个尾巴也闭环掉。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-缓存三大经典问题的组合拳&#34;&gt;6.2 缓存三大经典问题的组合拳&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;我遇到的场景&lt;/th&gt;
					&lt;th&gt;解法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;穿透&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;恶意刷不存在的 metaId / accountId&lt;/td&gt;
					&lt;td&gt;布隆过滤器拦截 + 空值短 TTL 缓存（30 秒）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;击穿&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;爆款剧上线，热点 key 过期瞬间&lt;/td&gt;
					&lt;td&gt;逻辑过期 + 互斥重建：value 里带逻辑过期时间，过期后只放一个线程回源，其余返回旧值&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;雪崩&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;大量 key 同 TTL 同时到期&lt;/td&gt;
					&lt;td&gt;TTL 加随机抖动（基础值 ± 20%）+ Redis Cluster 多主分摊&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;逻辑过期方案有个工程细节：回源重建要用分布式锁（Meta 绑定我用的就是分布式锁，获取失败快速失败、重新调度），且锁的 TTL 必须大于回源耗时，finally 里释放——我在 CloudAsset 方案里专门写了「任何异常都会释放分布式锁、清除 ThreadLocal」，这是血泪经验，锁泄漏一次就够排查一晚上。&lt;/p&gt;
&lt;h3 id=&#34;63-缓存自身的高可用&#34;&gt;6.3 缓存自身的高可用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Redis Cluster 多主 + 从&lt;/strong&gt;：Meta 缓存我用独立集群，和 Session/验证码的集群物理隔离——Meta 集群被打挂不影响用户登录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多级缓存兜底&lt;/strong&gt;：Redis 整体不可用时，L1（Caffeine）还能扛住读过期窗口，服务降级而不是全灭。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;从 EVCache 学到的运营观&lt;/strong&gt;：我研究 Netflix EVCache 时最大的收获不是 memcached 本身，而是&lt;strong&gt;他们把缓存当成 tier-0 服务运营&lt;/strong&gt;——独立容量规划、独立监控、独立 SLO。我照这个思路给缓存集群配了和业务服务同级别的告警与容量评审。缓存在高并发系统里不是优化项，是一级基础设施——这点很多团队要到出事才懂，我庆幸自己学得早。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七零信任安全与必要防御&#34;&gt;七、零信任安全与必要防御&lt;/h2&gt;
&lt;h3 id=&#34;71-零信任永不信任始终验证&#34;&gt;7.1 零信任：永不信任，始终验证&lt;/h3&gt;
&lt;p&gt;我们的安全体系是在几次整改后逐步按零信任思路重建的——内网不再等于可信，&lt;strong&gt;每一次调用都要验证身份，不论它来自哪里&lt;/strong&gt;。落地到我的系统，是这几件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用户侧&lt;/strong&gt;：accessToken（JWT，&lt;code&gt;sub = accountId&lt;/code&gt;）+ 播放票据 tk12（内含 &lt;code&gt;expireTime / accountId / streamId&lt;/code&gt;，由平台密钥签发）。App 起播时两个都要验，token 验「你是谁」，tk12 验「你能不能播这条流、能播到什么时候」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设备侧&lt;/strong&gt;：账号卡与设备绑定（&lt;code&gt;ac_device_binding&lt;/code&gt;），&lt;code&gt;device_limit&lt;/code&gt; 限制并发设备数，挤掉盗号共享的盈利空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务间&lt;/strong&gt;：内部接口（&lt;code&gt;/internal-api/...&lt;/code&gt;）不走公网网关，服务间调用带服务身份凭证。我评估过 mTLS + SPIFFE 这套工作负载身份方案（Netflix 零信任改造用的就是这条路），对我们体量偏重，最终落地为「内部接口不暴露 + 调用方白名单」，等规模上来再升级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最小权限&lt;/strong&gt;：cloudProxy 是我专门做的收敛层——把对第三方网盘（Mega 等）的所有访问收敛成一个适配层，只暴露 &lt;code&gt;checkPassword&lt;/code&gt; 和 &lt;code&gt;getDownloadLink&lt;/code&gt; 两个接口，业务服务根本不接触第三方凭证。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;72-一次真实的安全整改网盘密码泄露&#34;&gt;7.2 一次真实的安全整改：网盘密码泄露&lt;/h3&gt;
&lt;p&gt;这是我亲手推动的整改，值得完整复盘。背景：网盘打包工具生成的加密网盘包里&lt;strong&gt;含账号 + 加密后的登录密码&lt;/strong&gt;，加解密密钥一旦泄露，所有网盘账号密码全部泄露。&lt;/p&gt;
&lt;p&gt;整改分两步走：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步，密码不落地。&lt;/strong&gt; 新网盘包不再写密码，改为写入与网盘账号一一对应的 &lt;code&gt;shareKey&lt;/code&gt;（UUID）。导入时只校验 shareKey 一致性，通过后把网盘与用户关联。按网盘包 &lt;code&gt;version&lt;/code&gt; 字段区分新老包走新旧两条校验逻辑，平滑迁移——&lt;strong&gt;存量兼容是安全改造里最容易被低估的工作量&lt;/strong&gt;，这一步我花的精力比新逻辑本身还多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步，密钥体系升级。&lt;/strong&gt; 对称加密改 RSA 非对称加密：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    A[&amp;#34;CP / Reseller&amp;lt;br/&amp;gt;只拿到 RSA 公钥&amp;#34;] --&amp;gt; B[&amp;#34;公钥加密网盘包&amp;lt;br/&amp;gt;公钥泄露也无法解包&amp;#34;]
    C[&amp;#34;VNO 上传私钥&amp;lt;br/&amp;gt;加密保存于平台 pkg_rsa_key 表&amp;#34;] --&amp;gt; D[&amp;#34;导入时按 VNO 查私钥&amp;#34;]
    B --&amp;gt; E[&amp;#34;网盘包带签名&amp;lt;br/&amp;gt;含 pkgId + keyId&amp;#34;]
    E --&amp;gt; D
    D --&amp;gt; F[&amp;#34;先验签再解密&amp;lt;br/&amp;gt;解密成功才关联用户&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;配套我设计的&lt;strong&gt;密钥轮换机制&lt;/strong&gt;：平台同时最多两个 Key 生效；更新时旧密钥先进入最长 30 天缓冲期，再生成新密钥对；存在生效中的旧密钥则不允许再次更新——防止轮换频率失控导致历史包无法解密。&lt;/p&gt;
&lt;p&gt;另外扫码登录链路我也推动做过同类加固：扫码站点从 S3 静态托管（明文登录）迁移到托管服务并接入加固方案，鉴权统一收口到 vod-auth。&lt;/p&gt;
&lt;h3 id=&#34;73-接口消息体加密https-之上的第二道锁&#34;&gt;7.3 接口消息体加密：HTTPS 之上的第二道锁&lt;/h3&gt;
&lt;p&gt;有人会问：全站都上 HTTPS 了，为什么还要在应用层再加密一次消息体？我的理由有三个，都来自实际教训：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;HTTPS 防不住终端侧的抓包&lt;/strong&gt;。Charles/Fiddler 挂个代理证书，App 和网关之间的明文报文一览无余——协议字段、业务参数、token 用法全暴露。C 端 App 发出去的那一刻起，客户端环境就不可信。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS 到网关就终止了&lt;/strong&gt;。TLS 在 ELB/APISIX 卸载后，内网转发的是明文；零信任的原则是内网也不可信，敏感报文在网关之后依然应该处于加密状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPTV 场景的特殊性&lt;/strong&gt;：播放鉴权、充值、账号接口一旦被逆向出报文格式，黑产就可以脱离 App 直接伪造请求刷接口。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我设计的方案是 &lt;strong&gt;TLS 为底线 + 应用层 AES 加密消息体&lt;/strong&gt;，核心链路如下：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;App 启动&amp;lt;br/&amp;gt;ECDH 密钥协商&amp;lt;br/&amp;gt;网关公钥经 SSL Pinning 校验&amp;#34;] --&amp;gt; B[&amp;#34;得到会话密钥 sessionKey&amp;lt;br/&amp;gt;按 session 绑定&amp;#34;]
    B --&amp;gt; C[&amp;#34;业务请求&amp;lt;br/&amp;gt;AES-256-GCM 加密 body&amp;#34;]
    C --&amp;gt; D[&amp;#34;明文头带 token + 时间戳&amp;lt;br/&amp;gt;+ nonce + HMAC 签名&amp;#34;]
    D --&amp;gt; E[&amp;#34;网关统一解密过滤器&amp;lt;br/&amp;gt;验签名 / 验时间窗 / 验 nonce&amp;#34;]
    E --&amp;gt; F[&amp;#34;业务服务收到明文&amp;lt;br/&amp;gt;响应同样加密回传&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;工程上的几个取舍，都是试出来的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;密钥协商用 ECDH，不用硬编码密钥&lt;/strong&gt;。早期图省事在 App 里内置过 AES 密钥，App 被反编译后全线失守，改成登录/启动时 ECDH 动态协商、密钥绑定 session、服务端定期强制轮换。SSL Pinning（证书固定）挡住大部分中间人代理，让 ECDH 协商本身不被劫持。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AES-256-GCM 而不是 CBC&lt;/strong&gt;：GCM 自带完整性校验（AEAD），加密和防篡改一次完成；密钥 nonce 绝不复用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;头部明文、body 加密&lt;/strong&gt;：token、时间戳、nonce、签名放明文头里，网关不解密就能先做限流和鉴权前置；body 整体加密，解密收敛在网关的统一过滤器里，业务服务无感知——加解密逻辑散落进每个 Controller 是维护灾难。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不是所有接口都加密&lt;/strong&gt;。EPG、Meta 这类公开内容接口只走 HTTPS + 签名；登录、充值、播放鉴权、账号操作这些敏感接口才上消息体加密。全量加密的 CPU 成本和排查问题的痛苦不成比例——加密后的日志排障要靠专门的脱密工具，这也是成本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;绝不自研算法&lt;/strong&gt;。所有原语都用成熟实现（AES-GCM、ECDH、HMAC-SHA256），自己拼协议可以，自己造算法不行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;74-必要防御防攻击清单&#34;&gt;7.4 必要防御：防攻击清单&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;攻击面&lt;/th&gt;
					&lt;th&gt;我们的防御手段&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;接口刷量 / CC 攻击&lt;/td&gt;
					&lt;td&gt;网关限流 + Sentinel 熔断；登录、验证码接口按 IP + 账号双维度计数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;短信/邮件轰炸机&lt;/td&gt;
					&lt;td&gt;验证码存 Redis 带 TTL，&lt;strong&gt;验证成功后立即删除&lt;/strong&gt;防重放；单账号/单 IP 频次限制&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;重放攻击&lt;/td&gt;
					&lt;td&gt;请求带时间戳 + nonce，token 短过期&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;抓包 / 中间人&lt;/td&gt;
					&lt;td&gt;HTTPS + SSL Pinning + 敏感接口消息体加密（见 7.3）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;越权访问&lt;/td&gt;
					&lt;td&gt;tk12 绑定 &lt;code&gt;accountId + streamId&lt;/code&gt;，换账号播放直接鉴权失败&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;篡改网盘包&lt;/td&gt;
					&lt;td&gt;RSA 签名验签，验签不过不解密&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;撞库 / 盗号&lt;/td&gt;
					&lt;td&gt;登录失败计数锁定（&lt;code&gt;account_black_list&lt;/code&gt;）、设备绑定、子账号体系隔离&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;敏感信息外泄&lt;/td&gt;
					&lt;td&gt;交接文档脱敏（我们的交接文档里曾出现明文环境密码，是我抓出来的典型反面教材）；日志脱敏；DB 密码字段只存哈希&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;75-drm内容安全的最后一道门&#34;&gt;7.5 DRM：内容安全的最后一道门&lt;/h3&gt;
&lt;p&gt;DRM 链路的安全设计我同样按零信任思路做：ContentKey 由独立密钥服务（drmencryptor）管理，&lt;strong&gt;先查缓存没有再向外部 DRM 厂商申请并落库缓存&lt;/strong&gt;；播放优先走离线 license（有缓存用缓存），PSSH 异常才降级在线 license。license 本身带过期时间，App 本地不持久化密钥。系统支持 Goose DRM 与 Multi-DRM（Widevine）切换，切换动作就是删旧流加新流——密钥体系与业务流解耦，这个设计让后来的 DRM 厂商迁移几乎没动业务代码。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八可观测性没有度量就没有高可用&#34;&gt;八、可观测性：没有度量就没有高可用&lt;/h2&gt;
&lt;p&gt;我们的现网组合：&lt;strong&gt;ELK 日志 + Skywalking 链路追踪 + Prometheus/Grafana 指标 + Nacos 配置中心&lt;/strong&gt;，每个子系统独立 ES index。&lt;/p&gt;
&lt;p&gt;我压测过的容量数据（v7.16 集群）：只写日志约 11 万条/秒，只写 Skywalking 约 2 万条/秒，混合写时日志 6 万 + 链路 1 万/秒。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;血泪教训&lt;/strong&gt;：周一高峰 Skywalking 曾出现 4 小时写入延迟，日志量增长也让 Logstash 排队。事后我立了两条规矩：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;日志与链路采样分离&lt;/strong&gt;：高峰期链路数据降采样，保住日志；两者绝不能互相拖死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本地日志保留 30 天&lt;/strong&gt;，crontab 定时清理——磁盘写满导致的 ELK 雪崩比业务故障还常见。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我还坚持一点：告警要打在&lt;strong&gt;用户可感知的指标&lt;/strong&gt;上（起播成功率、鉴权失败率、链接过期率），而不是只打 CPU 内存——机器好好的但用户看不了，是最尴尬的监控盲区。服务间的依赖关系复杂到一定程度后，「告警来了不知道谁影响了谁」会成为新问题，Netflix 为此专门做过一套服务拓扑系统，我研究过它的实现并整理了笔记：&lt;a href=&#34;https://ftmao.com/blog/blog/posts/netflix-service-topology-at-scale/&#34;&gt;Netflix 服务拓扑系统笔记&lt;/a&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九总结一张-checklist&#34;&gt;九、总结：一张 checklist&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;我采用的核心手段&lt;/th&gt;
					&lt;th&gt;用到的 Netflix 开源组件 / 实践&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;高并发&lt;/td&gt;
					&lt;td&gt;网关限流、缓存优先、MQ 削峰、多级缓存&lt;/td&gt;
					&lt;td&gt;Zuul 2 异步化思想、EVCache 启发 keCache&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;高可用&lt;/td&gt;
					&lt;td&gt;熔断降级、失败兜底调度器、多租户分库隔离、订阅模式解耦&lt;/td&gt;
					&lt;td&gt;Hystrix（后迁 Resilience4j/Sentinel）、Chaos Monkey 轻量版&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;高性能&lt;/td&gt;
					&lt;td&gt;Meta 全量入内存、ES 检索、CDN/Squid 边缘缓存、大表治理&lt;/td&gt;
					&lt;td&gt;Open Connect 的边缘缓存角色&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存一致性&lt;/td&gt;
					&lt;td&gt;先写 DB 后删缓存 + 延迟双删、逻辑过期防击穿、随机 TTL 防雪崩&lt;/td&gt;
					&lt;td&gt;EVCache 的缓存运营观&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;零信任&lt;/td&gt;
					&lt;td&gt;双票据鉴权、设备绑定、内部接口白名单、最小权限适配层&lt;/td&gt;
					&lt;td&gt;零信任改造思路（mTLS/SPIFFE 评估后暂缓）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;攻击防御&lt;/td&gt;
					&lt;td&gt;网关 WAF/限流、验证码防重放、RSA 验签、黑名单、敏感接口消息体加密&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可观测&lt;/td&gt;
					&lt;td&gt;ELK + Skywalking + Prometheus，日志链路采样分离&lt;/td&gt;
					&lt;td&gt;服务拓扑系统的思路&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最后一句实话：&lt;strong&gt;这套架构没有一样是发明出来的，全部是踩坑踩出来的&lt;/strong&gt;。网盘密码泄露、Skywalking 四小时延迟、2-3 亿行的历史大表——每个设计决策背后都有一次事故或一次险些事故。Netflix 的开源组件和博客帮我少踩了很多坑，但终究要自己踩过才知道坑有多深。架构师的职责不是画出完美的图，而是让每个坑只踩一次。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>我开发的keCache：面向 Spring Boot 3 的 L1 &#43; L2 两级缓存框架</title>
      <link>https://ftmao.com/blog/posts/kecache-intro/</link>
      <pubDate>Thu, 04 Sep 2025 14:16:44 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/kecache-intro/</guid>
      <description>&lt;p&gt;在高并发 Java 业务里，缓存几乎是标配。但“用缓存”这件事从来不是只有“加 Redis”一种答案：本地缓存快却解决不了多机一致，Redis 能共享却多了网络与序列化开销。我最近实现一个把两者结合起来、并针对集群一致性做了不少细节处理的小而美框架 —— &lt;strong&gt;keCache&lt;/strong&gt;，现在做一次系统梳理。&lt;/p&gt;
&lt;h2 id=&#34;一kecache-是什么&#34;&gt;一、keCache 是什么&lt;/h2&gt;
&lt;p&gt;keCache 是一个基于 &lt;strong&gt;Java 17 + Spring Boot 3.x + Caffeine + Redisson&lt;/strong&gt; 的本地缓存框架，采用 &lt;strong&gt;L1（Caffeine 本地缓存）+ L2（Redis 分布式缓存）&lt;/strong&gt; 两级架构，同时提供：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;声明式注解（&lt;code&gt;@LocalCacheable&lt;/code&gt;、&lt;code&gt;@LocalCacheEvict&lt;/code&gt;、&lt;code&gt;@LocalCacheClear&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;编程式 API（&lt;code&gt;CacheTemplate&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;集群失效广播（Redisson Pub/Sub）&lt;/li&gt;
&lt;li&gt;空值保护（防止缓存穿透）&lt;/li&gt;
&lt;li&gt;数据一致性校准 SPI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它的定位不是“又一个 Redis 客户端”，而是“让本地缓存能在集群环境下安全使用”的胶水层。&lt;/p&gt;
&lt;h3 id=&#34;11-整体架构&#34;&gt;1.1 整体架构&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    App[业务 Service] --&amp;gt;|注解或 API| Facade[&amp;#34;CacheTemplate / AOP 切面&amp;#34;]
    Facade --&amp;gt; L1{L1 Caffeine}
    L1 --&amp;gt;|命中| Return[返回数据]
    L1 --&amp;gt;|未命中| L2{L2 Redis}
    L2 --&amp;gt;|命中| Deserialize[Jackson 反序列化]
    Deserialize --&amp;gt; Backfill[回填 L1]
    Backfill --&amp;gt; Return
    L2 --&amp;gt;|未命中| Load[&amp;#34;执行方法 / 加载数据&amp;#34;]
    Load --&amp;gt; Write[&amp;#34;写入 L1 &amp;amp; L2&amp;#34;]
    Write --&amp;gt; Return
    PubSub[&amp;#34;Redisson Pub/Sub&amp;#34;] --&amp;gt;|失效事件| L1
    Calibration[CalibrationProvider SPI] --&amp;gt;|定时校准| L2&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;二开源地址&#34;&gt;二、开源地址&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Gitee：&lt;a href=&#34;https://gitee.com/soulnew/ke-cache&#34;&gt;https://gitee.com/soulnew/ke-cache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;三为什么需要-kecache与纯-redis--纯-caffeine-的对比&#34;&gt;三、为什么需要 keCache？与纯 Redis / 纯 Caffeine 的对比&lt;/h2&gt;
&lt;p&gt;很多团队在选择缓存方案时，常常在“纯 Redis”和“纯 Caffeine”之间摇摆。keCache 试图在二者之间取得平衡。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;纯 Caffeine&lt;/th&gt;
					&lt;th&gt;纯 Redis&lt;/th&gt;
					&lt;th&gt;keCache（L1 + L2）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;读取延迟&lt;/td&gt;
					&lt;td&gt;极低（内存内）&lt;/td&gt;
					&lt;td&gt;较高（网络 + 序列化）&lt;/td&gt;
					&lt;td&gt;热数据近似 Caffeine，冷数据走 Redis&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单机内存压力&lt;/td&gt;
					&lt;td&gt;大（全部数据在本 JVM）&lt;/td&gt;
					&lt;td&gt;小（Redis 承担）&lt;/td&gt;
					&lt;td&gt;小（只有热数据在本地）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集群一致性&lt;/td&gt;
					&lt;td&gt;无，各节点独立加载&lt;/td&gt;
					&lt;td&gt;强一致&lt;/td&gt;
					&lt;td&gt;最终一致，写操作广播失效&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存穿透保护&lt;/td&gt;
					&lt;td&gt;需自行实现&lt;/td&gt;
					&lt;td&gt;需自行实现&lt;/td&gt;
					&lt;td&gt;内置 NullValue 短 TTL 保护&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多机共享&lt;/td&gt;
					&lt;td&gt;不支持&lt;/td&gt;
					&lt;td&gt;支持&lt;/td&gt;
					&lt;td&gt;支持（L2 Redis）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;适用场景&lt;/td&gt;
					&lt;td&gt;只读/准静态数据、单节点&lt;/td&gt;
					&lt;td&gt;写多读少、需要强一致&lt;/td&gt;
					&lt;td&gt;读多写少、可接受最终一致&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;31-相比纯-caffeine-的好处&#34;&gt;3.1 相比纯 Caffeine 的好处&lt;/h3&gt;
&lt;p&gt;Caffeine 是 JVM 内缓存的佼佼者，但它天然只服务于当前进程。在集群部署时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每台机器都要独立加载数据&lt;/strong&gt;，导致数据库在启动/预热时被打 N 次。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据更新后其他节点无法感知&lt;/strong&gt;，容易出现 A 节点已更新、B 节点仍读旧值的“ stale data ”问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存占用随节点数线性增长&lt;/strong&gt;，同样一份热数据在每个 JVM 都存一份。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;keCache 用 L2 Redis 作为共享底座：首次加载从 Redis 取，后续读请求命中本地 Caffeine；写操作时通过 Redis Pub/Sub 通知其他节点清除本地缓存。这样既保留了 Caffeine 的本地速度，又解决了多机一致性难题。&lt;/p&gt;
&lt;h3 id=&#34;32-相比纯-redis-的好处&#34;&gt;3.2 相比纯 Redis 的好处&lt;/h3&gt;
&lt;p&gt;Redis 虽然性能好，但毕竟跨进程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;网络 RTT 不可忽视&lt;/strong&gt;：高频读取时，哪怕 1ms 也会累积成可观的延迟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列化/反序列化开销&lt;/strong&gt;：Java 对象要先转成 JSON/二进制，再还原，CPU 和 GC 都有压力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;热点 Key 风险&lt;/strong&gt;：大促期间某些热 Key 会被反复打到同一个 Redis 节点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;keCache 把最热的数据放在本地 JVM 里，只有本地未命中或更新时才访问 Redis，显著降低网络和序列化开销，也分散了 Redis 热点压力。&lt;/p&gt;
&lt;h3 id=&#34;33-方案选型速查&#34;&gt;3.3 方案选型速查&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    Start([开始选缓存方案]) --&amp;gt; Q1{是否需要多机共享?}
    Q1 --&amp;gt;|否| Q2{数据是否只读 /&amp;lt;br/&amp;gt;准静态?}
    Q2 --&amp;gt;|是| Caffeine[纯 Caffeine&amp;lt;br/&amp;gt;简单高效]
    Q2 --&amp;gt;|否| Q3{能否接受最终一致?}
    Q3 --&amp;gt;|能| KeCache[keCache&amp;lt;br/&amp;gt;L1 + L2 加速]
    Q3 --&amp;gt;|否| RedisOnly[纯 Redis /&amp;lt;br/&amp;gt;DB 强一致]
    Q1 --&amp;gt;|是| Q4{写多读少 /&amp;lt;br/&amp;gt;强一致?}
    Q4 --&amp;gt;|是| RedisOnly
    Q4 --&amp;gt;|否| KeCache&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;34-一句话总结&#34;&gt;3.4 一句话总结&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;keCache 适合“读多写少、能接受最终一致、希望兼顾本地速度与集群共享”的业务场景；如果你的业务对实时一致性要求极高（如库存扣减、金融记账），建议直接走 Redis 或数据库方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;四核心能力速览&#34;&gt;四、核心能力速览&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;能力&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;声明式缓存&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;@LocalCacheable&lt;/code&gt; / &lt;code&gt;@LocalCacheEvict&lt;/code&gt; / &lt;code&gt;@LocalCacheClear&lt;/code&gt;，支持 SpEL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多键映射&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;@LocalCacheable.Multi&lt;/code&gt; 一次方法返回构建多个索引 key&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;编程式 API&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;CacheTemplate.get/put/evict/clear&lt;/code&gt;，类型安全&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;二级缓存&lt;/td&gt;
					&lt;td&gt;L1 Caffeine + L2 Redis，读时先 L1 再 L2，未命中回调加载&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;空值保护&lt;/td&gt;
					&lt;td&gt;DB 不存在的 key 缓存短 TTL 的 NullValue，防止缓存穿透&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集群一致性&lt;/td&gt;
					&lt;td&gt;Redisson Pub/Sub 广播失效事件，其他 JVM 清除本地 L1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自失效屏蔽&lt;/td&gt;
					&lt;td&gt;事件携带 &lt;code&gt;sourceId&lt;/code&gt;，本机忽略自己发出的事件，避免误清本地缓存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;广播风暴防护&lt;/td&gt;
					&lt;td&gt;远程失效事件只清 L1，不再次广播&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;数据校准&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;CalibrationProvider&lt;/code&gt; SPI，支持时间戳扫描或 CDC 扩展&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;41-读取流程&#34;&gt;4.1 读取流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    Start([读请求]) --&amp;gt; L1[查 L1 Caffeine]
    L1 --&amp;gt;|命中| A[直接返回]
    L1 --&amp;gt;|未命中| L2[查 L2 Redis]
    L2 --&amp;gt;|命中| Deserialize[Jackson 反序列化]
    Deserialize --&amp;gt; Backfill[回填 L1]
    Backfill --&amp;gt; Return[返回数据]
    L2 --&amp;gt;|未命中| Method[执行真实方法]
    Method --&amp;gt; NullCheck{结果是否为 null?}
    NullCheck --&amp;gt;|是| NullVal[写入 NullValue 到 L1/L2&amp;lt;br/&amp;gt;TTL: null-value-ttl]
    NullCheck --&amp;gt;|否| RealVal[写入真实值到 L1/L2]
    NullVal --&amp;gt; ReturnNull[返回 null]
    RealVal --&amp;gt; Return&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-写入与集群失效广播流程&#34;&gt;4.2 写入与集群失效广播流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as 节点 A（写请求）
    participant DB as 数据库
    participant R as Redis（L2）
    participant P as Redis Pub/Sub
    participant B as 节点 B
    participant C as 节点 C

    A-&amp;gt;&amp;gt;DB: 更新数据
    A-&amp;gt;&amp;gt;R: 删除/覆盖 L2 Key
    A-&amp;gt;&amp;gt;P: 发布 EvictEvent（携带 sourceId）
    A-&amp;gt;&amp;gt;A: 清除本机 L1

    P-&amp;gt;&amp;gt;B: 接收 EvictEvent
    B-&amp;gt;&amp;gt;B: sourceId ≠ 本机？
    B-&amp;gt;&amp;gt;B: 清除本地 L1
    Note over B: 只清 L1，不再次广播

    P-&amp;gt;&amp;gt;C: 接收 EvictEvent
    C-&amp;gt;&amp;gt;C: sourceId ≠ 本机？
    C-&amp;gt;&amp;gt;C: 清除本地 L1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;五快速开始&#34;&gt;五、快速开始&lt;/h2&gt;
&lt;h3 id=&#34;51-引入-starter&#34;&gt;5.1 引入 Starter&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.keke&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;kecache-spring-boot-starter&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0-SNAPSHOT&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;52-启用缓存&#34;&gt;5.2 启用缓存&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.keke.kecache.starter.annotation.EnableKeCache;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.springframework.boot.autoconfigure.SpringBootApplication;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SpringBootApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@EnableKeCache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Application&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SpringApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(Application.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;53-基础配置&#34;&gt;5.3 基础配置&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;local-cache&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;enabled&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;namespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;kecache.sample&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;default-ttl&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;null-value-ttl&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;5m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;caffeine&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;default&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;maximum-size&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;expire-after-write&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;record-stats&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;address&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;redis://127.0.0.1:6379&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;broadcast-channel&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;kecache:evict&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;default-ttl&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;calibration&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;enabled&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;54-声明式使用示例&#34;&gt;5.4 声明式使用示例&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AccountService&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;id_&amp;#39; + #accountId&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        unless &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#result == null&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; AccountInfo &lt;span style=&#34;color:#a6e22e&#34;&gt;getById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadById&lt;/span&gt;(accountId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 一次查询构建多个索引 key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable.Multi&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;id_&amp;#39; + #accountId&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;nm_&amp;#39; + #result.accountName&amp;#34;&lt;/span&gt;, unless &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#result == null&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; AccountInfo &lt;span style=&#34;color:#a6e22e&#34;&gt;getByIdMultiIndex&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadById&lt;/span&gt;(accountId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheEvict&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;id_&amp;#39; + #accountId&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;updateById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId, AccountInfo info) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;update&lt;/span&gt;(accountId, info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheClear&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clearAll&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;clear&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;55-编程式使用&#34;&gt;5.5 编程式使用&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AccountService&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Autowired&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; CacheTemplate cacheTemplate;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; AccountInfo &lt;span style=&#34;color:#a6e22e&#34;&gt;getById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; cacheTemplate.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id_&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; accountId, key &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadById&lt;/span&gt;(accountId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h2 id=&#34;六模块结构&#34;&gt;六、模块结构&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    Parent[kecache-parent] --&amp;gt; API[kecache-api&amp;lt;br/&amp;gt;注解 / SPI / 配置模型 / 事件]
    Parent --&amp;gt; Core[kecache-core&amp;lt;br/&amp;gt;统一门面 / Key策略 / 空值保护]
    Parent --&amp;gt; Caffeine[kecache-provider-caffeine&amp;lt;br/&amp;gt;L1 Caffeine 实现]
    Parent --&amp;gt; Redisson[kecache-provider-redisson&amp;lt;br/&amp;gt;L2 Redis + 失效广播]
    Parent --&amp;gt; Calibration[kecache-calibration&amp;lt;br/&amp;gt;数据一致性校准]
    Parent --&amp;gt; Starter[kecache-spring-boot-starter&amp;lt;br/&amp;gt;自动配置 + AOP]
    Parent --&amp;gt; Sample[kecache-spring-boot-sample&amp;lt;br/&amp;gt;示例应用 + 测试]

    Starter --&amp;gt; API
    Starter --&amp;gt; Core
    Core --&amp;gt; Caffeine
    Core --&amp;gt; Redisson
    Core --&amp;gt; Calibration&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;七关键约定写代码前必读&#34;&gt;七、关键约定（写代码前必读）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L1 key&lt;/strong&gt;：&lt;code&gt;cacheName#dataKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L2 key&lt;/strong&gt;：&lt;code&gt;{namespace|cacheName}:dataKey&lt;/code&gt;（使用 hash tag 避免集群模式下 slot 分散）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认读写策略&lt;/strong&gt;：Cache-Aside&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失效广播频道&lt;/strong&gt;：默认 &lt;code&gt;kecache:evict&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列化&lt;/strong&gt;：Jackson 启用 default typing，保留类型信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;八使用注意点与避坑指南&#34;&gt;八、使用注意点与避坑指南&lt;/h2&gt;
&lt;h3 id=&#34;81-redis-是硬性依赖&#34;&gt;8.1 Redis 是硬性依赖&lt;/h3&gt;
&lt;p&gt;L2 缓存和失效广播都依赖 Redis，测试与运行前请确保 Redis 可达。运行全部测试需要本地启动 Redis：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mvn clean test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;82-集群一致性是最终一致不是实时一致&#34;&gt;8.2 集群一致性是“最终一致”，不是实时一致&lt;/h3&gt;
&lt;p&gt;本地缓存存在一致窗口：A 节点写入并广播，B 节点收到消息前仍可能读到旧值。对于库存、余额、订单状态等强一致场景，建议绕过本地缓存，直接走 Redis 或数据库。&lt;/p&gt;
&lt;h3 id=&#34;83-空值保护是把双刃剑&#34;&gt;8.3 空值保护是把双刃剑&lt;/h3&gt;
&lt;p&gt;默认对 &lt;code&gt;null&lt;/code&gt; 结果缓存 5 分钟，可防止缓存穿透。但如果业务刚刚从 DB 写入了一条新记录，而之前查询时正好命中了 NullValue，就会出现“写入后 5 分钟内查不到”的延迟。需要时可：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缩短 &lt;code&gt;null-value-ttl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在写入后主动 &lt;code&gt;@LocalCacheEvict&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;对新增数据敏感的 cacheName 关闭空值保护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;84-序列化类型变更要兼容&#34;&gt;8.4 序列化类型变更要兼容&lt;/h3&gt;
&lt;p&gt;Redis 层使用 Jackson 并保留类型信息。如果缓存对象发生不兼容的类结构变更（如字段类型改变、删除字段、包名调整），旧数据反序列化可能失败。建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对缓存 DTO 做版本控制&lt;/li&gt;
&lt;li&gt;重大变更时清空 Redis 对应 key 或切换 &lt;code&gt;cacheName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;避免把接口、匿名类、动态代理对象放入缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;85-注意-key-的命名空间隔离&#34;&gt;8.5 注意 Key 的命名空间隔离&lt;/h3&gt;
&lt;p&gt;L2 key 格式为 &lt;code&gt;{namespace|cacheName}:dataKey&lt;/code&gt;。如果多个业务共用同一个 Redis 实例，务必设置不同的 &lt;code&gt;namespace&lt;/code&gt;，否则可能出现 Key 冲突。&lt;/p&gt;
&lt;h3 id=&#34;86-localcacheablemulti-的-result-引用要判空&#34;&gt;8.6 &lt;code&gt;@LocalCacheable.Multi&lt;/code&gt; 的 &lt;code&gt;result&lt;/code&gt; 引用要判空&lt;/h3&gt;
&lt;p&gt;多索引注解里如果通过 &lt;code&gt;#result.xxx&lt;/code&gt; 取字段，必须配合 &lt;code&gt;unless = &amp;quot;#result == null&amp;quot;&lt;/code&gt;，否则方法返回 null 时会触发 SpEL 计算异常。&lt;/p&gt;
&lt;h3 id=&#34;87-aop-自调用不会生效&#34;&gt;8.7 AOP 自调用不会生效&lt;/h3&gt;
&lt;p&gt;和 Spring Cache 一样，keCache 的注解基于 AOP。同类方法内部调用（&lt;code&gt;this.method()&lt;/code&gt;）不会走代理，缓存注解不会生效。需要时注入自身代理或改用编程式 &lt;code&gt;CacheTemplate&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;88-谨慎使用-localcacheclear&#34;&gt;8.8 谨慎使用 &lt;code&gt;@LocalCacheClear&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@LocalCacheClear&lt;/code&gt; 会清空整个 &lt;code&gt;cacheName&lt;/code&gt; 的本地缓存并广播。在高并发场景下，频繁全量清除会：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;导致本地缓存命中率骤降&lt;/li&gt;
&lt;li&gt;引发集体回源，DB 压力陡增&lt;/li&gt;
&lt;li&gt;产生大量广播消息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建议优先使用 &lt;code&gt;@LocalCacheEvict&lt;/code&gt; 精确驱逐。&lt;/p&gt;
&lt;h3 id=&#34;89-校准任务不是开箱即用的&#34;&gt;8.9 校准任务不是开箱即用的&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CalibrationProvider&lt;/code&gt; 目前只是 SPI 骨架，需要业务方自己实现 &lt;code&gt;fetchChanges(Instant start, Instant end)&lt;/code&gt; 逻辑（如查变更表、CDC 日志）。开启 &lt;code&gt;local-cache.calibration.enabled=true&lt;/code&gt; 后才会按 Cron 触发。&lt;/p&gt;
&lt;h3 id=&#34;810-当前-redis-部署模式限制&#34;&gt;8.10 当前 Redis 部署模式限制&lt;/h3&gt;
&lt;p&gt;自动配置里的 &lt;code&gt;RedissonClient&lt;/code&gt; 目前只实现了 &lt;strong&gt;single 单节点&lt;/strong&gt;模式。如果生产环境使用 Redis Cluster、Sentinel 或主从，需要自行扩展配置。&lt;/p&gt;
&lt;h3 id=&#34;811-关注-caffeine-内存与统计&#34;&gt;8.11 关注 Caffeine 内存与统计&lt;/h3&gt;
&lt;p&gt;默认开启 &lt;code&gt;record-stats: true&lt;/code&gt;，本地缓存命中率、加载次数等都会被记录。建议配合 actuator / Micrometer（后续可扩展）监控：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;maximum-size&lt;/code&gt; 是否设置合理&lt;/li&gt;
&lt;li&gt;本地缓存命中率&lt;/li&gt;
&lt;li&gt;加载耗时与异常率&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;九许可证&#34;&gt;九、许可证&lt;/h2&gt;
&lt;p&gt;keCache 采用 &lt;strong&gt;MIT&lt;/strong&gt; 协议开源。&lt;/p&gt;
&lt;h2 id=&#34;十总结&#34;&gt;十、总结&lt;/h2&gt;
&lt;p&gt;keCache 是一个目标聚焦、实现克制的两级缓存框架：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你想要 &lt;strong&gt;Caffeine 的速度 + Redis 的共享能力&lt;/strong&gt;，它值得尝试。&lt;/li&gt;
&lt;li&gt;如果你希望 &lt;strong&gt;声明式注解即可解决集群本地缓存一致性问题&lt;/strong&gt;，它提供了失效广播与自失效屏蔽。&lt;/li&gt;
&lt;li&gt;如果你的业务对 &lt;strong&gt;实时一致性零容忍&lt;/strong&gt;，请把它当作“加速层”而非“唯一数据源”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有兴趣可以直接到 Gitee 查看源码与示例：&lt;a href=&#34;https://gitee.com/soulnew/ke-cache&#34;&gt;https://gitee.com/soulnew/ke-cache&lt;/a&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>我开发的KE Reliable Executor：基于 Redis/MySQL 的 Spring Boot 可靠任务执行框架</title>
      <link>https://ftmao.com/blog/posts/ke-reliable-executor/</link>
      <pubDate>Mon, 04 Aug 2025 17:10:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/ke-reliable-executor/</guid>
      <description>&lt;h1 id=&#34;我开发的ke-reliable-executor基于-redismysql-的-spring-boot-可靠任务执行框架&#34;&gt;我开发的KE Reliable Executor：基于 Redis/MySQL 的 Spring Boot 可靠任务执行框架&lt;/h1&gt;
&lt;p&gt;在微服务里，异步任务随处可见：支付后通知第三方、订单超时取消、批量对账、报表生成……这些任务有一个共同诉求——&lt;strong&gt;不能丢、不能重复执行、失败能重试、状态要能查&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;传统做法是直接引入 Kafka、RocketMQ 这类重型消息中间件。但对于很多中小型业务来说，这未免太重：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要额外维护 Broker、Topic、消费组；&lt;/li&gt;
&lt;li&gt;团队要熟悉一套新的运维和监控体系；&lt;/li&gt;
&lt;li&gt;为了“可靠”还要配置死信队列、事务消息等高级特性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你的系统里已经有 Redis（或 MySQL），并且任务量级尚未达到 MQ 的吞吐上限，那么完全可以基于 Redis 搭一个&lt;strong&gt;轻量且可靠的异步任务执行框架&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我开发是这样一个框架：&lt;strong&gt;KE Reliable Executor&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;开源地址&lt;/strong&gt;：https://gitee.com/soulnew/reliableexecutor&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一为什么需要这个工具&#34;&gt;一、为什么需要这个工具？&lt;/h2&gt;
&lt;p&gt;先回顾一下常见异步任务场景里的痛点：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;痛点&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;任务丢失&lt;/td&gt;
					&lt;td&gt;服务重启或执行线程异常退出后，未执行的任务找不到了&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;重复执行&lt;/td&gt;
					&lt;td&gt;多实例部署时，同一个任务被多个节点同时消费&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;失败即结束&lt;/td&gt;
					&lt;td&gt;业务异常或网络抖动后，没有自动重试机制&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;状态黑盒&lt;/td&gt;
					&lt;td&gt;无法查询任务当前是成功、失败、执行中还是已取消&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;接入成本高&lt;/td&gt;
					&lt;td&gt;引入 MQ 需要改代码、加依赖、搭集群&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;KE Reliable Executor 要解决的就是这些问题。它把“可靠任务执行”抽象成一个 Spring Boot Starter：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务方只负责&lt;strong&gt;定义任务处理器&lt;/strong&gt;和&lt;strong&gt;提交任务&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;框架负责&lt;strong&gt;存储、调度、加锁、重试、状态流转、清理&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;所有状态都可通过 API 查询，也可通过 Micrometer 指标监控。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;适合什么场景&#34;&gt;适合什么场景？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;已有 Redis/MySQL，不想额外维护 MQ；&lt;/li&gt;
&lt;li&gt;任务量中等，对可靠性和重试有要求；&lt;/li&gt;
&lt;li&gt;需要任务状态可查询、可人工介入；&lt;/li&gt;
&lt;li&gt;团队希望以最小成本获得“类 MQ”的可靠执行能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;不适合什么场景&#34;&gt;不适合什么场景？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;海量消息、高吞吐（Kafka/RocketMQ 更合适）；&lt;/li&gt;
&lt;li&gt;严格顺序消费（ZSET 按时间排序，不保证业务绝对顺序）；&lt;/li&gt;
&lt;li&gt;需要复杂路由、死信队列、事务消息等高级 MQ 特性。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二与竞品的对比&#34;&gt;二、与竞品的对比&lt;/h2&gt;
&lt;h3 id=&#34;21-与-kafka--rocketmq-对比&#34;&gt;2.1 与 Kafka / RocketMQ 对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;Kafka / RocketMQ&lt;/th&gt;
					&lt;th&gt;KE Reliable Executor&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;基础设施&lt;/td&gt;
					&lt;td&gt;需要独立 Broker 集群&lt;/td&gt;
					&lt;td&gt;复用 Redis 或 MySQL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;运维成本&lt;/td&gt;
					&lt;td&gt;高（Topic、消费组、监控）&lt;/td&gt;
					&lt;td&gt;低（Spring Boot Starter）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;接入成本&lt;/td&gt;
					&lt;td&gt;需要学习 SDK 与语义&lt;/td&gt;
					&lt;td&gt;注解 + 注入接口即可&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;吞吐量&lt;/td&gt;
					&lt;td&gt;极高&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;状态查询&lt;/td&gt;
					&lt;td&gt;需自建审计表&lt;/td&gt;
					&lt;td&gt;内置状态查询&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;重试机制&lt;/td&gt;
					&lt;td&gt;需配置消费重试&lt;/td&gt;
					&lt;td&gt;内置可配置重试&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;适用阶段&lt;/td&gt;
					&lt;td&gt;大型业务、高并发&lt;/td&gt;
					&lt;td&gt;中小型业务、快速落地&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;结论：KE Reliable Executor 不是取代 MQ，而是&lt;strong&gt;在“已有 Redis/MySQL、任务量可控”的场景下，提供一个更轻量的选择&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;22-与-spring-scheduled--数据库轮询对比&#34;&gt;2.2 与 Spring &lt;code&gt;@Scheduled&lt;/code&gt; + 数据库轮询对比&lt;/h3&gt;
&lt;p&gt;很多团队最早会用数据库表 + &lt;code&gt;@Scheduled&lt;/code&gt; 轮询来实现异步任务。这种做法的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无分布式锁&lt;/strong&gt;：多实例同时轮询会重复执行；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无状态机&lt;/strong&gt;：任务状态全靠字段控制，容易混乱；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无统一重试策略&lt;/strong&gt;：重试间隔、重试次数需要手写；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无优雅关闭&lt;/strong&gt;：应用关闭时可能丢失正在执行的任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;KE Reliable Executor 把这些都封装好了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Master 选举 + 任务级锁保证不重复；&lt;/li&gt;
&lt;li&gt;明确的状态机（CREATED → RUNNING → PENDING/COMPLETED/FAILED）；&lt;/li&gt;
&lt;li&gt;可配置的重试间隔与次数；&lt;/li&gt;
&lt;li&gt;优雅释放锁与线程池关闭。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;23-与-xxl-job--powerjob-对比&#34;&gt;2.3 与 XXL-JOB / PowerJob 对比&lt;/h3&gt;
&lt;p&gt;XXL-JOB 和 PowerJob 是优秀的分布式任务调度平台，但它们更适合&lt;strong&gt;定时任务&lt;/strong&gt;和&lt;strong&gt;调度中心&lt;/strong&gt;模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要单独部署调度中心；&lt;/li&gt;
&lt;li&gt;适合 Cron 表达式触发的批处理任务；&lt;/li&gt;
&lt;li&gt;任务由业务主动提交的场景下，接入不够自然。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;KE Reliable Executor 更像一个&lt;strong&gt;嵌入式任务队列&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无额外服务，随业务应用启动；&lt;/li&gt;
&lt;li&gt;任务由业务代码主动提交；&lt;/li&gt;
&lt;li&gt;强调“提交即可靠执行”，而非定时触发。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三整体架构&#34;&gt;三、整体架构&lt;/h2&gt;
&lt;h3 id=&#34;31-模块解构图&#34;&gt;3.1 模块解构图&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[业务服务] --&amp;gt;|submitTask| B(ReliableExecutor API)
    B --&amp;gt; C[TaskScheduler]
    C --&amp;gt; D{Master 锁}
    D --&amp;gt;|持有锁| E[扫描 ZSET 任务]
    E --&amp;gt; F[任务级锁]
    F --&amp;gt;|获取成功| G[提交线程池]
    G --&amp;gt; H[TaskExecutor]
    H --&amp;gt; I[&amp;#34;任务处理器 @ReliableTaskHandler&amp;#34;]
    I --&amp;gt;|成功| J[移入 FINISHED]
    I --&amp;gt;|失败| K[更新为 PENDING 等待重试]
    C --&amp;gt; L[TaskStorage]
    L --&amp;gt; M[RedisTaskStorage]
    L --&amp;gt; N[JdbcTaskStorage]
    H --&amp;gt; O[ReliableTaskListener]
    C --&amp;gt; P[ReliableExecutorMetrics]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;32-两层锁模型&#34;&gt;3.2 两层锁模型&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 全局层
        A[Master 锁] --&amp;gt;|仅一个实例| B[负责调度扫描]
    end
    subgraph 任务层
        C[&amp;#34;任务级锁 task-lock:{taskId}&amp;#34;] --&amp;gt;|仅一个线程| D[执行任务]
    end
    B --&amp;gt; C&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Master 锁&lt;/strong&gt;：同一时刻只有一个实例负责扫描任务，避免多节点拉取同一批任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务级锁&lt;/strong&gt;：针对每个 CREATED 任务，提交执行前再抢一次锁，保证同一任务不会被并发执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;33-队列设计&#34;&gt;3.3 队列设计&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[提交任务] --&amp;gt;|add| B[Redis ZSET running-task-zset]
    B --&amp;gt;|ZRANGE 按 score| C[调度器]
    C --&amp;gt;|执行成功/失败/超时| D[Lua 原子转移]
    D --&amp;gt;|RPUSH + ZREM| E[Redis List finished-task-list]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;使用 ZSET 而非 List 的核心原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;天然支持时间调度&lt;/strong&gt;：score 编码创建时间或下次重试时间；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务唯一&lt;/strong&gt;：同一个 member 在队列中只有一份；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态分区&lt;/strong&gt;：CREATED 与 PENDING 使用不同 score 基数，一次范围查询即可区分。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Score 编码规则：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;score = BASE + epochMillis * 100 + retryCount
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CREATED 基数：1_000_000_000_000_000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PENDING 基数：2_000_000_000_000_000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;34-任务状态机&#34;&gt;3.4 任务状态机&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;stateDiagram-v2
    [*] --&amp;gt; CREATED: submitTask
    CREATED --&amp;gt; RUNNING: 调度/立即执行
    RUNNING --&amp;gt; COMPLETED: 执行成功
    RUNNING --&amp;gt; PENDING: 执行失败，未超重试
    PENDING --&amp;gt; RUNNING: 到达重试时间
    RUNNING --&amp;gt; FAILED: 超过重试次数
    RUNNING --&amp;gt; TIMEOUT: 超过最大时长
    CREATED --&amp;gt; CANCELLED: 取消任务
    PENDING --&amp;gt; CANCELLED: 取消任务
    RUNNING --&amp;gt; ERROR: 无处理器/参数错误&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;四核心设计要点&#34;&gt;四、核心设计要点&lt;/h2&gt;
&lt;h3 id=&#34;41-lua-原子转移&#34;&gt;4.1 Lua 原子转移&lt;/h3&gt;
&lt;p&gt;任务进入终态时，必须保证“写入 finished list”和“从 ZSET 移除”是原子的，否则可能出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务已判定成功但还在 ZSET 里，被下一次调度捞出来；&lt;/li&gt;
&lt;li&gt;任务已从 ZSET 移除但 finished list 没写入，查询不到结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;框架使用 Lua 脚本完成这一转移：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; score &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ZSCORE&amp;#39;&lt;/span&gt;, KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;RPUSH&amp;#39;&lt;/span&gt;, KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;], ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; score &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ZREM&amp;#39;&lt;/span&gt;, KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;逻辑：先查 ZSET，无论是否存在都把终态信息写入 finished list，如果存在再删除。整个脚本在 Redis 单线程执行，天然原子。&lt;/p&gt;
&lt;h3 id=&#34;42-double-check&#34;&gt;4.2 Double-Check&lt;/h3&gt;
&lt;p&gt;调度器拿到任务级锁后、提交线程池前，会再次回查 Redis：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;任务是否仍在 running ZSET 中？&lt;/li&gt;
&lt;li&gt;任务状态是否仍是 CREATED？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;任一条件不满足，立即释放锁并跳过。这是经典的双重检查模式，把“检查-执行”串在锁保护的临界区内。&lt;/p&gt;
&lt;h3 id=&#34;43-双存储后端&#34;&gt;4.3 双存储后端&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[TaskStorage 接口] --&amp;gt; B[RedisTaskStorage]
    A --&amp;gt; C[JdbcTaskStorage]
    B --&amp;gt; D[Redisson ZSET + List]
    C --&amp;gt; E[reliable_executor_task 表]
    F[配置 reliable-executor.task-storage] --&amp;gt;|redis/mysql| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;redis&lt;/code&gt;：默认，适合已有 Redis 的场景；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mysql&lt;/code&gt;：适合不希望引入 Redis 或需要更强持久化的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两种后端通过 Spring 条件装配切换，业务代码无感知。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五快速开始&#34;&gt;五、快速开始&lt;/h2&gt;
&lt;h3 id=&#34;51-引入依赖&#34;&gt;5.1 引入依赖&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-gradle&#34; data-lang=&#34;gradle&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dependencies &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    implementation &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;com.ftmao:ke-reliable-executor:1.0.0-SNAPSHOT&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    implementation &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;org.redisson:redisson-spring-boot-starter:3.27.2&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;52-配置&#34;&gt;5.2 配置&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spring&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;application&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;my-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;redisson&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;config&lt;/span&gt;: |&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        singleServerConfig:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;          address: &amp;#34;redis://127.0.0.1:6379&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;reliable-executor&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;task-storage&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;core-pool-size&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;max-pool-size&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;queue-capacity&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;53-定义处理器&#34;&gt;5.3 定义处理器&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderTaskHandler&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@ReliableTaskHandler&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;process_order&amp;#34;&lt;/span&gt;, paramType &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; OrderParam.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;processOrder&lt;/span&gt;(OrderParam param) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 业务逻辑&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@ReliableTaskHandler&lt;/span&gt;(name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;send_notify&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sendNotify&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 无参任务&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;54-提交任务&#34;&gt;5.4 提交任务&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OrderService&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; ReliableExecutor reliableExecutor;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;submitOrder&lt;/span&gt;(OrderParam param) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TaskSubmitRequest request &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TaskSubmitRequest.&lt;span style=&#34;color:#a6e22e&#34;&gt;builder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;taskId&lt;/span&gt;(UUID.&lt;span style=&#34;color:#a6e22e&#34;&gt;randomUUID&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;taskName&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;process_order&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;taskParameter&lt;/span&gt;(param)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;retryLimit&lt;/span&gt;(3)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;retryInterval&lt;/span&gt;(2000)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;maxDurationMillis&lt;/span&gt;(60000)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        reliableExecutor.&lt;span style=&#34;color:#a6e22e&#34;&gt;submitTask&lt;/span&gt;(request);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;55-监听生命周期&#34;&gt;5.5 监听生命周期&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MyTaskListener&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;implements&lt;/span&gt; ReliableTaskListener {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onTaskCompleted&lt;/span&gt;(String taskId, String taskName) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 任务成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onTaskFailed&lt;/span&gt;(String taskId, String taskName, String status) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 任务最终失败&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;六使用注意点与避坑指南&#34;&gt;六、使用注意点与避坑指南&lt;/h2&gt;
&lt;h3 id=&#34;61-业务幂等仍然是最后一道防线&#34;&gt;6.1 业务幂等仍然是最后一道防线&lt;/h3&gt;
&lt;p&gt;框架通过两层锁、double-check、Lua 原子转移把重复执行的概率降到极低，但&lt;strong&gt;任何分布式系统都无法 100% 保证幂等&lt;/strong&gt;。业务 Handler 仍然应该保证幂等，尤其是涉及写数据库、调用外部接口的场景。&lt;/p&gt;
&lt;h3 id=&#34;62-taskid-需要业务保证唯一&#34;&gt;6.2 taskId 需要业务保证唯一&lt;/h3&gt;
&lt;p&gt;框架使用业务传入的 &lt;code&gt;taskId&lt;/code&gt; 作为任务唯一标识。如果同一业务单据重复提交相同 &lt;code&gt;taskId&lt;/code&gt;，第二次提交会生成新的 ZSET member（如果业务字段不同）或覆盖（如果完全相同）。&lt;strong&gt;建议用业务主键 + 操作类型组合生成 taskId&lt;/strong&gt;，例如 &lt;code&gt;order_12345_pay&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;63-重试间隔与最大时长的权衡&#34;&gt;6.3 重试间隔与最大时长的权衡&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;reliable-executor&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;retryInterval&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;2000&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# 失败后 2s 再试&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;maxDurationMillis&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60000&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 任务最长存活 60s&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;retryInterval&lt;/code&gt; 过小会导致失败任务快速反复执行，放大下游压力；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxDurationMillis&lt;/code&gt; 是从任务创建时间算起，不是单次执行时间；&lt;/li&gt;
&lt;li&gt;建议失败任务采用指数退避（当前版本为固定间隔，后续可扩展）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;64-mysql-分支需要自行引入-jpa&#34;&gt;6.4 MySQL 分支需要自行引入 JPA&lt;/h3&gt;
&lt;p&gt;框架中 &lt;code&gt;spring-boot-starter-data-jpa&lt;/code&gt; 是 &lt;code&gt;compileOnly&lt;/code&gt;，使用 MySQL 存储时需要在业务应用里显式引入：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-gradle&#34; data-lang=&#34;gradle&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;implementation &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;org.springframework.boot:spring-boot-starter-data-jpa&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runtimeOnly &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;com.mysql:mysql-connector-j&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;并执行建表脚本 &lt;code&gt;db/migration/V1__create_reliable_executor_tables.sql&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;65-redis-集群环境注意-key-前缀&#34;&gt;6.5 Redis 集群环境注意 Key 前缀&lt;/h3&gt;
&lt;p&gt;框架默认使用 &lt;code&gt;spring.application.name&lt;/code&gt; 作为 Redis Key 前缀。如果多个应用共用同一个 Redis 实例，务必设置不同的 &lt;code&gt;spring.application.name&lt;/code&gt;，否则任务会互相串扰。&lt;/p&gt;
&lt;h3 id=&#34;66-线程池队列满载时会跳过调度&#34;&gt;6.6 线程池队列满载时会跳过调度&lt;/h3&gt;
&lt;p&gt;当线程池队列达到 &lt;code&gt;queueCapacity&lt;/code&gt; 时，调度器会跳过本次周期，避免无限制堆积。如果业务出现持续高峰，建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调大 &lt;code&gt;maxPoolSize&lt;/code&gt; 和 &lt;code&gt;queueCapacity&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;监控 &lt;code&gt;reliable_executor_threadpool_queue_size&lt;/code&gt; 指标；&lt;/li&gt;
&lt;li&gt;必要时配合业务限流。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;67-不要忽略超时任务&#34;&gt;6.7 不要忽略超时任务&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;maxDurationMillis&lt;/code&gt; 是从创建时间算起的总时长。如果一个任务在队列里排队太久，可能还没执行就触发 TIMEOUT。对于允许较长延迟的任务，要把这个值设大；对于实时性任务，要把重试间隔和排队容量一起考虑。&lt;/p&gt;
&lt;h3 id=&#34;68-本地开发无-redis-怎么办&#34;&gt;6.8 本地开发无 Redis 怎么办？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用 MySQL 分支（H2 也可以跑测试）；&lt;/li&gt;
&lt;li&gt;或者本地启动一个 Redis Docker 容器；&lt;/li&gt;
&lt;li&gt;框架的 Redis 集成测试会在无 Redis 时自动跳过，不影响 CI。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七监控指标&#34;&gt;七、监控指标&lt;/h2&gt;
&lt;p&gt;框架内置 Micrometer 指标，可接入 Prometheus/Actuator：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;指标名&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_is_master&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;当前节点是否为 Master&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_tasks_scheduled_total&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;累计调度任务数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_tasks_executed_total&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;累计执行任务数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_tasks_completed_total&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;累计成功任务数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_tasks_failed_total&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;累计失败任务数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_tasks_timeout_total&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;累计超时任务数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_tasks_retried_total&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;累计重试次数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_threadpool_queue_size&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;线程池队列大小&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;reliable_executor_task_execution_seconds&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;任务执行耗时&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八开源与示例&#34;&gt;八、开源与示例&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;开源地址&lt;/strong&gt;：https://gitee.com/soulnew/reliableexecutor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例模块&lt;/strong&gt;：&lt;code&gt;example/&lt;/code&gt; 子项目包含可运行的 Spring Boot 应用和 REST 接口示例，可直接启动体验任务提交与执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九总结&#34;&gt;九、总结&lt;/h2&gt;
&lt;p&gt;KE Reliable Executor 是一个&lt;strong&gt;轻量、可靠、易接入&lt;/strong&gt;的异步任务执行框架。它用 Redis ZSET + 两层锁 + Lua 原子转移，在“不引入重型 MQ”的前提下，提供了任务提交、调度、重试、状态查询、监控等完整能力；同时通过 MySQL 分支提供了另一种存储选择。&lt;/p&gt;
&lt;p&gt;如果你的业务符合以下特征，不妨试试它：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已有 Redis/MySQL；&lt;/li&gt;
&lt;li&gt;任务量中等，重视可靠性；&lt;/li&gt;
&lt;li&gt;希望快速落地，不想为 MQ 付出额外运维成本；&lt;/li&gt;
&lt;li&gt;需要任务状态可观测、可人工介入。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;记住：框架层负责把重复消费的概率降到极低，业务层仍应保证 Handler 幂等，这才是工程上的稳妥做法。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    <item>
      <title>《领域驱动设计》精简版：一张图看懂 DDD，引用开源 ddd-demo</title>
      <link>https://ftmao.com/blog/posts/domain-driven-design-concise/</link>
      <pubDate>Thu, 27 Jul 2023 20:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/domain-driven-design-concise/</guid>
      <description>&lt;h1 id=&#34;领域驱动设计精简版一张图看懂-ddd引用开源-ddd-demo&#34;&gt;《领域驱动设计》精简版：一张图看懂 DDD，引用开源 ddd-demo&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;这份笔记基于 Eric Evans 的《领域驱动设计》以及 Vaughn Vernon 的《实现领域驱动设计》《领域驱动设计精粹》，结合日常工程实践做了补充。文中完整代码实现参考开源项目 &lt;a href=&#34;https://gitee.com/soulnew/ddd-demo&#34;&gt;soulnew/ddd-demo&lt;/a&gt;，可直接克隆运行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一为什么需要-ddd&#34;&gt;一、为什么需要 DDD？&lt;/h2&gt;
&lt;p&gt;传统分层架构（Controller → Service → DAO）的问题：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;表现&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;贫血模型&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Service&lt;/code&gt; 里全是 if/else，Entity 只有 getter/setter&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;语义泄漏&lt;/td&gt;
					&lt;td&gt;业务术语被翻译成 &lt;code&gt;status = 1&lt;/code&gt;，新人读不懂&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;边界模糊&lt;/td&gt;
					&lt;td&gt;一个 &lt;code&gt;OrderService&lt;/code&gt; 同时处理库存、支付、物流&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;复用困难&lt;/td&gt;
					&lt;td&gt;同样叫“用户”，在会员、权限、订单里含义不同&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;DDD 的核心主张：&lt;strong&gt;让软件结构对齐业务领域，用业务语言写代码，用边界保护领域模型&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二一张图ddd-战略-战术全景&#34;&gt;二、一张图：DDD 战略-战术全景&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((领域驱动设计 DDD))
    战略设计 Strategic Design
      领域 Domain
      子域 Subdomain
        核心域 Core
        支撑域 Supporting
        通用域 Generic
      限界上下文 Bounded Context
      上下文映射 Context Mapping
        合作关系 Partnership
        共享内核 Shared Kernel
        客户-供应商 Customer-Supplier
        遵奉者 Conformist
        防腐层 ACL
        开放主机服务 OHS
        发布语言 PL
        各行其道 Separate Ways
    战术设计 Tactical Design
      实体 Entity
      值对象 Value Object
      聚合 Aggregate
      领域服务 Domain Service
      仓储 Repository
      工厂 Factory
      领域事件 Domain Event
      应用服务 Application Service
    统一语言 Ubiquitous Language
    分层架构 Layered Architecture
      用户界面层 UI
      应用层 Application
      领域层 Domain
      基础设施层 Infrastructure&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;记忆口诀&lt;/strong&gt;：先战略（划边界），后战术（建模型），全程讲“统一语言”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三战略设计先画地图再打仗&#34;&gt;三、战略设计：先画地图，再打仗&lt;/h2&gt;
&lt;h3 id=&#34;31-领域--问题空间--解决方案空间&#34;&gt;3.1 领域 = 问题空间 + 解决方案空间&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[问题空间&amp;lt;br/&amp;gt;Problem Space] --&amp;gt; B[领域 Domain&amp;lt;br/&amp;gt;业务要解决的问题]
    B --&amp;gt; C[子域 Subdomain&amp;lt;br/&amp;gt;可独立讨论的业务板块]
    C --&amp;gt; D[核心域 Core Domain&amp;lt;br/&amp;gt;竞争优势]
    C --&amp;gt; E[支撑域 Supporting&amp;lt;br/&amp;gt;定制但非核心]
    C --&amp;gt; F[通用域 Generic&amp;lt;br/&amp;gt;可外购/复用]
    D --&amp;gt; G[解决方案空间&amp;lt;br/&amp;gt;Solution Space]
    E --&amp;gt; G
    F --&amp;gt; G
    G --&amp;gt; H[限界上下文 Bounded Context&amp;lt;br/&amp;gt;一个模型只在一个上下文内有效]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;示例：在线医疗平台&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;子域&lt;/th&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;问诊&lt;/td&gt;
					&lt;td&gt;核心域&lt;/td&gt;
					&lt;td&gt;核心竞争力，需精研模型&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;处方&lt;/td&gt;
					&lt;td&gt;支撑域&lt;/td&gt;
					&lt;td&gt;受监管约束，需定制但非差异化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;支付&lt;/td&gt;
					&lt;td&gt;通用域&lt;/td&gt;
					&lt;td&gt;可直接对接微信/支付宝&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消息通知&lt;/td&gt;
					&lt;td&gt;通用域&lt;/td&gt;
					&lt;td&gt;短信/推送可用第三方&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;32-限界上下文-bounded-context&#34;&gt;3.2 限界上下文 Bounded Context&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：限界上下文是领域模型能够保持一致性和完整性的显式边界。在边界内部，每个业务术语、概念、规则都有精确且唯一的含义；同一概念在不同限界上下文中可以拥有不同定义。它是 DDD 战略设计中保护模型不被语义污染的最小单元。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;同一个业务名词，在不同上下文里含义可能完全不同。DDD 用“限界上下文”给模型画边界。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 商品上下文 Product Context
        P1[商品 = SKU + 价格 + 库存]
    end
    subgraph 订单上下文 Order Context
        P2[商品 = 快照 + 购买时价格 + 数量]
    end
    subgraph 物流上下文 Logistics Context
        P3[商品 = 重量 + 体积 + 易碎标识]
    end
    subgraph 营销上下文 Marketing Context
        P4[商品 = 卖点 + 优惠券适用规则]
    end
    P1 -.-&amp;gt;|映射| P2
    P1 -.-&amp;gt;|映射| P3
    P1 -.-&amp;gt;|映射| P4&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;关键认知&lt;/strong&gt;：不要追求“一个全局统一的数据模型”，要在上下文内部统一，在上下文之间显式映射。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;33-上下文映射模式&#34;&gt;3.3 上下文映射模式&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：上下文映射模式是 DDD 中用于描述不同限界上下文之间集成关系与协作方式的战略模式集合。由于每个限界上下文都有自己的模型和统一语言，当业务流程跨越多个上下文时，需要通过上下文映射来显式定义它们如何依赖、转换与协作，从而保证边界清晰、演化可控。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[上下文 A] --&amp;gt;|合作关系&amp;lt;br/&amp;gt;Partnership| B[上下文 B]
    C[上下文 C] --&amp;gt;|共享内核&amp;lt;br/&amp;gt;Shared Kernel| D[上下文 D]
    E[上游 Upstream] --&amp;gt;|客户-供应商&amp;lt;br/&amp;gt;Customer-Supplier| F[下游 Downstream]
    G[强势上游] --&amp;gt;|遵奉者&amp;lt;br/&amp;gt;Conformist| H[下游]
    I[下游] --&amp;gt;|防腐层 ACL| J[外部系统]
    K[开放主机服务 OHS&amp;lt;br/&amp;gt;+ 发布语言 PL] --&amp;gt; L[下游]
    M[上下文 M] -.-&amp;gt;|各行其道&amp;lt;br/&amp;gt;Separate Ways| N[上下文 N]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;使用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;合作关系&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;两个团队互相依赖，共同演进&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;共享内核&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;核心模型由双方共同维护，改动需同步&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;客户-供应商&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;上游优先服务下游需求，下游有话语权&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;遵奉者&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;上游强势（如外部 SaaS），下游无条件接受&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;防腐层 ACL&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;隔离遗留系统或外部 API，保护领域模型&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;开放主机服务 + 发布语言&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;上游提供稳定 API/协议，供多个下游消费&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;各行其道&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;关联微弱，各自独立&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;34-从限界上下文到微服务边界&#34;&gt;3.4 从限界上下文到微服务边界&lt;/h2&gt;
&lt;p&gt;限界上下文是&lt;strong&gt;语义边界&lt;/strong&gt;——一个模型在这个边界内含义一致；微服务是&lt;strong&gt;部署边界&lt;/strong&gt;——独立代码库、独立数据库、独立发布单元。二者不是一回事，但 DDD 划分微服务边界时，最可靠的起点就是限界上下文。&lt;/p&gt;
&lt;h3 id=&#34;映射原则&#34;&gt;映射原则&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[业务领域] --&amp;gt; B[识别子域&amp;lt;br/&amp;gt;Core / Supporting / Generic]
    B --&amp;gt; C[定义限界上下文&amp;lt;br/&amp;gt;语义边界]
    C --&amp;gt; D{是否满足&amp;lt;br/&amp;gt;拆分条件？}
    D --&amp;gt;|是| E[一个微服务]
    D --&amp;gt;|否| F[多个上下文合并&amp;lt;br/&amp;gt;或模块化单体]
    E --&amp;gt; G[独立数据库&amp;lt;br/&amp;gt;独立部署&amp;lt;br/&amp;gt;API / 事件交互]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;默认规则&lt;/strong&gt;：一个限界上下文对应一个微服务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例外情况&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;情况&lt;/th&gt;
					&lt;th&gt;处理&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;上下文内部模块差异极大（性能、安全、团队）&lt;/td&gt;
					&lt;td&gt;可进一步拆分，但要保留相同的“统一语言”子空间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多个上下文非常小且高度协作&lt;/td&gt;
					&lt;td&gt;可合并为一个服务，内部用模块隔离&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;团队规模小、业务简单&lt;/td&gt;
					&lt;td&gt;先模块化单体，随演进再拆&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;强一致事务需求&lt;/td&gt;
					&lt;td&gt;优先放在同一服务 / 同一数据库内&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;拆分的启发条件&#34;&gt;拆分的启发条件&lt;/h3&gt;
&lt;p&gt;满足以下任意一条，都可以把限界上下文拆成独立微服务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;独立演进&lt;/strong&gt;：团队能独立发布，不阻塞他人。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立伸缩&lt;/strong&gt;：流量特征不同，例如订单读多写少，库存强一致写频繁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立故障域&lt;/strong&gt;：一个服务故障不应拖垮整个平台。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;独立技术栈&lt;/strong&gt;：某些上下文需要特殊存储（图数据库、时序数据库等）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合规隔离&lt;/strong&gt;：支付、医疗、金融数据需要独立部署和审计。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;案例b2c-电商的微服务边界划分&#34;&gt;案例：B2C 电商的微服务边界划分&lt;/h3&gt;
&lt;p&gt;以常见的 B2C 电商为例，演示“子域 → 限界上下文 → 微服务”的完整推导过程。&lt;/p&gt;
&lt;h4 id=&#34;第一步识别子域&#34;&gt;第一步：识别子域&lt;/h4&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;子域&lt;/th&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;订单&lt;/td&gt;
					&lt;td&gt;核心域&lt;/td&gt;
					&lt;td&gt;下单、状态机、优惠计算&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;库存&lt;/td&gt;
					&lt;td&gt;核心域&lt;/td&gt;
					&lt;td&gt;实时库存、预占、扣减&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;支付&lt;/td&gt;
					&lt;td&gt;通用域&lt;/td&gt;
					&lt;td&gt;对接微信 / 支付宝 / 信用卡&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;物流&lt;/td&gt;
					&lt;td&gt;支撑域&lt;/td&gt;
					&lt;td&gt;运单、轨迹、配送规则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;商品&lt;/td&gt;
					&lt;td&gt;核心域&lt;/td&gt;
					&lt;td&gt;类目、属性、价格&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;营销&lt;/td&gt;
					&lt;td&gt;支撑域&lt;/td&gt;
					&lt;td&gt;优惠券、满减、秒杀&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;会员&lt;/td&gt;
					&lt;td&gt;支撑域&lt;/td&gt;
					&lt;td&gt;积分、等级、权益&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;第二步定义限界上下文与统一语言&#34;&gt;第二步：定义限界上下文与统一语言&lt;/h4&gt;
&lt;p&gt;同一个业务名词在不同上下文里含义不同，这正是拆分的语义依据。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;上下文&lt;/th&gt;
					&lt;th&gt;“商品”在该上下文中的含义&lt;/th&gt;
					&lt;th&gt;“订单”在该上下文中的含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;商品上下文&lt;/td&gt;
					&lt;td&gt;SKU、SPU、类目、主图、售价&lt;/td&gt;
					&lt;td&gt;无&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;订单上下文&lt;/td&gt;
					&lt;td&gt;商品快照（下单时的价格和描述）&lt;/td&gt;
					&lt;td&gt;聚合根：状态、金额、行项&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;库存上下文&lt;/td&gt;
					&lt;td&gt;可售数量、预占数量、仓库&lt;/td&gt;
					&lt;td&gt;无&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;物流上下文&lt;/td&gt;
					&lt;td&gt;重量、体积、易碎标识&lt;/td&gt;
					&lt;td&gt;发货单、包裹、运单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;营销上下文&lt;/td&gt;
					&lt;td&gt;适用优惠券的商品范围&lt;/td&gt;
					&lt;td&gt;优惠分摊、实付金额&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;关键判断&lt;/strong&gt;：如果两个地方对“商品”的定义和生命周期完全不同，就应分属不同上下文，也倾向于拆成不同微服务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;第三步映射为微服务&#34;&gt;第三步：映射为微服务&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 微服务边界
        P[商品服务&amp;lt;br/&amp;gt;Product Service]
        O[订单服务&amp;lt;br/&amp;gt;Order Service]
        I[库存服务&amp;lt;br/&amp;gt;Inventory Service]
        PAY[支付服务&amp;lt;br/&amp;gt;Payment Service]
        L[物流服务&amp;lt;br/&amp;gt;Logistics Service]
        M[营销服务&amp;lt;br/&amp;gt;Marketing Service]
        U[会员服务&amp;lt;br/&amp;gt;Member Service]
    end

    O --&amp;gt;|预占库存| I
    O --&amp;gt;|发起支付| PAY
    I --&amp;gt;|OrderPaid 事件| L
    O --&amp;gt;|查询商品信息| P
    O --&amp;gt;|计算优惠| M
    U --&amp;gt;|积分抵扣| O&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 id=&#34;第四步选择上下文映射模式&#34;&gt;第四步：选择上下文映射模式&lt;/h4&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;关系&lt;/th&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;订单 → 库存&lt;/td&gt;
					&lt;td&gt;客户-供应商 + 最终一致&lt;/td&gt;
					&lt;td&gt;订单调用库存预占接口；库存发布 &lt;code&gt;InventoryReserved&lt;/code&gt; 事件&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;订单 → 支付&lt;/td&gt;
					&lt;td&gt;开放主机服务 OHS&lt;/td&gt;
					&lt;td&gt;支付提供标准支付网关 API，多个业务共用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;库存 → 物流&lt;/td&gt;
					&lt;td&gt;发布语言 PL + 领域事件&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;OrderPaid&lt;/code&gt; 事件触发物流创建发货单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;订单 → 商品&lt;/td&gt;
					&lt;td&gt;遵奉者 / ACL&lt;/td&gt;
					&lt;td&gt;商品模型由商品服务定义，订单只保存快照&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;营销 → 订单&lt;/td&gt;
					&lt;td&gt;共享内核 Shared Kernel&lt;/td&gt;
					&lt;td&gt;优惠分摊算法需双方共同维护&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;常见反模式&#34;&gt;常见反模式&lt;/h4&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;反模式&lt;/th&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;正确做法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;按 CRUD 实体拆分&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;UserService&lt;/code&gt;、&lt;code&gt;OrderService&lt;/code&gt;、&lt;code&gt;ProductService&lt;/code&gt; 只是数据表代理&lt;/td&gt;
					&lt;td&gt;按业务能力（上下文）拆分，服务内包含完整业务规则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;按前端页面拆分&lt;/td&gt;
					&lt;td&gt;一个页面一个服务&lt;/td&gt;
					&lt;td&gt;页面是组合层，服务应按领域聚合&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;过度拆分&lt;/td&gt;
					&lt;td&gt;一次下单调 20 个接口&lt;/td&gt;
					&lt;td&gt;先模块化单体，验证边界后再拆&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;共享数据库&lt;/td&gt;
					&lt;td&gt;多个服务直接操作同一张表&lt;/td&gt;
					&lt;td&gt;每个服务拥有自己的数据，通过 API / 事件交互&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话&lt;/strong&gt;：微服务边界不是拍脑袋定的，而是从“限界上下文”出发，结合团队、性能、合规、发布节奏再微调的结果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四战术设计在限界上下文里建模型&#34;&gt;四、战术设计：在限界上下文里建模型&lt;/h2&gt;
&lt;h3 id=&#34;41-核心概念关系图&#34;&gt;4.1 核心概念关系图&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class AggregateRoot {
        +Entity root
        +invariant()
    }
    class Entity {
        +identity
        +continuity
    }
    class ValueObject {
        +immutable
        +equality by value
    }
    class DomainEvent {
        +eventId
        +occurredOn
        +payload
    }
    class Repository {
        +save(aggregate)
        +byId(id)
    }
    class DomainService {
        +cross-aggregate logic
    }
    class Factory {
        +create()
    }
    AggregateRoot --&amp;gt; Entity : contains
    AggregateRoot --&amp;gt; ValueObject : contains
    AggregateRoot --&amp;gt; DomainEvent : publishes
    Repository --&amp;gt; AggregateRoot : persists
    Factory --&amp;gt; AggregateRoot : creates
    DomainService --&amp;gt; AggregateRoot : orchestrates&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-实体-entity-vs-值对象-value-object&#34;&gt;4.2 实体 Entity vs 值对象 Value Object&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph Entity
        E1[Order #1001] --&amp;gt; E2[状态变化&amp;lt;br/&amp;gt;待支付 → 已发货 → 已完成]
        E2 --&amp;gt; E3[同一 identity&amp;lt;br/&amp;gt;状态变了还是它]
    end
    subgraph Value Object
        V1[Money amount=100 currency=CNY] --&amp;gt; V2[不可变]
        V2 --&amp;gt; V3[替换即新生&amp;lt;br/&amp;gt;money = money * 2]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;判断标准&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果对象需要被&lt;strong&gt;追踪生命周期&lt;/strong&gt;，有独立身份 → &lt;strong&gt;实体&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;如果对象只描述属性，换了值就等于换了对象 → &lt;strong&gt;值对象&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五完整设计-demo电商订单上下文&#34;&gt;五、完整设计 Demo：电商订单上下文&lt;/h2&gt;
&lt;p&gt;完整的代码实现可参考开源项目：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Gitee: soulnew/ddd-demo&lt;/strong&gt;&lt;br&gt;
&lt;a href=&#34;https://gitee.com/soulnew/ddd-demo&#34;&gt;https://gitee.com/soulnew/ddd-demo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;该项目演示了一个典型的 DDD 工程结构，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;领域层&lt;/strong&gt;：实体、值对象、聚合根、领域服务、领域事件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用层&lt;/strong&gt;：应用服务、Command/Query、事务编排&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基础设施层&lt;/strong&gt;：仓储实现、外部 API 适配、消息发布&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口层&lt;/strong&gt;：Controller / API 入口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建议结合本节的战术概念，重点阅读：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;domain/&lt;/code&gt; 下的聚合根如何封装业务规则（如订单状态机）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;application/&lt;/code&gt; 下的应用服务如何编排用例，不写业务规则。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;infrastructure/&lt;/code&gt; 下的仓储实现如何把领域模型映射到持久化。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你想快速跑通一个最小可运行示例，也可以直接克隆该项目：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://gitee.com/soulnew/ddd-demo.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;下面只保留一个最小示意图，帮助理解“充血模型”与“贫血模型”的关键差异。&lt;/p&gt;
&lt;h3 id=&#34;51-充血模型-vs-贫血模型对比&#34;&gt;5.1 充血模型 vs 贫血模型对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;贫血模型&lt;/th&gt;
					&lt;th&gt;充血模型&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OrderService&lt;/code&gt; 里写 &lt;code&gt;if (status.equals(&amp;quot;PAID&amp;quot;))&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Order.pay()&lt;/code&gt; 自己封装状态机&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OrderEntity&lt;/code&gt; 只有 getter/setter&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Order&lt;/code&gt; 有业务方法，规则集中在领域对象&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;规则散落在 Service&lt;/td&gt;
					&lt;td&gt;不变量、状态变更由聚合根保证&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;持久化即领域模型&lt;/td&gt;
					&lt;td&gt;持久化是领域模型的投影&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心原则&lt;/strong&gt;：把“什么样合法”的判断放到领域对象里，应用服务只负责“什么时候调用”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六分层架构代码放哪里&#34;&gt;六、分层架构：代码放哪里？&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 用户界面层 UI/API
        U1[Controller / Handler]
    end
    subgraph 应用层 Application
        A1[Application Service]
        A2[DTO / Command / Query]
    end
    subgraph 领域层 Domain
        D1[Entity]
        D2[Value Object]
        D3[Domain Service]
        D4[Domain Event]
        D5[Repository Interface]
    end
    subgraph 基础设施层 Infrastructure
        I1[Repository Implementation]
        I2[Message Queue]
        I3[Database]
        I4[External API Client]
    end

    U1 --&amp;gt; A1
    A1 --&amp;gt; D1
    A1 --&amp;gt; D3
    D1 --&amp;gt; D2
    D1 -.-&amp;gt;|发布| D4
    A1 --&amp;gt; D5
    D5 -.-&amp;gt;|由| I1
    I1 --&amp;gt; I3
    A1 -.-&amp;gt;|发送| I2
    A1 -.-&amp;gt;|调用| I4&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;依赖方向&lt;/strong&gt;：UI → 应用 → 领域；基础设施通过接口被领域依赖（依赖倒置）。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层级&lt;/th&gt;
					&lt;th&gt;职责&lt;/th&gt;
					&lt;th&gt;不该做的事&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;UI&lt;/td&gt;
					&lt;td&gt;接收请求、返回响应&lt;/td&gt;
					&lt;td&gt;不直接操作 Repository&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;应用层&lt;/td&gt;
					&lt;td&gt;编排用例、事务、权限校验&lt;/td&gt;
					&lt;td&gt;不写业务规则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;领域层&lt;/td&gt;
					&lt;td&gt;业务规则、不变量、状态变更&lt;/td&gt;
					&lt;td&gt;不依赖框架/数据库&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;基础设施&lt;/td&gt;
					&lt;td&gt;持久化、消息、外部调用&lt;/td&gt;
					&lt;td&gt;不侵入领域规则&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七聚合设计四条黄金规则&#34;&gt;七、聚合设计：四条黄金规则&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 聚合根 Order
        O1[Order] --&amp;gt; L1[OrderLineItem]
        O1 --&amp;gt; L2[OrderLineItem]
        O1 --&amp;gt; M1[Money total]
        O1 --&amp;gt; A1[Address shipping]
        O1 --&amp;gt; E1[DomainEvent list]
    end
    subgraph 外部只能通过聚合根访问
        X[其他上下文] --&amp;gt;|byId/order.pay| O1
        X -.-&amp;gt;|❌ 不能直接修改| L1
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一个聚合只有一个聚合根&lt;/strong&gt;，外部对象只能引用聚合根 ID。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;聚合内部强一致&lt;/strong&gt;，跨聚合最终一致（用领域事件）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;小聚合优先&lt;/strong&gt;，大聚合会成性能瓶颈和并发热点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除聚合根级联删除其内部对象&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;反例&lt;/strong&gt;：如果把 &lt;code&gt;User&lt;/code&gt; 和 &lt;code&gt;Order&lt;/code&gt; 放在同一个聚合里，用户每次下单都会锁定整个用户聚合，极端不合理。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八领域事件跨聚合上下文的胶水&#34;&gt;八、领域事件：跨聚合/上下文的胶水&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant O as Order 聚合
    participant E as Event Bus
    participant I as Inventory 上下文
    participant L as Logistics 上下文
    O-&amp;gt;&amp;gt;O: order.pay()
    O-&amp;gt;&amp;gt;E: publish OrderPaid
    E-&amp;gt;&amp;gt;I: consume OrderPaid
    I-&amp;gt;&amp;gt;I: 扣减库存
    E-&amp;gt;&amp;gt;L: consume OrderPaid
    L-&amp;gt;&amp;gt;L: 创建发货单&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;领域事件的价值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解耦&lt;/strong&gt;：Order 不需要知道 Inventory 和 Logistics 的存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最终一致&lt;/strong&gt;：接受短时间不一致，换取可扩展性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审计&lt;/strong&gt;：事件日志本身就是业务轨迹。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九常见反模式与纠正&#34;&gt;九、常见反模式与纠正&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;反模式&lt;/th&gt;
					&lt;th&gt;现象&lt;/th&gt;
					&lt;th&gt;纠正&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;贫血领域模型&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Entity 只有字段，Service 全是逻辑&lt;/td&gt;
					&lt;td&gt;把不变规则和状态变更放回实体&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;万能聚合&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;一个聚合包含十几个实体&lt;/td&gt;
					&lt;td&gt;按业务不变量拆分，跨聚合用事件&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;数据库驱动建模&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;先建表，再写代码&lt;/td&gt;
					&lt;td&gt;先建模，再映射持久化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;跨上下文直接引用&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Order 直接持有 Inventory 对象&lt;/td&gt;
					&lt;td&gt;只存 ID，通过 ACL/OHS 交互&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;应用层写业务规则&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;if order.status == ...&lt;/code&gt; 散落在 Service&lt;/td&gt;
					&lt;td&gt;下放到领域对象&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;值对象可变&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;money.setAmount(100)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用 final 类 + 不可变字段，替换而非修改&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十什么时候用-ddd什么时候不用&#34;&gt;十、什么时候用 DDD，什么时候不用？&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[项目是否复杂？] --&amp;gt;|否| B[简单 CRUD 即可]
    A --&amp;gt;|是| C[业务是否快速变化？]
    C --&amp;gt;|否| D[事务脚本 / 简单分层]
    C --&amp;gt;|是| E[团队是否愿意投入统一语言？]
    E --&amp;gt;|否| F[DDD 收益低]
    E --&amp;gt;|是| G[DDD 值得尝试]
    G --&amp;gt; H[从核心域开始&amp;lt;br/&amp;gt;小步迭代]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;适合 DDD&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务复杂、领域专家与开发需紧密协作&lt;/li&gt;
&lt;li&gt;核心域需要长期演进&lt;/li&gt;
&lt;li&gt;微服务边界模糊，需要战略设计&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不适合 DDD&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简单数据录入/展示系统&lt;/li&gt;
&lt;li&gt;生命周期短、需求稳定的项目&lt;/li&gt;
&lt;li&gt;团队没有领域专家时间投入&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一实战案例新项目从-0-到-1-落地-ddd&#34;&gt;十一、实战案例：新项目从 0 到 1 落地 DDD&lt;/h2&gt;
&lt;p&gt;前面讲的都是概念和 Demo，下面以 &lt;strong&gt;在线健身私教平台 FitCoach&lt;/strong&gt; 为例，把“如何划分领域、子域、限界上下文、微服务，以及如何决策充血/贫血模型、如何裁剪 DDD”串成一条完整思路。&lt;/p&gt;
&lt;h3 id=&#34;111-项目背景与目标&#34;&gt;11.1 项目背景与目标&lt;/h3&gt;
&lt;p&gt;FitCoach 要做一个连接私教与学员的平台，核心能力包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学员购买课程包、预约上课时间、查看训练记录&lt;/li&gt;
&lt;li&gt;教练管理学员、制定训练计划、记录每次上课内容&lt;/li&gt;
&lt;li&gt;运营人员配置课程 SKU、促销活动、处理退款申诉&lt;/li&gt;
&lt;li&gt;对接第三方支付、短信推送、以及第三方直播上课工具&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;业务不算极端复杂，但涉及“课程、预约、课时、计划、会员、支付、营销”多个概念，且预计会快速迭代。&lt;strong&gt;这正是 DDD 能帮上忙的地方：先把业务地图画清楚，再决定怎么写代码。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;112-第一步识别领域与子域&#34;&gt;11.2 第一步：识别领域与子域&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;领域（Domain）&lt;/strong&gt; 就是整个健身私教业务要解决的问题空间。通过事件风暴或用户旅程，可以识别出以下子域：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;子域&lt;/th&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;判断理由&lt;/th&gt;
					&lt;th&gt;投入策略&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;私教课程与预约&lt;/td&gt;
					&lt;td&gt;核心域&lt;/td&gt;
					&lt;td&gt;直接决定用户体验和变现效率&lt;/td&gt;
					&lt;td&gt;投入最好的领域专家和工程师&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;训练计划与记录&lt;/td&gt;
					&lt;td&gt;核心域&lt;/td&gt;
					&lt;td&gt;教练专业能力的载体，差异化关键&lt;/td&gt;
					&lt;td&gt;与课程域紧密协作演进&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;会员与教练档案&lt;/td&gt;
					&lt;td&gt;支撑域&lt;/td&gt;
					&lt;td&gt;需要定制，但不是竞争壁垒&lt;/td&gt;
					&lt;td&gt;内部实现，够用即可&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;支付与财务&lt;/td&gt;
					&lt;td&gt;通用域&lt;/td&gt;
					&lt;td&gt;行业通用能力&lt;/td&gt;
					&lt;td&gt;对接微信/支付宝，封装标准接口&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消息通知&lt;/td&gt;
					&lt;td&gt;通用域&lt;/td&gt;
					&lt;td&gt;短信/推送/站内信&lt;/td&gt;
					&lt;td&gt;使用第三方 SaaS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;内容（文章/视频）&lt;/td&gt;
					&lt;td&gt;支撑域&lt;/td&gt;
					&lt;td&gt;运营辅助，提升留存&lt;/td&gt;
					&lt;td&gt;可用开源 CMS 改造&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;划分子域的权衡要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不要从技术模块出发&lt;/strong&gt;。例如不要先想“需要一个 UserService”，而要想“会员管理”这个业务能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心域通常只有 1-2 个&lt;/strong&gt;。如果把所有子域都标成核心，等于没有重点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通用域不要自己造轮子&lt;/strong&gt;。支付、通知直接买，团队精力留给核心域。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子域边界可以随业务演化&lt;/strong&gt;。MVP 阶段可以把“训练计划”放在“课程域”里，等业务复杂后再拆出来。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;113-第二步划分限界上下文&#34;&gt;11.3 第二步：划分限界上下文&lt;/h3&gt;
&lt;p&gt;划分的核心问题是：&lt;strong&gt;同一个业务名词，在不同地方含义是否不同？生命周期是否独立？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以“课程”这个词为例：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;上下文&lt;/th&gt;
					&lt;th&gt;“课程”在该上下文中的含义&lt;/th&gt;
					&lt;th&gt;关键概念&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;课程目录上下文&lt;/td&gt;
					&lt;td&gt;可售卖的课程 SKU：价格、有效期、适用人群、退费规则&lt;/td&gt;
					&lt;td&gt;CourseCatalog, PricePolicy&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;预约调度上下文&lt;/td&gt;
					&lt;td&gt;一次具体的上课时间：教练、学员、时段、状态&lt;/td&gt;
					&lt;td&gt;Appointment, TimeSlot&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;训练记录上下文&lt;/td&gt;
					&lt;td&gt;一次实际上课产生的记录：消耗课时、训练内容、教练反馈&lt;/td&gt;
					&lt;td&gt;TrainingRecord, ConsumedSession&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;营销上下文&lt;/td&gt;
					&lt;td&gt;参与促销的课程范围：满减、套餐、优惠券适用&lt;/td&gt;
					&lt;td&gt;PromotionRule, Bundle&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;财务上下文&lt;/td&gt;
					&lt;td&gt;课程订单的应收、实收、退款流水&lt;/td&gt;
					&lt;td&gt;CourseOrder, Refund&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因为“课程”的含义和生命周期在每个上下文里都不同，所以应该拆成不同的限界上下文。FitCoach 最终划分如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;课程目录上下文&lt;/strong&gt;（Course Catalog Context）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预约调度上下文&lt;/strong&gt;（Scheduling Context）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;训练记录上下文&lt;/strong&gt;（Training Record Context）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;会员上下文&lt;/strong&gt;（Member Context）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;教练上下文&lt;/strong&gt;（Coach Context）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支付上下文&lt;/strong&gt;（Payment Context）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;营销上下文&lt;/strong&gt;（Marketing Context）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;消息通知上下文&lt;/strong&gt;（Notification Context）&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;关键判断&lt;/strong&gt;：如果两个地方对同一个名词的定义和生命周期不同，就倾向于拆成不同上下文；如果概念高度一致且总是一起变化，可以合并。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;114-第三步推导微服务边界&#34;&gt;11.4 第三步：推导微服务边界&lt;/h3&gt;
&lt;p&gt;默认规则是&lt;strong&gt;一个限界上下文对应一个微服务&lt;/strong&gt;，但 FitCoach 还要考虑团队规模、数据一致性、发布频率、性能特征。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;限界上下文&lt;/th&gt;
					&lt;th&gt;微服务决策&lt;/th&gt;
					&lt;th&gt;决策理由&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;课程目录&lt;/td&gt;
					&lt;td&gt;独立服务&lt;/td&gt;
					&lt;td&gt;变化频繁，运营经常调整 SKU 和价格&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;预约调度&lt;/td&gt;
					&lt;td&gt;独立服务&lt;/td&gt;
					&lt;td&gt;高并发读，学员大量刷可选时段，需单独伸缩&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;训练记录&lt;/td&gt;
					&lt;td&gt;独立服务&lt;/td&gt;
					&lt;td&gt;写多读少，数据量大，未来可能要独立归档&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;会员 + 教练档案&lt;/td&gt;
					&lt;td&gt;合并为“账户服务”&lt;/td&gt;
					&lt;td&gt;MVP 阶段都很薄，且登录后经常一起查询；内部用模块隔离&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;支付&lt;/td&gt;
					&lt;td&gt;独立服务&lt;/td&gt;
					&lt;td&gt;涉及资金安全、合规审计、对接第三方&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;营销&lt;/td&gt;
					&lt;td&gt;独立服务&lt;/td&gt;
					&lt;td&gt;活动频繁，不能影响核心预约链路&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消息通知&lt;/td&gt;
					&lt;td&gt;独立服务&lt;/td&gt;
					&lt;td&gt;可被多个业务复用，适合独立演进&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;合并的例外处理&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会员和教练虽然业务概念不同，但 MVP 阶段数据模型简单，合并可以减少一次远程调用。&lt;/li&gt;
&lt;li&gt;如果后续教练业务复杂化（排期、评级、分成、资质认证），再把教练上下文拆成独立服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话&lt;/strong&gt;：微服务边界从限界上下文出发，再结合团队、性能、合规、数据一致性做微调。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;115-第四步选择上下文映射模式&#34;&gt;11.5 第四步：选择上下文映射模式&lt;/h3&gt;
&lt;p&gt;不同上下文之间不是孤立的，需要显式定义它们如何协作。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[预约上下文] --&amp;gt;|Customer-Supplier| B[课程目录上下文]
    C[训练记录上下文] --&amp;gt;|Customer-Supplier| A
    D[支付上下文] --&amp;gt;|OHS + PL| E[预约上下文]
    F[营销上下文] --&amp;gt;|Shared Kernel| A
    G[消息通知上下文] --&amp;gt;|Conformist| H[短信/推送第三方]
    I[账户服务] --&amp;gt;|OHS + PL| J[所有业务服务]
    K[训练记录上下文] --&amp;gt;|ACL| L[直播上课工具]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;关系&lt;/th&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;预约 → 课程目录&lt;/td&gt;
					&lt;td&gt;客户-供应商&lt;/td&gt;
					&lt;td&gt;预约需要课程的名称、价格、时长；课程目录提供稳定查询接口&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;训练记录 → 预约&lt;/td&gt;
					&lt;td&gt;客户-供应商&lt;/td&gt;
					&lt;td&gt;训练记录基于已完成的预约生成&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;支付 → 各业务&lt;/td&gt;
					&lt;td&gt;开放主机服务 OHS&lt;/td&gt;
					&lt;td&gt;支付提供统一的收款、退款、对账 API&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;营销 → 预约&lt;/td&gt;
					&lt;td&gt;共享内核 Shared Kernel&lt;/td&gt;
					&lt;td&gt;促销分摊、优惠券核销规则需要双方共同维护&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消息通知 → 第三方&lt;/td&gt;
					&lt;td&gt;遵奉者 Conformist&lt;/td&gt;
					&lt;td&gt;直接调用第三方 SDK，模型由第三方定义&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;训练记录 → 直播工具&lt;/td&gt;
					&lt;td&gt;防腐层 ACL&lt;/td&gt;
					&lt;td&gt;直播工具接口不稳定，通过 ACL 隔离，保护领域模型&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;116-第五步充血模型-vs-贫血模型决策&#34;&gt;11.6 第五步：充血模型 vs 贫血模型决策&lt;/h3&gt;
&lt;p&gt;DDD 并不要求全场充血。&lt;strong&gt;模型选择应该看该上下文的业务复杂度&lt;/strong&gt;，而不是追求统一风格。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;上下文&lt;/th&gt;
					&lt;th&gt;推荐模型&lt;/th&gt;
					&lt;th&gt;理由&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;预约调度&lt;/td&gt;
					&lt;td&gt;充血模型&lt;/td&gt;
					&lt;td&gt;状态机复杂：待预约 → 已预约 → 已签到 → 已完成 → 已取消；有取消规则、爽约规则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;训练记录&lt;/td&gt;
					&lt;td&gt;充血模型&lt;/td&gt;
					&lt;td&gt;记录不可变，但课时消耗计算、教练评分聚合有业务规则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;课程目录&lt;/td&gt;
					&lt;td&gt;贫血模型可接受&lt;/td&gt;
					&lt;td&gt;主要是 CRUD，业务规则少&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;账户服务&lt;/td&gt;
					&lt;td&gt;贫血模型&lt;/td&gt;
					&lt;td&gt;数据管理为主，验证规则简单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消息通知&lt;/td&gt;
					&lt;td&gt;贫血/过程式&lt;/td&gt;
					&lt;td&gt;基本是发送逻辑，无复杂领域规则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;营销&lt;/td&gt;
					&lt;td&gt;充血模型&lt;/td&gt;
					&lt;td&gt;优惠计算、互斥规则、分摊逻辑复杂&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;预约上下文的充血模型示例&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Appointment&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; AppointmentId id;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; TimeSlot timeSlot;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; MemberId memberId;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; CoachId coachId;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; AppointmentStatus status;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;DomainEvent&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; domainEvents &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ArrayList&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cancel&lt;/span&gt;(LocalDateTime now) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (status &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; AppointmentStatus.&lt;span style=&#34;color:#a6e22e&#34;&gt;BOOKED&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DomainException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;只能取消已预约的课程&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (timeSlot.&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;minusHours&lt;/span&gt;(24).&lt;span style=&#34;color:#a6e22e&#34;&gt;isBefore&lt;/span&gt;(now)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DomainException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;开课 24 小时内不可取消&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AppointmentStatus.&lt;span style=&#34;color:#a6e22e&#34;&gt;CANCELLED&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        domainEvents.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; AppointmentCancelled(id, now));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;checkIn&lt;/span&gt;(LocalDateTime now) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (status &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; AppointmentStatus.&lt;span style=&#34;color:#a6e22e&#34;&gt;BOOKED&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DomainException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;未预约不能签到&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AppointmentStatus.&lt;span style=&#34;color:#a6e22e&#34;&gt;COMPLETED&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        domainEvents.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; AppointmentCheckedIn(id, memberId, coachId, now));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;决策口诀&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;状态机复杂、不变量多、业务规则变化快 → &lt;strong&gt;充血模型&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;主要是增删改查、数据为主、规则简单 → &lt;strong&gt;贫血模型即可&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;不确定时，先写贫血，等规则复杂了再重构为充血&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;117-第六步ddd-是否可以裁剪&#34;&gt;11.7 第六步：DDD 是否可以裁剪？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;可以，而且应该根据项目情况裁剪。&lt;/strong&gt; DDD 不是全有或全无的框架。&lt;/p&gt;
&lt;h4 id=&#34;场景-1只用战略设计战术设计从简&#34;&gt;场景 1：只用战略设计，战术设计从简&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;适合情况&lt;/strong&gt;：业务复杂但技术栈简单、团队经验不足、MVP 阶段要快速上线。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FitCoach 初期做法&lt;/strong&gt;：先画好限界上下文和微服务边界，服务内部用简单 CRUD + Service 实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;价值&lt;/strong&gt;：至少解决了“边界混乱、术语不一致、重复造轮子”的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;场景-2只用战术设计在已有边界内建模&#34;&gt;场景 2：只用战术设计，在已有边界内建模&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;适合情况&lt;/strong&gt;：系统边界已经清楚，但某个模块内部逻辑混乱（如遗留系统重构）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FitCoach 示例&lt;/strong&gt;：课程目录已经稳定，但预约状态机满是 if/else，可以只对“预约上下文”做充血模型重构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;风险&lt;/strong&gt;：如果战略边界本身不清晰，战术设计会变成“在错误的地方做正确的事”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;场景-3战略--战术都用但不用高级模式&#34;&gt;场景 3：战略 + 战术都用，但不用高级模式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;这是最常见的落地方式&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;FitCoach 可以用：限界上下文 + 充血模型 + 领域事件 + 分层架构。&lt;/li&gt;
&lt;li&gt;可以先不用：事件溯源、CQRS、六边形架构的严格形式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;裁剪原则&#34;&gt;裁剪原则&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;复杂的地方用 DDD，简单的地方不要硬套&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先战略后战术&lt;/strong&gt;，边界不清不要深入建模型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个项目可以不同上下文用不同成熟度&lt;/strong&gt;：核心域充血 + 贫血，通用域直接用 SDK。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要为了 DDD 而 DDD&lt;/strong&gt;，目标是降低业务复杂度，不是写出“DDD 标准代码”。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;118-落地时间线建议&#34;&gt;11.8 落地时间线建议&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;阶段&lt;/th&gt;
					&lt;th&gt;目标&lt;/th&gt;
					&lt;th&gt;关键产出&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Week 1-2&lt;/td&gt;
					&lt;td&gt;事件风暴 + 统一语言&lt;/td&gt;
					&lt;td&gt;子域图、限界上下文图、术语表&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Week 3&lt;/td&gt;
					&lt;td&gt;微服务边界与映射模式&lt;/td&gt;
					&lt;td&gt;服务拆分方案、接口契约初稿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Week 4-6&lt;/td&gt;
					&lt;td&gt;核心域战术设计&lt;/td&gt;
					&lt;td&gt;聚合、实体、值对象、领域事件、测试&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Week 7-8&lt;/td&gt;
					&lt;td&gt;基础设施 + 集成测试&lt;/td&gt;
					&lt;td&gt;数据库、消息队列、CI/CD 流水线&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;持续&lt;/td&gt;
					&lt;td&gt;演进&lt;/td&gt;
					&lt;td&gt;根据业务变化调整边界和模型&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新手常犯的错误&lt;/strong&gt;：一上来就在所有服务里写聚合根、值对象、领域事件。正确做法是先画地图，再对核心域精雕细琢，其他域能跑通即可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十二一句话总结&#34;&gt;十二、一句话总结&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DDD = 用“统一语言”沟通业务，用“限界上下文”划分边界并推导微服务边界，用“聚合/实体/值对象”封装领域知识，用“领域事件”连接上下文，让软件结构真正反映业务结构。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;把 &lt;a href=&#34;https://gitee.com/soulnew/ddd-demo&#34;&gt;soulnew/ddd-demo&lt;/a&gt; 的 Demo 跑一遍，再画一张你自己业务的战略地图，并尝试标出可能的微服务边界，你就已经比 80% 自称做 DDD 的人更接近它的本质了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Eric Evans. &lt;em&gt;Domain-Driven Design: Tackling Complexity in the Heart of Software&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Vaughn Vernon. &lt;em&gt;Implementing Domain-Driven Design&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Vaughn Vernon. &lt;em&gt;Domain-Driven Design Distilled&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>MongoDB 从入门到精通：存储引擎、建模、索引、副本集、分片与事务实战</title>
      <link>https://ftmao.com/blog/posts/mongodb-beginner-to-mastery/</link>
      <pubDate>Tue, 26 Oct 2021 10:30:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/mongodb-beginner-to-mastery/</guid>
      <description>&lt;h1 id=&#34;mongodb-从入门到精通存储引擎建模索引副本集分片与事务实战&#34;&gt;MongoDB 从入门到精通：存储引擎、建模、索引、副本集、分片与事务实战&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：用过 MySQL 之类的关系型数据库，现在要接 MongoDB 的同学——可能是接手存量系统，可能是新业务选型，也可能是想给自己的技术栈补上 NoSQL 这一块。&lt;br&gt;
本文不浪费篇幅在「什么是文档」这种名词解释上，直接从真正影响系统生死的东西讲起：存储引擎、一致性、高可用、性能。&lt;br&gt;
写这篇的契机：2021 年我所在团队的电商中台把部分业务从 MySQL 迁到 MongoDB，整个过程脱了一层皮。这篇是我希望迁移前有人能拍在桌上的笔记。&lt;br&gt;
文内版本基线：MongoDB 5.0（2021 年 7 月发布）、官方 Java 同步驱动 4.3、JDK 11。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;全文八篇，按真实的成长路径编排：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;一 选型认知&amp;lt;br/&amp;gt;Mongo 适合什么&amp;#34;] --&amp;gt; B[&amp;#34;二 存储引擎&amp;lt;br/&amp;gt;WiredTiger 底牌&amp;#34;]
    B --&amp;gt; C[&amp;#34;三 数据建模&amp;lt;br/&amp;gt;核心战场&amp;#34;]
    C --&amp;gt; D[&amp;#34;四 索引与 explain&amp;lt;br/&amp;gt;调优基本功&amp;#34;]
    D --&amp;gt; E[&amp;#34;五 聚合管道&amp;lt;br/&amp;gt;服务端计算&amp;#34;]
    E --&amp;gt; F[&amp;#34;六 副本集&amp;lt;br/&amp;gt;高可用与读写语义&amp;#34;]
    F --&amp;gt; G[&amp;#34;七 事务与分片&amp;lt;br/&amp;gt;规模化与一致性&amp;#34;]
    G --&amp;gt; H[&amp;#34;八 运维与 AI 时代&amp;lt;br/&amp;gt;监控备份 少写代码&amp;#34;]
    style H fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;学习 MongoDB 要始终围绕三个问题：&lt;strong&gt;数据安不安全、查询快不快、流量扛不扛得住&lt;/strong&gt;。这篇的每一节，都是对这三问的某一次正面回答。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一篇-选型认知什么时候该用-mongodb什么时候别逞强&#34;&gt;第一篇 选型认知：什么时候该用 MongoDB，什么时候别逞强&lt;/h2&gt;
&lt;p&gt;第一课不是怎么用，是&lt;strong&gt;什么时候用&lt;/strong&gt;。用错场景的 MongoDB，比不用更可怕。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 适合[&amp;#34;MongoDB 的主场&amp;#34;]
        P[&amp;#34;商品中心&amp;lt;br/&amp;gt;类目属性千差万别&amp;lt;br/&amp;gt;文档模型天然契合&amp;#34;]
        C[&amp;#34;购物车/会话&amp;lt;br/&amp;gt;读多写多 结构灵活&amp;#34;]
        L[&amp;#34;行为日志/埋点&amp;lt;br/&amp;gt;写入量大 schema 常变&amp;lt;br/&amp;gt;TTL 索引自动过期&amp;#34;]
        CM[&amp;#34;CMS/配置中心&amp;lt;br/&amp;gt;页面配置 JSON 化&amp;#34;]
    end
    subgraph 谨慎[&amp;#34;需要精心设计的边界&amp;#34;]
        O[&amp;#34;订单/库存&amp;lt;br/&amp;gt;可用 但要事务+对账设计&amp;#34;]
        M[&amp;#34;消息/Feed 流&amp;lt;br/&amp;gt;可用 注意分片键设计&amp;#34;]
    end
    subgraph 别上[&amp;#34;别硬上 Mongo&amp;#34;]
        F[&amp;#34;账务/清结算&amp;lt;br/&amp;gt;强事务 强审计&amp;lt;br/&amp;gt;交给关系型&amp;#34;]
        S[&amp;#34;复杂搜索&amp;lt;br/&amp;gt;交给 Elasticsearch&amp;#34;]
        J[&amp;#34;重度多表关联分析&amp;lt;br/&amp;gt;交给 OLAP/数仓&amp;#34;]
    end
    style 适合 fill:#e8f4e8
    style 别上 fill:#fde8e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;一句话原则：&lt;strong&gt;MongoDB 适合「单实体自成一体、结构多变、读多」的在线业务；不适合「钱」「搜索」「重度关联分析」。&lt;/strong&gt; 判断依据不是性能 benchmark，而是数据访问模式——先把系统里 80% 的查询列出来，看它们能不能被「单集合 + 少量 $lookup」覆盖。覆盖不了，选型本身就有问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之一&lt;/strong&gt;：数据库选型和框架选型完全不同。框架选错了可以重构，数据库选错了是「数据搬家」级别的灾难——停机迁移、双写对账、灰度切流，每一步都在钢丝上走。所以对「不做什么」的清单，要比「做什么」的清单更长。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第二篇-wiredtiger理解存储引擎才配谈性能&#34;&gt;第二篇 WiredTiger：理解存储引擎，才配谈性能&lt;/h2&gt;
&lt;p&gt;很多人用 MongoDB 三年，不知道底下是 WiredTiger。这就像开了一辈子车不懂发动机——平时没事，出事时只能干瞪眼。这一篇先把「数据到底怎么存的」讲透，再回答那个面试必问的问题：&lt;strong&gt;MongoDB 为什么快，为什么扛得住大量读写。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;21-底层数据模型bson磁盘上到底存了什么&#34;&gt;2.1 底层数据模型：BSON，磁盘上到底存了什么&lt;/h3&gt;
&lt;p&gt;你在应用层操作的是「文档」（可以理解成 JSON），但磁盘上存的是 &lt;strong&gt;BSON（Binary JSON）&lt;/strong&gt;——一种二进制、自描述、长度前缀的编码格式：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph BSON[&amp;#34;一个 BSON 文档的字节布局&amp;#34;]
        L[&amp;#34;int32 总长度&amp;lt;br/&amp;gt;开头 4 字节&amp;#34;] --&amp;gt; E1[&amp;#34;元素1&amp;lt;br/&amp;gt;类型字节 + 字段名 + 值&amp;#34;]
        E1 --&amp;gt; E2[&amp;#34;元素2&amp;lt;br/&amp;gt;类型字节 + 字段名 + 值&amp;#34;]
        E2 --&amp;gt; EN[&amp;#34;...&amp;#34;]
        EN --&amp;gt; Z[&amp;#34;0x00 结束符&amp;#34;]
    end
    subgraph TYPES[&amp;#34;BSON 比 JSON 多出的类型&amp;#34;]
        T1[&amp;#34;ObjectId / Date / 正则&amp;#34;]
        T2[&amp;#34;int32 / int64 / double&amp;#34;]
        T3[&amp;#34;Decimal128 高精度十进制&amp;#34;]
        T4[&amp;#34;BinData 二进制 / 数组 / 内嵌文档&amp;#34;]
    end
    BSON -.-&amp;gt; TYPES&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;BSON 的设计直接回答了两个性能问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;长度前缀 + 类型字节 → 遍历时可以「跳过」字段。&lt;/strong&gt; 查询只取 &lt;code&gt;name&lt;/code&gt; 字段时，引擎不需要解析整个文档，读到字段长度直接指针后移——这就是为什么投影（projection）能省时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型丰富 → 不需要序列化转换。&lt;/strong&gt; JSON 只有 string/number/boolean，日期只能存字符串再解析；BSON 原生支持 Date、ObjectId、Decimal128（钱的精确计算就靠它）、二进制，省去了应用层和存储层之间大量的类型转换开销。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;更关键的是&lt;strong&gt;文档在磁盘上是连续存放的&lt;/strong&gt;：一个商品文档（含内嵌的 SKU 数组、图片列表）在磁盘上是&lt;strong&gt;一整块连续的字节&lt;/strong&gt;。这和关系型数据库形成鲜明对比——&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph MYSQL[&amp;#34;MySQL 读一个完整商品&amp;#34;]
        A1[&amp;#34;product 表的 B+ 树页&amp;#34;] --&amp;gt; J[&amp;#34;JOIN&amp;#34;]
        A2[&amp;#34;sku 表的 B+ 树页&amp;lt;br/&amp;gt;磁盘另一处&amp;#34;] --&amp;gt; J
        A3[&amp;#34;image 表的 B+ 树页&amp;lt;br/&amp;gt;磁盘又一处&amp;#34;] --&amp;gt; J
        J --&amp;gt; R[&amp;#34;多次随机 I/O&amp;lt;br/&amp;gt;才能拼出一个商品&amp;#34;]
    end
    subgraph MONGO[&amp;#34;MongoDB 读一个完整商品&amp;#34;]
        B[&amp;#34;一次磁盘寻道&amp;lt;br/&amp;gt;读出连续的一整块 BSON&amp;#34;]
    end
    style MONGO fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这就是 MongoDB 设计哲学里最重要的词：&lt;strong&gt;数据局部性（data locality）&lt;/strong&gt;。「一起读的数据，就存在一起」。嵌入建模之所以是 Mongo 的灵魂，底层原因就在这里——它不是风格偏好，是磁盘 I/O 决定的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;追问：文档是定长连续存放的，那修改、变大之后怎么办？磁盘会不连续吗？&lt;/strong&gt; 这要分两个时代说。MMAPv1 时代（3.0 之前）文档存在定长槽位里，变大就得整体搬家（document move），旧槽位变空洞，磁盘碎片是当年最臭名昭著的问题。WiredTiger 时代换了个思路：&lt;strong&gt;存储单位是 B 树的页而不是文档&lt;/strong&gt;，文档是叶页里的一条 record；修改&lt;strong&gt;从不在原地写&lt;/strong&gt;——checkpoint 时用 copy-on-write 把改后的整页写到磁盘&lt;strong&gt;新位置&lt;/strong&gt;，旧块进空闲列表复用；文档长大导致页放不下就触发&lt;strong&gt;页分裂&lt;/strong&gt;，超大文档走 overflow 页。所以「连续」的准确含义是「逻辑上一条 record、一次 I/O 读取」，物理位置本来就会搬。代价有两个：长期高频更新会积累碎片（用 &lt;code&gt;db.collection.compact()&lt;/code&gt; 定期回收），以及&lt;strong&gt;频繁长大的文档会引发频繁页分裂&lt;/strong&gt;——这是第三篇「增长边界」判断的底层原因，时序类数据的标准解法是按时间桶预分配文档的 bucket 模式，让单文档尺寸保持稳定。&lt;/p&gt;
&lt;h3 id=&#34;22-为什么快读快与写快的底层原因&#34;&gt;2.2 为什么快：读快与写快的底层原因&lt;/h3&gt;
&lt;p&gt;「MongoDB 快」不能当口号喊，要能拆成具体机制。先写后读。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;写入为什么快——一条写请求的完整路径：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    APP[&amp;#34;应用&amp;lt;br/&amp;gt;Java 驱动&amp;#34;] --&amp;gt; M[&amp;#34;mongod&amp;lt;br/&amp;gt;内存操作&amp;#34;]
    M --&amp;gt; C[&amp;#34;WiredTiger cache&amp;lt;br/&amp;gt;修改页标记为 dirty&amp;lt;br/&amp;gt;写操作到此即完成&amp;#34;]
    M --&amp;gt; J[&amp;#34;journal 日志&amp;lt;br/&amp;gt;顺序追加写&amp;lt;br/&amp;gt;磁盘顺序 I/O 极快&amp;#34;]
    C -.-&amp;gt;|&amp;#34;checkpoint&amp;lt;br/&amp;gt;默认每 60 秒&amp;lt;br/&amp;gt;批量刷盘&amp;#34;| D[&amp;#34;数据文件&amp;lt;br/&amp;gt;压缩后的 B 树页&amp;#34;]
    style C fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;四个叠加的机制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;写内存即返回&lt;/strong&gt;。写操作修改的是 cache 里的页，标记 dirty 就完事，数据文件由 checkpoint 线程&lt;strong&gt;每 60 秒批量刷一次&lt;/strong&gt;——随机写被攒成了顺序批量写。这是「写快」的第一来源。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;journal 用顺序写保证持久性&lt;/strong&gt;。怕宕机丢内存里的数据？journal（WAL 日志）是&lt;strong&gt;顺序追加写&lt;/strong&gt;，磁盘的顺序写比随机写快几个数量级。&lt;code&gt;j: true&lt;/code&gt; 的写确认就是等 journal 落盘，而不是等数据文件落盘。&lt;/p&gt;
&lt;p&gt;这里必须正面回答一个所有人都会问的问题：&lt;strong&gt;「写入内存就返回，服务器挂了怎么办？」&lt;/strong&gt; 不会变成脏数据——cache 里丢掉的只是 dirty 页的内存副本，每次修改都已先追加进 journal；mongod 重启时以最后一次 checkpoint 为基线&lt;strong&gt;重放 journal&lt;/strong&gt;，把丢失的写补回数据文件。真正的风险窗口在确认时机：默认 &lt;code&gt;w:1&lt;/code&gt; 下 journal 每 ~100ms 才批量提交，断电最多丢「最近约 100ms 且未同步到从节点」的写（第六篇丢的 23 笔订单就是这么没的）；&lt;code&gt;j: true&lt;/code&gt; 等日志落盘，单节点宕机不丢；&lt;code&gt;w: majority&lt;/code&gt; 等多数派确认，机器物理损毁也不丢。&lt;strong&gt;丢不丢数据不是 MongoDB 决定的，是你选 writeConcern 档位时决定的&lt;/strong&gt;——这套「内存脏页异步刷盘 + 日志重放」和 MySQL InnoDB 的 redo log 是同一套思想。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文档级并发&lt;/strong&gt;。更新只锁单个文档，100 个线程写 100 个不同文档完全不互斥。对比早期 MySQL MyISAM 的表锁、乃至行锁时代的间隙锁开销，文档锁的冲突概率天然更低。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;没有跨行约束的包袱&lt;/strong&gt;。无外键检查、无触发器、单文档原子性不需要 undo log（改一个文档就是改一整块，要么全改要么不改），写入路径短。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;读取为什么快——四道防线：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;局部性&lt;/strong&gt;：如上，嵌入建模让「读一个完整实体」= 一次磁盘寻道，没有 JOIN 的多次随机 I/O。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存&lt;/strong&gt;：热数据常驻 WiredTiger cache（默认一半内存），命中内存的读在微秒级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B 树索引&lt;/strong&gt;：索引项 O(log n) 定位，千万级数据也就 4 层左右，加覆盖索引（第四篇）连文档都不用读。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;副本集读扩展&lt;/strong&gt;：&lt;code&gt;readPreference: secondary&lt;/code&gt; 把展示类读流量分摊到从节点，读吞吐随节点数线性扩展。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;大量读 + 大量写同时扛住的原因&lt;/strong&gt;：读写分离（从节点扛读）+ MVCC（读不阻塞写、写不阻塞读）+ 写内存化。这三件事同时成立，才撑得起电商大促「几十万读 QPS 的商品页 + 几万写 QPS 的库存扣减」同框的场景。还不够？分片把写入水平切到多个副本集，写吞吐同样线性扩展（第七篇）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快的代价（必须说清楚，否则就是忽悠）&lt;/strong&gt;：写内存即返回意味着默认 &lt;code&gt;w:1&lt;/code&gt; 下宕机可能丢最后一小段数据（第六篇的丢单事故就是这么来的）；热数据依赖内存，内存小于工作集时性能断崖；没有 JOIN 意味着关联查询要自己扛。&lt;strong&gt;MongoDB 的快是「用设计取舍换来的快」，不是魔法。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;23-wiredtiger-你必须知道的四件事&#34;&gt;2.3 WiredTiger 你必须知道的四件事&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    W[&amp;#34;WiredTiger 存储引擎&amp;lt;br/&amp;gt;MongoDB 3.2+ 默认&amp;#34;] --&amp;gt; A[&amp;#34;文档级并发控制&amp;lt;br/&amp;gt;多线程写同一集合不互斥&amp;lt;br/&amp;gt;只有同一文档冲突才重试&amp;#34;]
    W --&amp;gt; B[&amp;#34;压缩存储&amp;lt;br/&amp;gt;snappy 默认&amp;lt;br/&amp;gt;磁盘占用约为数据量的 30%-70%&amp;#34;]
    W --&amp;gt; C[&amp;#34;缓存机制&amp;lt;br/&amp;gt;cacheSizeGB 默认 = 50% × (内存 - 1GB)&amp;lt;br/&amp;gt;热数据全在内存里&amp;#34;]
    W --&amp;gt; D[&amp;#34;MVCC 多版本&amp;lt;br/&amp;gt;读不阻塞写 写不阻塞读&amp;lt;br/&amp;gt;snapshot 隔离&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;要记的结论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;文档级锁&lt;/strong&gt;：并发写同一个集合是免费的，但&lt;strong&gt;并发改同一个文档会串行重试&lt;/strong&gt;。这直接决定建模——秒杀库存如果把 100 个 SKU 的库存都嵌在一个商品文档里，大促时这个文档就是全站最热的锁热点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存即命&lt;/strong&gt;：WiredTiger cache 默认吃掉一半内存。生产上「Mongo 内存打满」往往不是泄漏，是 cache 在正常工作——看 &lt;code&gt;wiredTiger.cache&lt;/code&gt; 指标而不是看进程 RSS 下结论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;压缩是把双刃剑&lt;/strong&gt;：省磁盘，费 CPU。写密集型日志库可换 zlib 更高压缩比，CPU 紧张时别乱调。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MVCC 的代价&lt;/strong&gt;：老版本数据要清理，长时间不提交的事务会压住清理线程，导致 cache 中 dirty data 堆积、写入被拖慢。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #1（cache 被大事务压垮）&lt;/strong&gt;：我们有个定时任务一次性更新 200 万条商品，跑的时候整个库延迟飙升。原因：巨型事务导致 WiredTiger 无法清理旧版本，cache 中 dirty bytes 超过阈值后应用线程被迫参与淘汰（eviction），写入集体变慢。解法：&lt;strong&gt;批量操作必须拆批&lt;/strong&gt;（每批 1000 条 + 短暂让出窗口），生产环境永远不开「不设限的大批量更新」。这条后来成了团队《MongoDB 使用红线》第一条。&lt;/p&gt;
&lt;h3 id=&#34;24-本地环境直接上副本集别玩单机&#34;&gt;2.4 本地环境：直接上副本集，别玩单机&lt;/h3&gt;
&lt;p&gt;单节点 MongoDB 不支持事务，也不具备生产形态。本地练手就用 Docker Compose 起一套三节点副本集：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;3.8&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;mongo1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;mongo:5.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;command&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--replSet&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rs0&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--bind_ip_all&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;ports&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;27017:27017&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;mongo2&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;mongo:5.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;command&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--replSet&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rs0&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--bind_ip_all&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;mongo3&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;mongo:5.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;command&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--replSet&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rs0&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--bind_ip_all&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 初始化副本集（执行一次）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mongosh --eval &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;rs.initiate({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  _id: &amp;#34;rs0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  members: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    { _id: 0, host: &amp;#34;mongo1:27017&amp;#34; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    { _id: 1, host: &amp;#34;mongo2:27017&amp;#34; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    { _id: 2, host: &amp;#34;mongo3:27017&amp;#34; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;})&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;第三篇-数据建模核心战场没有之一&#34;&gt;第三篇 数据建模：核心战场，没有之一&lt;/h2&gt;
&lt;p&gt;MongoDB 精通与否，就看建模。SQL 建模有范式护体，Mongo 建模全靠经验与取舍——这也是 AI 最帮不上忙的部分（第八篇详谈）。&lt;/p&gt;
&lt;h3 id=&#34;31-唯一重要的问题嵌入还是引用&#34;&gt;3.1 唯一重要的问题：嵌入还是引用&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    Q[&amp;#34;一份数据，嵌入父文档&amp;lt;br/&amp;gt;还是独立成集合？&amp;#34;] --&amp;gt; A{&amp;#34;三个判断&amp;#34;}
    A --&amp;gt; B[&amp;#34;读写边界&amp;lt;br/&amp;gt;总是一起读写吗？&amp;#34;]
    A --&amp;gt; C[&amp;#34;增长边界&amp;lt;br/&amp;gt;数量有上限吗？&amp;#34;]
    A --&amp;gt; D[&amp;#34;并发边界&amp;lt;br/&amp;gt;会被高并发独立更新吗？&amp;#34;]
    B --&amp;gt;|&amp;#34;是，且&amp;#34;| E[&amp;#34;嵌入&amp;lt;br/&amp;gt;如：订单内嵌 items 快照&amp;#34;]
    C --&amp;gt;|&amp;#34;否，无限增长&amp;#34;| R[&amp;#34;引用&amp;lt;br/&amp;gt;如：商品引用评论&amp;#34;]
    D --&amp;gt;|&amp;#34;是，热点&amp;#34;| R2[&amp;#34;引用&amp;lt;br/&amp;gt;如：SKU 库存独立成文档&amp;lt;br/&amp;gt;避免单文档成锁热点&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个判断，每个都对应一个坑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读写边界&lt;/strong&gt;：订单的 &lt;code&gt;items&lt;/code&gt; 嵌入（下单时一次写入、之后整体读取）；但「收货地址」独立成集合——地址在下单、物流、售后多个流程被独立读写，嵌在用户文档里每次都要加载整个用户。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增长边界&lt;/strong&gt;：单文档 16MB 硬上限。商品的「问大家」嵌进商品文档，爆款商品一年撑爆，连夜迁移。教训：&lt;strong&gt;文档不是仓库，是货架。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并发边界&lt;/strong&gt;：文档级锁决定了——&lt;strong&gt;凡是会被高并发写的数据，从父文档里拆出去&lt;/strong&gt;。秒杀库存必须一个 SKU 一个文档，绝不能嵌在商品里。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-电商核心建模订单快照与库存拆分&#34;&gt;3.2 电商核心建模：订单快照与库存拆分&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;erDiagram
    USER ||--o{ ORDER : &amp;#34;引用&amp;#34;
    PRODUCT ||--o{ SKU_STOCK : &amp;#34;拆出 独立高并发写&amp;#34;
    ORDER ||--|{ ORDER_ITEM : &amp;#34;内嵌 商品快照&amp;#34;
    PRODUCT ||--o{ COMMENT : &amp;#34;引用 无界增长&amp;#34;
    PRODUCT {
        ObjectId _id
        string name
        string categoryId
        array attrs &amp;#34;类目属性 灵活嵌入&amp;#34;
        string detailUrl &amp;#34;富文本放对象存储&amp;#34;
    }
    SKU_STOCK {
        ObjectId _id
        ObjectId productId
        string spec
        int stock &amp;#34;独立文档 原子扣减&amp;#34;
        int version &amp;#34;乐观锁&amp;#34;
    }
    ORDER {
        ObjectId _id
        ObjectId userId
        array items &amp;#34;名称/单价/图片快照&amp;#34;
        long totalAmount &amp;#34;单位: 分&amp;#34;
        string status
        date createdAt
    }&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两个关键设计，每个都值一次线上事故的钱：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;订单必须存商品快照&lt;/strong&gt;。下单那一刻把商品名、成交单价、主图冗余进 &lt;code&gt;items&lt;/code&gt;。商品会改名改价下架，三个月后客服处理售后，看的必须是「成交时」的样子。快照冗余不是脏数据，是业务真相。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;金额用「分」，long 存&lt;/strong&gt;。浮点的 &lt;code&gt;199.00&lt;/code&gt; 元在 IEEE 754 世界里是 &lt;code&gt;198.999...&lt;/code&gt;，所有和钱有关的字段一律整数存分。这不是 Mongo 的问题，但 Mongo 的自由度会让人更容易犯懒。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之二&lt;/strong&gt;：SQL 建模回答「数据之间是什么关系」，Mongo 建模回答「数据以什么节奏被读写」。前者是静态的学问，后者是动态的学问——必须预测流量形状（谁是热点、谁会暴涨、谁一起出现）才能建好模。这就是为什么 Mongo 建模更依赖业务理解，也更难被自动化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第四篇-索引与-explain调优基本功没有捷径&#34;&gt;第四篇 索引与 explain：调优基本功，没有捷径&lt;/h2&gt;
&lt;h3 id=&#34;41-explain-是唯一标准答案&#34;&gt;4.1 explain 是唯一标准答案&lt;/h3&gt;
&lt;p&gt;任何「这条查询为什么慢」的讨论，不贴 explain 输出就是耍流氓。Java 里看执行计划：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 在 Java 驱动里用 explain 需要走 Document 命令（或先在 mongosh 里验证）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// mongosh 中的写法：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;db.&lt;span style=&#34;color:#a6e22e&#34;&gt;products&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;({ categoryId: cid, status: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;on&amp;#34;&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .&lt;span style=&#34;color:#a6e22e&#34;&gt;sort&lt;/span&gt;({ price: &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;1 })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .&lt;span style=&#34;color:#a6e22e&#34;&gt;explain&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;executionStats&amp;#34;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;输出只盯四处：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;winningPlan.stage&amp;#34;] --&amp;gt; B{&amp;#34;COLLSCAN?&amp;#34;}
    B --&amp;gt;|&amp;#34;是&amp;#34;| C[&amp;#34;全表扫描&amp;lt;br/&amp;gt;立刻建索引&amp;#34;]
    B --&amp;gt;|&amp;#34;IXSCAN&amp;#34;| D[&amp;#34;看三个数字&amp;#34;]
    D --&amp;gt; E[&amp;#34;nReturned&amp;lt;br/&amp;gt;返回多少条&amp;#34;]
    D --&amp;gt; F[&amp;#34;totalDocsExamined&amp;lt;br/&amp;gt;读了多少文档&amp;#34;]
    D --&amp;gt; G[&amp;#34;totalKeysExamined&amp;lt;br/&amp;gt;扫了多少索引键&amp;#34;]
    E --&amp;gt; H{&amp;#34;理想: 三者接近&amp;lt;br/&amp;gt;危险: Examined 远大于 Returned&amp;#34;}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-esr-法则与-java-建索引&#34;&gt;4.2 ESR 法则与 Java 建索引&lt;/h3&gt;
&lt;p&gt;复合索引字段顺序：&lt;strong&gt;Equality（等值）→ Sort（排序）→ Range（范围）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;商品列表页查询：&lt;code&gt;{ categoryId: 等值, status: 等值 }&lt;/code&gt; + 按价格倒序，用 Java 驱动建索引：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 依赖: org.mongodb:mongodb-driver-sync:4.3.x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClients;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Indexes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IndexDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; (MongoClient client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MongoClients.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mongodb://localhost:27017/?replicaSet=rs0&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; products &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;products&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// ESR：categoryId/status 等值(E) 在前，price 排序(S) 在后&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            products.&lt;span style=&#34;color:#a6e22e&#34;&gt;createIndex&lt;/span&gt;(Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;compoundIndex&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;categoryId&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;descending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;price&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 部分索引：只给在售商品建，索引体积砍半&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            products.&lt;span style=&#34;color:#a6e22e&#34;&gt;createIndex&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;price&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;IndexOptions&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            .&lt;span style=&#34;color:#a6e22e&#34;&gt;partialFilterExpression&lt;/span&gt;(com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;on&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// TTL 索引：埋点日志 7 天自动删除，省掉清理脚本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;access_logs&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    .&lt;span style=&#34;color:#a6e22e&#34;&gt;createIndex&lt;/span&gt;(Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;IndexOptions&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;expireAfter&lt;/span&gt;(7L, java.&lt;span style=&#34;color:#a6e22e&#34;&gt;util&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;concurrent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;TimeUnit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DAYS&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意：加了「价格区间筛选」后 &lt;code&gt;price&lt;/code&gt; 就从 Sort 变成 Range，一个索引满足不了两个需求——&lt;strong&gt;索引设计永远是针对具体查询集合的权衡，没有万能索引&lt;/strong&gt;。每个索引都拖慢写入，写多读少的日志库尤其敏感。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #2（32MB 排序内存墙）&lt;/strong&gt;：运营后台「全量商品按上架时间倒序」接口，商品过 10 万后直接报 &lt;code&gt;Sort exceeded memory limit of 33554432 bytes&lt;/code&gt;。Mongo 内存排序上限 32MB，超了就炸。解法不是调参数，是建索引让排序走索引（索引天然有序）。&lt;strong&gt;上线红线第二条：凡带 sort 的查询，必须 explain 验证走索引排序。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #3（正则索引进退两难）&lt;/strong&gt;：模糊搜索 &lt;code&gt;find({ name: /卫衣/ })&lt;/code&gt;，非前缀正则无法走索引，数据量上来后 P99 三秒起步。前缀匹配 &lt;code&gt;/^卫衣/&lt;/code&gt; 可以走索引，但中文搜索很少是前缀形态。2021 年的正经答案：&lt;strong&gt;Mongo 管数据，ES 管搜索&lt;/strong&gt;，用 oplog/canal 同步，别指望一个库包打天下。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第五篇-聚合管道把计算下推给服务端&#34;&gt;第五篇 聚合管道：把计算下推给服务端&lt;/h2&gt;
&lt;p&gt;核心思维：「能在库里算完的，绝不拉回应用层」。拉 10 万条数据到 JVM 里 for 循环求和，是最常见的性能自杀。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;$match&amp;lt;br/&amp;gt;筛: 本月已支付&amp;#34;] --&amp;gt; B[&amp;#34;$unwind&amp;lt;br/&amp;gt;拆: items 展开&amp;#34;]
    B --&amp;gt; C[&amp;#34;$group&amp;lt;br/&amp;gt;按商品汇总&amp;#34;]
    C --&amp;gt; D[&amp;#34;$sort&amp;lt;br/&amp;gt;销量倒序&amp;#34;]
    D --&amp;gt; E[&amp;#34;$limit&amp;lt;br/&amp;gt;Top 10&amp;#34;]
    E --&amp;gt; F[&amp;#34;热销榜&amp;lt;br/&amp;gt;一次出结果&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;实战 Demo：本月热销商品 Top 10（Java 驱动写法）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo01-top-sales：本月热销榜&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClients;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Accumulators;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Aggregates;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Filters;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Sorts;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.conversions.Bson;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.ArrayList;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.Arrays;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.Date;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.List;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TopSalesDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; (MongoClient client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MongoClients.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mongodb://localhost:27017/?replicaSet=rs0&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; orders &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;orders&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Date monthStart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date(); &lt;span style=&#34;color:#75715e&#34;&gt;// 实际取本月 1 号 0 点，此处从简&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Date monthEnd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Bson&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; pipeline &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Arrays.&lt;span style=&#34;color:#a6e22e&#34;&gt;asList&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;// 第一道永远是 $match：能用索引，砍掉 99% 的数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt;(Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;paid&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;gte&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;, monthStart),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;lt&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;, monthEnd))),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;unwind&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.productId&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Accumulators.&lt;span style=&#34;color:#a6e22e&#34;&gt;first&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;productName&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.productName&amp;#34;&lt;/span&gt;), &lt;span style=&#34;color:#75715e&#34;&gt;// 快照红利：不用 $lookup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Accumulators.&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalQty&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.qty&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Accumulators.&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalAmount&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Document(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$multiply&amp;#34;&lt;/span&gt;, Arrays.&lt;span style=&#34;color:#a6e22e&#34;&gt;asList&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.qty&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.price&amp;#34;&lt;/span&gt;)))),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;sort&lt;/span&gt;(Sorts.&lt;span style=&#34;color:#a6e22e&#34;&gt;descending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalQty&amp;#34;&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;(10)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; hotList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ArrayList&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            orders.&lt;span style=&#34;color:#a6e22e&#34;&gt;aggregate&lt;/span&gt;(pipeline)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    .&lt;span style=&#34;color:#a6e22e&#34;&gt;allowDiskUse&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)   &lt;span style=&#34;color:#75715e&#34;&gt;// 中间结果超 100MB 允许落盘，报表查询记得开&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    .&lt;span style=&#34;color:#a6e22e&#34;&gt;into&lt;/span&gt;(hotList);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            hotList.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;(doc &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(doc.&lt;span style=&#34;color:#a6e22e&#34;&gt;toJson&lt;/span&gt;()));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;三个要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$match&lt;/code&gt; 必须放第一&lt;/strong&gt;，放 &lt;code&gt;$unwind&lt;/code&gt; 后面等于先把 500 万订单拆成 3000 万条再筛——有人这么写过，直接把一个节点的内存打爆。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;allowDiskUse(true)&lt;/code&gt;&lt;/strong&gt;：聚合中间结果超 100MB 默认报错，报表类查询记得开。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能不用 &lt;code&gt;$lookup&lt;/code&gt; 就不用&lt;/strong&gt;。它是应急手段，性能和多表 join 一样是噩梦。需要大量 &lt;code&gt;$lookup&lt;/code&gt; 才能完成的查询，说明建模（第三篇）出了问题——回头改模型，别在查询层硬扛。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第六篇-副本集高可用与读写语义的分水岭&#34;&gt;第六篇 副本集：高可用与读写语义的分水岭&lt;/h2&gt;
&lt;p&gt;单节点 MongoDB 是玩具，副本集才是起点。这篇是「会用 Mongo」和「敢把核心业务放 Mongo」之间的分水岭。&lt;/p&gt;
&lt;h3 id=&#34;61-架构与选主&#34;&gt;6.1 架构与选主&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph RS[&amp;#34;副本集 rs0（3 节点是最小生产配置）&amp;#34;]
        P[&amp;#34;Primary&amp;lt;br/&amp;gt;唯一可写&amp;#34;]
        S1[&amp;#34;Secondary&amp;lt;br/&amp;gt;异步复制 oplog&amp;#34;]
        S2[&amp;#34;Secondary&amp;lt;br/&amp;gt;异步复制 oplog&amp;#34;]
        P -- &amp;#34;oplog 同步&amp;#34; --&amp;gt; S1
        P -- &amp;#34;oplog 同步&amp;#34; --&amp;gt; S2
    end
    P -.-&amp;gt;|&amp;#34;心跳超时 10s&amp;lt;br/&amp;gt;自动选主&amp;#34;| S1
    APP[&amp;#34;应用（驱动）&amp;#34;] --&amp;gt;|&amp;#34;自动感知拓扑变化&amp;lt;br/&amp;gt;故障转移对代码透明&amp;#34;| RS&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;必须理解的机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;oplog&lt;/strong&gt;：主节点的操作日志，从节点靠它追赶。oplog 是有界集合，从节点宕机太久会追不上，只能全量同步（initial sync）——大库的全量同步以小时计，这就是「从节点宕机要当事故处理」的原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动选主&lt;/strong&gt;：主挂了，10 秒左右选出新主。&lt;strong&gt;驱动自动重连新主，应用无感&lt;/strong&gt;——前提是连接串写对了（多节点 + &lt;code&gt;replicaSet=rs0&lt;/code&gt;），而不是只写了一个 IP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接池&lt;/strong&gt;：&lt;code&gt;MongoClient&lt;/code&gt; 是线程安全的重对象，&lt;strong&gt;全应用一个单例&lt;/strong&gt;，连接池大小用 &lt;code&gt;maxPoolSize&lt;/code&gt; 调。每请求 new 一个 &lt;code&gt;MongoClient&lt;/code&gt; 等于每请求建一批 TCP 连接，压测时会把数据库连接数打爆——这和「每请求连一次 MySQL」是同一种自杀。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-读写语义writeconcern-与-readconcern&#34;&gt;6.2 读写语义：writeConcern 与 readConcern&lt;/h3&gt;
&lt;p&gt;这部分决定你能不能睡着觉——数据到底「写没写进去」？&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo02-write-concern：不同档位，不同的安全与延迟权衡&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.WriteConcern;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; orders &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;orders&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 快但可能丢：主节点内存确认就返回（埋点日志可用）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;orders.&lt;span style=&#34;color:#a6e22e&#34;&gt;withWriteConcern&lt;/span&gt;(WriteConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;W1&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;insertOne&lt;/span&gt;(logDoc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 安全：多数派落盘 + journal 落盘才返回（订单必须这档）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;orders.&lt;span style=&#34;color:#a6e22e&#34;&gt;withWriteConcern&lt;/span&gt;(WriteConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;MAJORITY&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;withJournal&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)).&lt;span style=&#34;color:#a6e22e&#34;&gt;insertOne&lt;/span&gt;(orderDoc);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;电商业务对号入座：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;业务&lt;/th&gt;
					&lt;th&gt;writeConcern&lt;/th&gt;
					&lt;th&gt;理由&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;订单创建&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;majority&lt;/code&gt; + journal&lt;/td&gt;
					&lt;td&gt;主从切换时绝不能丢单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;库存扣减&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;majority&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;丢扣减记录不可忍&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;行为埋点&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;w: 1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;丢几条日志无所谓，别拖累写入&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;购物车&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;majority&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用户加购后刷新没了，投诉就来了&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;readConcern&lt;/code&gt; 同理：读「已提交到多数派」的数据用 &lt;code&gt;ReadConcern.MAJORITY&lt;/code&gt;，避免读到主从切换后被回滚的数据。&lt;strong&gt;钱相关的读，一律 majority；展示类的读，local 即可。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #4（丢单惊魂夜）&lt;/strong&gt;：上线初期订单写入用默认 &lt;code&gt;w: 1&lt;/code&gt;。某天主节点宕机选主，新主上没有最后 8 秒的 23 笔订单——用户付了钱（支付回调已记录），订单却不存在，客服电话被打爆。复盘结论：&lt;strong&gt;支付回调链路和订单链路的 writeConcern 必须对齐 majority&lt;/strong&gt;，宁慢勿丢。这 8 秒人工补单补了一整夜。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之三&lt;/strong&gt;：分布式系统里「默认配置」几乎都是「性能优先、安全靠边」的，MongoDB 的 &lt;code&gt;w:1&lt;/code&gt;、MySQL 的异步复制、Kafka 的 &lt;code&gt;acks=1&lt;/code&gt; 皆如此。成熟的标志之一：&lt;strong&gt;每接一个中间件，先把默认一致性档位改成业务需要的档位，再开始写业务代码。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第七篇-事务与分片规模化之后的一致性难题&#34;&gt;第七篇 事务与分片：规模化之后的一致性难题&lt;/h2&gt;
&lt;h3 id=&#34;71-能不用事务就不用原子操作的威力&#34;&gt;7.1 能不用事务就不用：原子操作的威力&lt;/h3&gt;
&lt;p&gt;MongoDB 单文档操作天然原子。电商防超卖，一条条件更新就够，根本不需要事务：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo03-deduct-stock：条件原子扣减，1000 并发抢 1 件也只有 1 个成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Filters;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Updates;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.result.UpdateResult;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.types.ObjectId;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DeductStockDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;deduct&lt;/span&gt;(MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; skuStocks, ObjectId skuId, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; qty) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        UpdateResult r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; skuStocks.&lt;span style=&#34;color:#a6e22e&#34;&gt;updateOne&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 库存够不够，放进查询条件 —— 判断和修改合并成一次原子操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;, skuId),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;gte&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, qty)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;combine&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;inc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;qty),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;inc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sold&amp;#34;&lt;/span&gt;, qty),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentDate&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;updatedAt&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (r.&lt;span style=&#34;color:#a6e22e&#34;&gt;getModifiedCount&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 0) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; IllegalStateException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;库存不足&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 没抢到，上层返回售罄&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;精髓：判断（&lt;code&gt;stock &amp;gt;= qty&lt;/code&gt;）和修改（&lt;code&gt;$inc&lt;/code&gt;）合并成&lt;strong&gt;一次原子操作&lt;/strong&gt;，不存在「查出来够、扣的时候不够」的竞态。这比任何分布式锁都干净。&lt;/p&gt;
&lt;h3 id=&#34;72-真正需要事务的场景&#34;&gt;7.2 真正需要事务的场景&lt;/h3&gt;
&lt;p&gt;跨集合的一致性——下单要同时「扣库存 + 建订单 + 核销券」，三个动作三个集合，这时上多文档事务（4.0+，需副本集）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo04-place-order-tx：多文档事务下单&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.ClientSessionOptions;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.ReadConcern;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.TransactionOptions;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.WriteConcern;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.ClientSession;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PlaceOrderTxDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;placeOrder&lt;/span&gt;(MongoClient client, Document orderDoc, ObjectId skuId, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; qty, ObjectId couponId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TransactionOptions txnOptions &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TransactionOptions.&lt;span style=&#34;color:#a6e22e&#34;&gt;builder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;readConcern&lt;/span&gt;(ReadConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;SNAPSHOT&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;writeConcern&lt;/span&gt;(WriteConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;MAJORITY&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; (ClientSession session &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;startSession&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ClientSessionOptions.&lt;span style=&#34;color:#a6e22e&#34;&gt;builder&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;defaultTransactionOptions&lt;/span&gt;(txnOptions).&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;())) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            session.&lt;span style=&#34;color:#a6e22e&#34;&gt;withTransaction&lt;/span&gt;(() &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; db &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 条件原子扣库存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db.&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sku_stocks&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;updateOne&lt;/span&gt;(session,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;, skuId),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gte&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, qty)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Updates&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;inc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;qty));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (r.&lt;span style=&#34;color:#a6e22e&#34;&gt;getModifiedCount&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 0) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; IllegalStateException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;库存不足，触发回滚&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 插入订单（含商品快照）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                db.&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;orders&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;insertOne&lt;/span&gt;(session, orderDoc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 3. 核销优惠券&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                db.&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;coupons&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;updateOne&lt;/span&gt;(session,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;, couponId),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;used&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Updates&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;used&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 任何一步抛异常，前面所有操作自动回滚&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as 下单服务
    participant M as MongoDB 副本集
    A-&amp;gt;&amp;gt;M: withTransaction 开启
    A-&amp;gt;&amp;gt;M: 1. 条件原子扣库存
    alt 库存不足
        M--&amp;gt;&amp;gt;A: modifiedCount = 0
        A-&amp;gt;&amp;gt;M: 自动 abortTransaction&amp;lt;br/&amp;gt;三步全部回滚
        A--&amp;gt;&amp;gt;A: 返回售罄
    else 成功
        A-&amp;gt;&amp;gt;M: 2. 插入订单(含快照)
        A-&amp;gt;&amp;gt;M: 3. 核销优惠券
        A-&amp;gt;&amp;gt;M: commit (w:majority)
        M--&amp;gt;&amp;gt;A: 提交成功
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;踩坑故事 #5（事务的 QPS 税）&lt;/strong&gt;：压测发现全事务下单链路 QPS 比「原子扣减 + 消息队列异步落单」低约 40%，且长事务会压住 WiredTiger 的旧版本清理（第二篇的坑）。大促链路的最终方案：&lt;strong&gt;核心扣减用单文档原子操作（快），订单与券的一致性用 MQ + 定时对账任务兜底（最终一致）&lt;/strong&gt;。事务留给后台管理类低频强一致操作。教训：&lt;strong&gt;事务是保险丝，不是日常用电。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;73-分片单副本集扛不住之后&#34;&gt;7.3 分片：单副本集扛不住之后&lt;/h3&gt;
&lt;p&gt;数据量单副本集放不下（经验值：单集合过 2TB 或写入 QPS 过万再考虑），才上分片：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    APP[&amp;#34;应用&amp;#34;] --&amp;gt; MONGOS[&amp;#34;mongos 路由层&amp;#34;]
    MONGOS --&amp;gt; S1[&amp;#34;Shard 1&amp;lt;br/&amp;gt;副本集&amp;#34;]
    MONGOS --&amp;gt; S2[&amp;#34;Shard 2&amp;lt;br/&amp;gt;副本集&amp;#34;]
    MONGOS --&amp;gt; S3[&amp;#34;Shard 3&amp;lt;br/&amp;gt;副本集&amp;#34;]
    MONGOS -.-&amp;gt; CFG[&amp;#34;Config Server&amp;lt;br/&amp;gt;元数据副本集&amp;#34;]
    S1 -.-&amp;gt;|&amp;#34;均衡器&amp;lt;br/&amp;gt;chunk 迁移&amp;#34;| S2&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;分片键（shard key）是 Mongo 最「一选定终身」的决策，选错无法修改（5.0 之前）。三条铁律：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;高基数&lt;/strong&gt;：用 &lt;code&gt;status&lt;/code&gt;（只有几个枚举值）做分片键，所有数据挤在少数 chunk 里，分了个寂寞。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免单调递增&lt;/strong&gt;：用 &lt;code&gt;createdAt&lt;/code&gt; 或自增 ObjectId 做分片键，所有新写入都打到&lt;strong&gt;最后一个分片&lt;/strong&gt;——热点分片被写爆，其他分片围观。日志类业务要么 hash 分片键，要么接受这个形态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;匹配查询模式&lt;/strong&gt;：订单按 &lt;code&gt;userId&lt;/code&gt; hash 分片，「查我的订单」路由到单分片，飞快；「全平台订单报表」变成广播查询（scatter-gather），慢——所以报表本来就不该打在分片集群上，该走数仓。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #6（jumbo chunk 卡死均衡）&lt;/strong&gt;：早期一张集合按 &lt;code&gt;categoryId&lt;/code&gt; 分片，头部类目一个 chunk 涨到 500MB 还无法拆分（jumbo chunk），均衡器彻底停摆，分片间数据倾斜到 3:1。最后导出数据重建集合换 hash 分片键才解决，停机 4 小时。&lt;strong&gt;分片键评审必须过架构组，这是红线第三条。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第八篇-运维底线与-ai-时代把系统做稳把代码写少&#34;&gt;第八篇 运维底线与 AI 时代：把系统做稳，把代码写少&lt;/h2&gt;
&lt;h3 id=&#34;81-生产运维的最低纲领&#34;&gt;8.1 生产运维的最低纲领&lt;/h3&gt;
&lt;p&gt;不展开讲 DBA 手册，只列必须亲自盯的五件事：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    M[&amp;#34;监控&amp;lt;br/&amp;gt;slow query / cache 压力 / oplog 窗口 / 复制延迟&amp;#34;] --&amp;gt; A[&amp;#34;告警&amp;lt;br/&amp;gt;复制延迟 &amp;gt; 30s 即报警&amp;lt;br/&amp;gt;别等追不上才发现&amp;#34;]
    B[&amp;#34;备份&amp;lt;br/&amp;gt; mongodump 逻辑备份&amp;lt;br/&amp;gt;+ 快照物理备份&amp;lt;br/&amp;gt;恢复演练每季度一次&amp;#34;] --&amp;gt; A
    C[&amp;#34;慢查询&amp;lt;br/&amp;gt;profiler 只开采样&amp;lt;br/&amp;gt;每周过 top 慢查询&amp;#34;] --&amp;gt; A
    D[&amp;#34;容量&amp;lt;br/&amp;gt;磁盘水位 70% 预警&amp;lt;br/&amp;gt;oplog 大小按写入峰值调&amp;#34;] --&amp;gt; A
    E[&amp;#34;权限&amp;lt;br/&amp;gt;最小化授权 禁止裸奔&amp;lt;br/&amp;gt;生产禁 dropDatabase&amp;#34;] --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;踩坑故事 #7（恢复演练救了我们一命）&lt;/strong&gt;：团队坚持每季度做一次「备份恢复演练」，被吐槽形式主义。直到有人误删集合（&lt;code&gt;drop()&lt;/code&gt; 当 &lt;code&gt;deleteMany()&lt;/code&gt; 用了），靠备份 40 分钟恢复，业务无损。&lt;strong&gt;没演练过的备份等于没有备份&lt;/strong&gt;——这句话值一个 P0 事故。&lt;/p&gt;
&lt;h3 id=&#34;82-ai-时代如何少写代码把系统做稳&#34;&gt;8.2 AI 时代：如何少写代码把系统做稳&lt;/h3&gt;
&lt;p&gt;写这篇博客的 2021 年，GitHub Copilot 刚发布。站在工程视角，做几个判断，留给未来验证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AI 会吃掉「模式化代码」，吃不掉「模式化决策」。&lt;/strong&gt; CRUD、Schema 定义、聚合管道骨架、连接池配置——这些高度模式化的代码 AI 生成得又快又准（你甚至可以现在就试试让 Copilot 补全上面的 &lt;code&gt;DeductStockDemo&lt;/code&gt;，完成度惊人）。但「嵌入还是引用」「分片键选什么」「writeConcern 定哪档」——这些&lt;strong&gt;依赖业务流量预测的决策&lt;/strong&gt;，AI 给不出答案。核心竞争力从「会写」转向「会判断」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「少写代码」的正确姿势是把决策做在数据层。&lt;/strong&gt; 用快照冗余消灭 &lt;code&gt;$lookup&lt;/code&gt;，用 TTL 索引消灭清理脚本，用原子扣减消灭分布式锁，用条件更新消灭事务——本篇通篇的「少写代码」讲的都是这个：&lt;strong&gt;好的建模让代码自然变少&lt;/strong&gt;。AI 是放大器，放大的既可以是好设计，也可以是坏设计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个人撑起一个 App 的门槛在崩塌。&lt;/strong&gt; MongoDB Atlas（托管副本集/分片）+ Serverless 函数 + AI 生成 CRUD 层，一个小电商的服务端几天就能搭起来。2021 年这还只是苗头，我确信它会成常态。但反过来：&lt;strong&gt;门槛越低，线上事故越便宜&lt;/strong&gt;——不懂 writeConcern 的人也能上线了，丢单的事故只会更多。AI 时代的工程教育，重点应该从「怎么实现」转向「哪里会炸」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 生成代码的第一评审项是资源语义。&lt;/strong&gt; 连接谁关、事务多长、批量多大、一致性哪档——AI 生成的代码功能往往是对的，资源语义常常是错的（比如给你 &lt;code&gt;w: 1&lt;/code&gt; 的默认写入）。Code Review 的重心会整体迁移到这四个问题上。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之四（收尾）&lt;/strong&gt;：工程的本质，是「替业务把不确定性挡在系统之外」。MongoDB 给了你自由度，也把范式的保护伞收走了——锁粒度、一致性档位、分片拓扑，每一个都要自己选。AI 会帮你把代码写得越来越快，但「选什么」的责任永远在人身上。这既是这份工作的辛苦之处，也是它的护城河。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;附录demo-与检查清单&#34;&gt;附录：Demo 与检查清单&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Demo&lt;/th&gt;
					&lt;th&gt;类/片段&lt;/th&gt;
					&lt;th&gt;覆盖知识点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;建索引&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;IndexDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;ESR 复合索引、部分索引、TTL 索引&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;热销榜聚合&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;TopSalesDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;$match&lt;/code&gt; 前置、&lt;code&gt;$unwind&lt;/code&gt;/&lt;code&gt;$group&lt;/code&gt;、&lt;code&gt;allowDiskUse&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;写入档位&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;demo02-write-concern&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;w:1&lt;/code&gt; / &lt;code&gt;majority&lt;/code&gt; / journal 的取舍&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;原子扣库存&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DeductStockDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;条件原子更新防超卖&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;事务下单&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;PlaceOrderTxDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多文档事务、回滚、事务读写关注级别&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;MongoDB 生产红线（本文沉淀，可直接抄进团队规范）：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生产环境永远不开不设限的大批量更新，批量操作必拆批 + 让出窗口。&lt;/li&gt;
&lt;li&gt;凡带 sort 的查询，必须 explain 验证走索引排序。&lt;/li&gt;
&lt;li&gt;分片键评审必须过架构组；能用副本集解决的绝不上分片。&lt;/li&gt;
&lt;li&gt;钱相关的读写一律 &lt;code&gt;majority&lt;/code&gt;；默认配置永远不可信。&lt;/li&gt;
&lt;li&gt;备份必须每季度做恢复演练，没演练过的备份等于没有备份。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;文中代码基于 MongoDB 5.0 + 官方 Java 同步驱动 4.3 实测可编译运行。如果你正站在「要不要接 MongoDB」的门口，建议按八篇顺序在本地副本集上把每个坑亲手复现一遍——看别人踩坑是故事，自己踩一遍才是经验。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>starmall：一个微信小程序电商系统的全栈实战——预约、支付与订单架构解析</title>
      <link>https://ftmao.com/blog/posts/starmall-wechat-miniprogram-ecommerce-fullstack/</link>
      <pubDate>Tue, 20 Jul 2021 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/starmall-wechat-miniprogram-ecommerce-fullstack/</guid>
      <description>&lt;h1 id=&#34;starmall一个微信小程序电商系统的全栈实战预约支付与订单架构解析&#34;&gt;starmall：一个微信小程序电商系统的全栈实战——预约、支付与订单架构解析&lt;/h1&gt;
&lt;p&gt;这是我 2021 年做的一套完整电商系统 &lt;strong&gt;starmall&lt;/strong&gt;（六爻星辰）：微信小程序端 + Java 后端 + PC 管理后台，支持商品购买、老师咨询时段预约、择吉日查询、在线咨询等业务。整理开源出来，写篇文章做一次完整的技术复盘。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端（uni-app 小程序）：&lt;a href=&#34;https://gitee.com/soulnew/starmall&#34;&gt;https://gitee.com/soulnew/starmall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;后端（Java + 管理端）：&lt;a href=&#34;https://gitee.com/soulnew/crm_server&#34;&gt;https://gitee.com/soulnew/crm_server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;技术栈：Spring Boot 2.2 + MyBatis-Plus + MySQL + Redis / uni-app + uView / Vue 2 + Element UI（管理端）&lt;/li&gt;
&lt;li&gt;说明：后端是在开源商城 &lt;strong&gt;CRMEB Java 版&lt;/strong&gt; 基础上二次开发的，分销/秒杀/拼团/砍价等营销能力来自底座，预约系统、择吉日、名片、咨询聊天等是自研部分&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一整体架构&#34;&gt;一、整体架构&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    MP[&amp;#34;微信小程序端&amp;lt;br/&amp;gt;starmall uni-app + uView&amp;#34;] --&amp;gt;|&amp;#34;https://ftmao.com/api/front/**&amp;#34;| API[&amp;#34;SpringBoot 单体后端&amp;lt;br/&amp;gt;62 个 Mapper / 23 个前台 Controller&amp;#34;]
    H5[&amp;#34;H5 / App&amp;lt;br/&amp;gt;uni-app 多端编译&amp;#34;] --&amp;gt; API
    ADMIN[&amp;#34;PC 管理后台&amp;lt;br/&amp;gt;Vue 2 + Element UI + ECharts&amp;#34;] --&amp;gt;|&amp;#34;后台接口&amp;#34;| API
    API --&amp;gt; MYSQL[&amp;#34;MySQL 5.7&amp;lt;br/&amp;gt;62 张表 Druid 连接池&amp;#34;]
    API --&amp;gt; REDIS[&amp;#34;Redis&amp;lt;br/&amp;gt;token / 订单缓存 / 任务队列 / 配置缓存&amp;#34;]
    API --&amp;gt; WX[&amp;#34;微信开放平台&amp;lt;br/&amp;gt;登录 / 支付 V2 / 订阅消息&amp;#34;]
    API --&amp;gt; QN[&amp;#34;七牛云 OSS&amp;lt;br/&amp;gt;图片上传&amp;#34;]
    API --&amp;gt; QY[&amp;#34;企业微信机器人&amp;lt;br/&amp;gt;部署结果通知&amp;#34;]
    MP --&amp;gt;|&amp;#34;wss://... websocket&amp;#34;| CHAT[&amp;#34;咨询聊天服务&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;代码规模：后端约 850 个 Java 文件、12.6 万行；小程序前端业务代码约 1.2 万行；数据库初始化 SQL 全量在仓库里，共 62 张表。后端按业务分包：&lt;code&gt;front&lt;/code&gt;（小程序接口）、&lt;code&gt;order/payment&lt;/code&gt;、&lt;code&gt;marketing&lt;/code&gt;、&lt;code&gt;seckill&lt;/code&gt;、&lt;code&gt;task&lt;/code&gt;（定时任务）、&lt;code&gt;wechat&lt;/code&gt;，以及自研的 &lt;code&gt;appoint&lt;/code&gt;（预约）、&lt;code&gt;lunar&lt;/code&gt;（黄历）等模块。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二功能介绍&#34;&gt;二、功能介绍&lt;/h2&gt;
&lt;h3 id=&#34;21-商品购买全流程&#34;&gt;2.1 商品购买全流程&lt;/h3&gt;
&lt;p&gt;标准的商城闭环：商品详情（多规格 SKU）→ 立即购买/加购物车 → 确认订单（优惠券、积分抵扣、运费计算）→ 创建订单 → 微信支付 → 轮询支付结果 → 收货评价 → 退款/售后。&lt;strong&gt;支付是真实对接的微信支付 V2&lt;/strong&gt;——后端手工实现 XML 报文 + MD5 签名调统一下单（&lt;code&gt;WeChatPayServiceImpl&lt;/code&gt;，746 行），前端弹起 &lt;code&gt;wx.requestPayment&lt;/code&gt;，不是 mock。&lt;/p&gt;
&lt;p&gt;此外还支持：余额支付、运费模板（满额包邮/指定城市包邮/按件数重量体积三种首续费）、物流查询、自提核销码、再次下单。营销玩法（秒杀、砍价、拼团、优惠券）来自 CRMEB 底座，一应俱全。&lt;/p&gt;
&lt;h3 id=&#34;22-老师咨询时段预约自研核心&#34;&gt;2.2 老师咨询时段预约（自研核心）&lt;/h3&gt;
&lt;p&gt;这是这个项目区别于普通商城的地方——卖的不只是实物商品，还有&amp;quot;老师的时间&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;商品分类为&amp;quot;老师类&amp;quot;时，商品详情页出现一个&lt;strong&gt;时段选择器&lt;/strong&gt;（每天 6 个时段：9/11/13/15/17/19 点）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;teacher_plan&lt;/code&gt; 表按「日期 × 时段」预生成号源，被约走的时段前端置灰；&lt;/li&gt;
&lt;li&gt;下单时携带 &lt;code&gt;teacherPlanId&lt;/code&gt;，&lt;strong&gt;创建订单的同一个事务里&lt;/strong&gt;把该时段标记为已约——号源占用和订单创建要么一起成功要么一起回滚，不会出现&amp;quot;钱付了时段没了&amp;quot;；&lt;/li&gt;
&lt;li&gt;号源由定时任务 &lt;code&gt;TeacherPlanTask&lt;/code&gt; 每 8 小时自动向前补齐未来 8 天，并清理过期的空闲号源。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;23-其他自研功能&#34;&gt;2.3 其他自研功能&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;择吉日查询&lt;/strong&gt;：4.7 万行黄历数据入库（&lt;code&gt;lunar&lt;/code&gt; 系列表），按&amp;quot;宜&amp;quot;事反查吉日，比如查&amp;quot;最近适合搬家的日子&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在线咨询&lt;/strong&gt;：基于 WebSocket 长连接的实时聊天，支持文字/语音/图片消息（详见第三章「长连接聊天」）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;老师名片&lt;/strong&gt;、文章资讯、签到积分、充值、小程序订阅消息提醒；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管理后台&lt;/strong&gt;：商品/订单/营销/分销/财务/短信/按钮级权限/数据统计大屏（ECharts）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三后端能力全景&#34;&gt;三、后端能力全景&lt;/h2&gt;
&lt;p&gt;后端虽然是 SpringBoot 单体，但能力面相当完整：前台 23 个 Controller、62 个 MyBatis Mapper，按业务分包组织：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    subgraph M1[&amp;#34;业务模块&amp;#34;]
        A1[&amp;#34;front 前台接口&amp;lt;br/&amp;gt;23 个 Controller&amp;#34;]
        A2[&amp;#34;order / payment&amp;lt;br/&amp;gt;订单与支付&amp;#34;]
        A3[&amp;#34;marketing / seckill / bargain / combination&amp;lt;br/&amp;gt;优惠券 秒杀 砍价 拼团&amp;#34;]
        A4[&amp;#34;appoint / lunar / businessCard&amp;lt;br/&amp;gt;预约 择吉日 名片（自研）&amp;#34;]
        A5[&amp;#34;finance / statistics / export&amp;lt;br/&amp;gt;充值提现 数据统计 Excel 导出&amp;#34;]
        A6[&amp;#34;wechat / wechatMessage / sms&amp;lt;br/&amp;gt;微信生态与短信&amp;#34;]
        A7[&amp;#34;task&amp;lt;br/&amp;gt;定时任务体系&amp;#34;]
    end
    subgraph M2[&amp;#34;公共层&amp;#34;]
        B1[&amp;#34;拦截器链&amp;lt;br/&amp;gt;前台 Token / 后台 Token+权限 / Swagger&amp;#34;]
        B2[&amp;#34;工具层 RedisUtil / WxPayUtil / CrmebUtil&amp;#34;]
        B3[&amp;#34;ThreadLocal 上下文传递当前用户&amp;#34;]
    end
    A1 --&amp;gt; B1
    A2 --&amp;gt; B2
    B1 --&amp;gt; B3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;商城主链路之外，后端还有一批容易被忽略的能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;微信公众号消息自动回复&lt;/strong&gt;：&lt;code&gt;wechatMessage&lt;/code&gt; 模块支持文字/语音/图片/图文四种回复类型，关注、取关、扫码等事件都有对应处理；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模板消息与订阅消息&lt;/strong&gt;：小程序订阅消息、公众号模板消息的增删查与群发，且发送走异步任务（&lt;code&gt;AsyncWeChatProgramTempMessage&lt;/code&gt; 等），不阻塞主流程；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渠道二维码与素材管理&lt;/strong&gt;：带参数的推广二维码、微信素材的上传管理；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据导出与统计&lt;/strong&gt;：POI 实现的 Excel 导出、交易/商品/用户多维统计接口（配合管理端 ECharts 大屏）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;短信与物流&lt;/strong&gt;：短信验证码/通知、快递物流轨迹查询；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口文档&lt;/strong&gt;：Swagger 在线文档 + 仓库内置 admin/app/公共三份离线接口文档。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;31-长连接聊天websocket架构&#34;&gt;3.1 长连接聊天（WebSocket）架构&lt;/h3&gt;
&lt;p&gt;在线咨询是这个系统的&amp;quot;第二前台&amp;quot;，采用的是 &lt;strong&gt;WebSocket 长连接 + HTTP 接口&lt;/strong&gt;的组合方案。部署上两个服务跑在同一台服务器，由 nginx 按路径前缀分流：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    MP[&amp;#34;小程序聊天页&amp;lt;br/&amp;gt;pages/chat&amp;#34;] --&amp;gt;|&amp;#34;wss://.../websocket/userId/sessionId&amp;#34;| NGX[&amp;#34;nginx 按路径分流&amp;#34;]
    MP --&amp;gt;|&amp;#34;HTTP /chat/session/**  /chat/msg/**&amp;#34;| NGX
    MP --&amp;gt;|&amp;#34;HTTP /api/front/**&amp;#34;| NGX
    NGX --&amp;gt; CHAT[&amp;#34;聊天服务&amp;lt;br/&amp;gt;WebSocket 长连接 + 会话/消息接口&amp;#34;]
    NGX --&amp;gt; MALL[&amp;#34;商城后端 crm_server&amp;lt;br/&amp;gt;SpringBoot 18080&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;从开源的小程序端实现和接口契约，可以看到这套长连接聊天的几个设计要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;连接即身份&lt;/strong&gt;：WebSocket 握手 URL 直接携带 &lt;code&gt;userId/sessionId&lt;/code&gt;，连接建立的同时就完成了身份识别与会话路由，不需要额外的登录帧；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时走长连接，持久化走 HTTP&lt;/strong&gt;：收发消息走 WebSocket（消息体为 JSON）；会话绑定（&lt;code&gt;session/bindSession&lt;/code&gt;）、会话列表、删除会话、历史消息拉取（&lt;code&gt;msg/msgList&lt;/code&gt;）都走 &lt;code&gt;/chat/&lt;/code&gt; 前缀的 HTTP 接口——长连接管&amp;quot;实时推送&amp;quot;，HTTP 管&amp;quot;可查询、可回溯&amp;quot;，职责分得很干净；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一的消息类型协议&lt;/strong&gt;：&lt;code&gt;contentType&lt;/code&gt; 1=文字、2=语音（带 &lt;code&gt;contentDuration&lt;/code&gt; 时长）、3=图片（先经 &lt;code&gt;user/upload/image&lt;/code&gt; 上传拿到 URL，再把 URL 作为消息内容发出）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语音消息的移动端交互&lt;/strong&gt;：按住说话（&lt;code&gt;touchstart&lt;/code&gt;/&lt;code&gt;touchend&lt;/code&gt; 控制录音起止）、上滑取消（&lt;code&gt;touchmove&lt;/code&gt;/&lt;code&gt;touchcancel&lt;/code&gt;），体验和微信原生聊天对齐；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;发送体验乐观化&lt;/strong&gt;：消息发出后先本地 push 进消息列表立即上屏，&lt;code&gt;send&lt;/code&gt; 的 fail 回调再 toast 兜底提示。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;需要如实说明：聊天服务端的代码没有随这两个仓库开源（它部署在同一台服务器上，由 nginx 分流），仓库里能看到的生产实现是小程序端；&lt;code&gt;crm_server&lt;/code&gt; 里的 &lt;code&gt;com/im/SocketServer.java&lt;/code&gt; 只是当时练手的原生 TCP Socket demo（12345 端口），并非线上实现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四技术亮点&#34;&gt;四、技术亮点&lt;/h2&gt;
&lt;h3 id=&#34;41-下单幂等两段式--唯一键兜底&#34;&gt;4.1 下单幂等：两段式 + 唯一键兜底&lt;/h3&gt;
&lt;p&gt;小程序网络环境下用户双击、重试、断网重发是常态，下单接口必须幂等。这里用了一个两段式设计：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;order/confirm 确认订单页&amp;#34;] --&amp;gt; B[&amp;#34;订单数据序列化存 Redis&amp;lt;br/&amp;gt;key = md5 当前时间&amp;lt;br/&amp;gt;有效期 60 分钟&amp;#34;]
    B --&amp;gt; C[&amp;#34;order/create/orderKey 创建订单&amp;#34;]
    C --&amp;gt; D{&amp;#34;第一步:查库判重&amp;lt;br/&amp;gt;StoreOrder.unique = orderKey&amp;#34;}
    D --&amp;gt;|&amp;#34;已存在&amp;#34;| E[&amp;#34;直接返回已有订单&amp;lt;br/&amp;gt;不重复创建&amp;#34;]
    D --&amp;gt;|&amp;#34;不存在&amp;#34;| F{&amp;#34;第二步:校验 Redis 缓存&amp;lt;br/&amp;gt;是否过期/被用过&amp;#34;}
    F --&amp;gt;|&amp;#34;过期&amp;#34;| G[&amp;#34;拒绝 提示重新确认&amp;#34;]
    F --&amp;gt;|&amp;#34;有效&amp;#34;| H[&amp;#34;事务内创建订单&amp;lt;br/&amp;gt;成功后删除缓存&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;unique&lt;/code&gt; 字段是最后的兜底——即使 Redis 挂了、缓存丢了，数据库唯一约束也保证同一个 orderKey 不会建出两张单。&lt;/p&gt;
&lt;h3 id=&#34;42-原子扣库存防超卖&#34;&gt;4.2 原子扣库存防超卖&lt;/h3&gt;
&lt;p&gt;库存扣减没有用&amp;quot;先查后改&amp;quot;，而是一条 SQL 完成：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// setSql + 条件改写，数据库层面原子完成&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; stock &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; n   且要求   stock &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;数据库单行更新的原子性天然挡住并发超卖，不需要分布式锁。秒杀/砍价/拼团场景更复杂：同一事务内要扣&lt;strong&gt;活动表 + 活动 SKU 表 + 商品表 + 商品 SKU 表&lt;/strong&gt;四份库存，任何一步失败整体回滚。&lt;/p&gt;
&lt;h3 id=&#34;43-编程式事务包住整个建单过程&#34;&gt;4.3 编程式事务包住整个建单过程&lt;/h3&gt;
&lt;p&gt;创建订单是个多写操作：扣库存、占预约时段、写订单主表、写订单详情、核销优惠券、写订单日志。代码用 &lt;code&gt;TransactionTemplate&lt;/code&gt; 编程式事务（而不是 &lt;code&gt;@Transactional&lt;/code&gt; 注解）把这一串操作包起来——好处是事务边界精确可控，不会把整个方法（包括不需要事务的远程调用）都圈进去。订单核心逻辑集中在 &lt;code&gt;OrderUtils.java&lt;/code&gt;（1500+ 行）和 &lt;code&gt;OrderServiceImpl.java&lt;/code&gt;（近 1000 行）。&lt;/p&gt;
&lt;h3 id=&#34;44-支付回调防重--异步化&#34;&gt;4.4 支付回调：防重 + 异步化&lt;/h3&gt;
&lt;p&gt;微信支付的回调处理（&lt;code&gt;CallbackServiceImpl.weChat&lt;/code&gt;，448 行）有几个关键点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;解析 XML、校验 &lt;code&gt;return_code&lt;/code&gt;/&lt;code&gt;result_code&lt;/code&gt;，按 &lt;code&gt;attach&lt;/code&gt; 里携带的 userId + type 区分是订单支付还是充值；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;已支付直接返回 SUCCESS&lt;/strong&gt;——微信会多次重推回调，幂等是回调接口的生死线；&lt;/li&gt;
&lt;li&gt;事务内完成 &lt;code&gt;updatePaid&lt;/code&gt;（置订单已支付）+ 扣积分 + 拼团状态处理；&lt;/li&gt;
&lt;li&gt;后续的&amp;quot;慢动作&amp;quot;——发短信、推订阅消息、分销结算——&lt;strong&gt;不放在回调里同步做&lt;/strong&gt;，而是 &lt;code&gt;lPush&lt;/code&gt; 进 Redis 队列异步执行，回调接口尽快给微信返回，避免超时重推。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;45-没有-mqredis-list--定时任务的务实替代&#34;&gt;4.5 没有 MQ：Redis List + 定时任务的务实替代&lt;/h3&gt;
&lt;p&gt;项目没有引入 RabbitMQ/RocketMQ，而是用 &lt;strong&gt;Redis List 当队列 + Spring &lt;code&gt;@Scheduled&lt;/code&gt; 定时任务消费&lt;/strong&gt;，撑起了一整套异步任务体系：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;下单成功&amp;lt;br/&amp;gt;lPush ORDER_AUTO_CANCEL_KEY&amp;#34;] --&amp;gt; Q[&amp;#34;Redis List 队列&amp;#34;]
    P[&amp;#34;支付成功回调&amp;lt;br/&amp;gt;lPush 后续动作&amp;#34;] --&amp;gt; Q
    Q --&amp;gt; T1[&amp;#34;OrderAutoCancelTask&amp;lt;br/&amp;gt;每分钟消费&amp;lt;br/&amp;gt;超时未支付自动取消 + 回滚库存&amp;#34;]
    Q --&amp;gt; T2[&amp;#34;自动收货 / 自动好评&amp;#34;]
    Q --&amp;gt; T3[&amp;#34;优惠券过期 / 砍价截止&amp;lt;br/&amp;gt;佣金解冻&amp;#34;]
    Q --&amp;gt; T4[&amp;#34;TeacherPlanTask&amp;lt;br/&amp;gt;每8小时补齐号源&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;task/order/&lt;/code&gt; 下共有 8 个定时任务。对这个体量的系统，省掉一个 MQ 的部署运维成本，换来的是完全够用的可靠性——这是小项目技术选型上我很认可的一个取舍。&lt;/p&gt;
&lt;h3 id=&#34;46-鉴权链路uuid-token--redis--threadlocal&#34;&gt;4.6 鉴权链路：UUID Token + Redis + ThreadLocal&lt;/h3&gt;
&lt;p&gt;没有用 JWT，而是更传统的方案：登录后签发 UUID 存 Redis（按模块区分 key 前缀），拦截器 &lt;code&gt;FrontTokenInterceptor&lt;/code&gt; → &lt;code&gt;CheckFrontToken&lt;/code&gt; → &lt;code&gt;TokenManagerImpl&lt;/code&gt; 逐级校验，通过后把当前用户放进 &lt;code&gt;ThreadLocal&lt;/code&gt; 供全链路取用。管理端是 Token + 权限双拦截器。好处是&lt;strong&gt;服务端可随时踢人下线&lt;/strong&gt;（删 Redis key 即可），这是纯 JWT 做不到的。&lt;/p&gt;
&lt;h3 id=&#34;47-其他值得提的实现&#34;&gt;4.7 其他值得提的实现&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;订单快照&lt;/strong&gt;：下单时把购物车内容序列化成 JSON 存进 &lt;code&gt;StoreOrderInfo.info&lt;/code&gt;，之后商品改价改名都不影响历史订单的展示；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运费计算引擎&lt;/strong&gt;：&lt;code&gt;getOrderPriceGroup&lt;/code&gt; 支持满额包邮、指定城市包邮（件数 + 金额双条件）、按件数/重量/体积三种首续计费模式；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XSS 转义&lt;/strong&gt;：用户输入统一 &lt;code&gt;escapeHtml4&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署脚本&lt;/strong&gt;：&lt;code&gt;start.sh&lt;/code&gt; nohup 启动 + 轮询日志判断启动成功 + 企业微信机器人推送部署结果——单机部署也搞出了仪式感。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五复盘与遗憾&#34;&gt;五、复盘与遗憾&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;底座与自研的边界&lt;/strong&gt;：秒杀/拼团/砍价/分销这些&amp;quot;重&amp;quot;能力直接复用 CRMEB 底座，把精力集中在业务差异化（预约、择吉日、聊天）上——对个人开发者来说这是正确的杠杆；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单体的局限&lt;/strong&gt;：所有东西在一个 SpringBoot 应用里，定时任务、支付、前台接口互相影响，量大了必然要拆；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原子扣库存的返回值没检查&lt;/strong&gt;：&lt;code&gt;setSql&lt;/code&gt; 扣减失败（库存不足）时影响的行数为 0，但代码没有校验这个返回值，极端情况下会漏判超卖——这是现在回头看想补的一个洞；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;聊天服务端未开源&lt;/strong&gt;：如第三章所述，长连接聊天的服务端实现没有随仓库开源，小程序端和接口契约是完整的，算是开源的一点遗憾。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六写在最后&#34;&gt;六、写在最后&lt;/h2&gt;
&lt;p&gt;这个项目让我完整走了一遍&amp;quot;真实收钱&amp;quot;的系统该有的样子：幂等、事务、防超卖、回调防重、异步补偿——每一个点背后都是电商系统趟过的坑。代码在 &lt;a href=&#34;https://gitee.com/soulnew/starmall&#34;&gt;https://gitee.com/soulnew/starmall&lt;/a&gt; 和 &lt;a href=&#34;https://gitee.com/soulnew/crm_server&#34;&gt;https://gitee.com/soulnew/crm_server&lt;/a&gt;，建库 SQL、接口文档都在仓库里，希望能给做小程序商城的朋友一个可参照的完整样本。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《Netty 源码剖析与应用》详细总结：写给从未接触过 Netty 的人</title>
      <link>https://ftmao.com/blog/bookstore/netty-source-code-analysis-notes/</link>
      <pubDate>Sat, 20 Feb 2021 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/netty-source-code-analysis-notes/</guid>
      <description>&lt;h1 id=&#34;netty-源码剖析与应用详细总结写给从未接触过-netty-的人&#34;&gt;《Netty 源码剖析与应用》详细总结：写给从未接触过 Netty 的人&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：刘耀林《Netty 源码剖析与应用》（电子工业出版社，2020 年 11 月）
目标读者：会 Java、听说过 Netty 但没系统学过，想一篇文章建立 Netty 核心知识框架的人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;这本书讲什么为什么学-netty&#34;&gt;〇、这本书讲什么？为什么学 Netty？&lt;/h2&gt;
&lt;p&gt;普通开发在工作中很少直接碰 Netty，但只要翻开任何一个主流分布式框架的底层源码，通信模块几乎都是它：&lt;strong&gt;Dubbo、gRPC、Spark、Flink、Elasticsearch、HBase&lt;/strong&gt;（HBase 从 2.0 开始默认用 Netty RPC Server 替代原生实现）。不管是做 Java Web 后台还是大数据，Netty 都是绕不过去的地基。&lt;/p&gt;
&lt;p&gt;为什么不直接用 JDK 的 NIO？作者的回答很直白：&lt;strong&gt;用原生 NIO 写一套高效稳定的通信模块非常难&lt;/strong&gt;——要处理多路复用、粘包拆包、线程模型、内存管理、断线重连等一堆坑。Netty 把这些全部封装好，让普通人也能快速开发高性能、面向协议的服务端和客户端。&lt;/p&gt;
&lt;p&gt;全书的结构是循序渐进的四部分：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;第一部分：实战入门&amp;lt;br/&amp;gt;第1章 Netty基础篇&amp;lt;br/&amp;gt;第2章 原理部分&amp;lt;br/&amp;gt;第3章 分布式RPC&amp;#34;] --&amp;gt; B[&amp;#34;第二部分：核心源码剖析&amp;lt;br/&amp;gt;第4章 核心组件&amp;lt;br/&amp;gt;（NioEventLoop/Channel/ByteBuf）&amp;lt;br/&amp;gt;第5章 读/写请求源码&amp;#34;]
    B --&amp;gt; C[&amp;#34;第三部分：内存管理&amp;lt;br/&amp;gt;第6章 jemalloc内存池&amp;lt;br/&amp;gt;（全书最难）&amp;#34;]
    C --&amp;gt; D[&amp;#34;第四部分：高级应用与调优&amp;lt;br/&amp;gt;第7章 时间轮高级应用&amp;lt;br/&amp;gt;第8章 问题分析与性能调优&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两个贯穿全书的高级项目：&lt;strong&gt;一套类似 Dubbo 的分布式 RPC 框架&lt;/strong&gt;（第 3 章写、第 8 章压测调优），以及&lt;strong&gt;一套基于时间轮的 10 亿级任务调度监控引擎&lt;/strong&gt;（第 7 章）。&lt;/p&gt;
&lt;h2 id=&#34;一netty-基础篇先看长连接通信长什么样第-1-章&#34;&gt;一、Netty 基础篇：先看长连接通信长什么样（第 1 章）&lt;/h2&gt;
&lt;h3 id=&#34;11-服务端五步法&#34;&gt;1.1 服务端五步法&lt;/h3&gt;
&lt;p&gt;Netty 服务端程序的固定套路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建两个线程组：&lt;strong&gt;Boss 线程组&lt;/strong&gt;专门接收客户端连接；&lt;strong&gt;Worker 线程组&lt;/strong&gt;处理已接收连接的 I/O 读写；&lt;/li&gt;
&lt;li&gt;用启动辅助类 &lt;code&gt;ServerBootstrap&lt;/code&gt; 配置启动参数（如 &lt;code&gt;ChannelOption.SO_RCVBUF&lt;/code&gt; / &lt;code&gt;SO_SNDBUF&lt;/code&gt; 设置接收/发送缓冲区大小）；&lt;/li&gt;
&lt;li&gt;Boss 把接收到的连接注册到 Worker 后，交给 &lt;code&gt;ChannelInitializer&lt;/code&gt; 初始化 Handler 链；&lt;/li&gt;
&lt;li&gt;编写业务 Handler（继承 &lt;code&gt;ChannelInboundHandlerAdapter&lt;/code&gt;，重写 &lt;code&gt;channelRead&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;绑定端口——绑定由 Boss 线程完成，所以主线程要执行同步阻塞方法等待绑定完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这就是经典的&lt;strong&gt;主从 Reactor 模型&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph BossGroup[&amp;#34;Boss 线程组（接收连接）&amp;#34;]
        B1[&amp;#34;Boss NioEventLoop&amp;lt;br/&amp;gt;监听 OP_ACCEPT&amp;#34;]
    end
    subgraph WorkerGroup[&amp;#34;Worker 线程组（处理 I/O）&amp;#34;]
        W1[&amp;#34;Worker 线程 1&amp;lt;br/&amp;gt;管理多个 Channel&amp;#34;]
        W2[&amp;#34;Worker 线程 2&amp;lt;br/&amp;gt;管理多个 Channel&amp;#34;]
    end
    C1[&amp;#34;客户端连接 1&amp;#34;] --&amp;gt; B1
    C2[&amp;#34;客户端连接 2&amp;#34;] --&amp;gt; B1
    C3[&amp;#34;客户端连接 3&amp;#34;] --&amp;gt; B1
    B1 --&amp;gt;|&amp;#34;注册 SocketChannel&amp;#34;| W1
    B1 --&amp;gt;|&amp;#34;注册 SocketChannel&amp;#34;| W2
    W1 --&amp;gt; P1[&amp;#34;ChannelPipeline&amp;lt;br/&amp;gt;Decoder → 业务Handler → Encoder&amp;#34;]
    W2 --&amp;gt; P2[&amp;#34;ChannelPipeline&amp;lt;br/&amp;gt;Decoder → 业务Handler → Encoder&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;12-一个值得琢磨的示例客户端怎么同步等异步响应&#34;&gt;1.2 一个值得琢磨的示例：客户端怎么&amp;quot;同步等&amp;quot;异步响应&lt;/h3&gt;
&lt;p&gt;第 1 章的客户端示例藏着一个 Netty 编程的核心范式。客户端连续发 100 个请求，服务端异步返回。问题是：&lt;strong&gt;&lt;code&gt;channel.writeAndFlush()&lt;/code&gt; 是异步的，主线程怎么拿到响应结果？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;书里的解法是自实现一套 &lt;code&gt;RequestFuture&lt;/code&gt; 机制（这正是 Dubbo 等 RPC 框架的原型）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个请求带唯一 id，发送前把 &lt;code&gt;RequestFuture&lt;/code&gt; 存入以 id 为 key 的 Map，主线程在 &lt;code&gt;future.get()&lt;/code&gt; 上阻塞等待；&lt;/li&gt;
&lt;li&gt;服务端 Handler 处理完后，把结果&lt;strong&gt;带上请求 id&lt;/strong&gt; 写回；&lt;/li&gt;
&lt;li&gt;客户端 &lt;code&gt;channelRead&lt;/code&gt; 收到响应，按 id 找到对应的 Future，设置结果并唤醒主线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书中的思考题也很有意思：示例里客户端输出是有序的（按 id 逐个 get），&lt;strong&gt;如何修改代码让输出无序？&lt;/strong&gt;——答案是不再按顺序 get，而是在响应到达的回调里直接输出。这个小实验能帮你真正理解&amp;quot;异步 + 回调&amp;quot;与&amp;quot;同步等待&amp;quot;的差别。&lt;/p&gt;
&lt;h2 id=&#34;二原理部分绕不开的六块基石第-2-章&#34;&gt;二、原理部分：绕不开的六块基石（第 2 章）&lt;/h2&gt;
&lt;h3 id=&#34;21-多路复用器selectpollepoll&#34;&gt;2.1 多路复用器：select、poll、epoll&lt;/h3&gt;
&lt;p&gt;BIO 时代每个 Socket 连接要一条线程同步阻塞，1 万连接就要 1 万条线程，根本扛不住。NIO 的**多路复用器（Selector）**让一条线程可以管理成千上万个连接的接入和读写事件。&lt;/p&gt;
&lt;p&gt;三种经典模型的区别（面试高频）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模型&lt;/th&gt;
					&lt;th&gt;遍历方式&lt;/th&gt;
					&lt;th&gt;连接数上限&lt;/th&gt;
					&lt;th&gt;触发方式&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;select&lt;/td&gt;
					&lt;td&gt;轮询全部 fd&lt;/td&gt;
					&lt;td&gt;1024 左右&lt;/td&gt;
					&lt;td&gt;水平触发&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;poll&lt;/td&gt;
					&lt;td&gt;轮询全部 fd&lt;/td&gt;
					&lt;td&gt;无硬上限&lt;/td&gt;
					&lt;td&gt;水平触发&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;epoll&lt;/td&gt;
					&lt;td&gt;回调就绪列表，只处理活跃连接&lt;/td&gt;
					&lt;td&gt;无硬上限&lt;/td&gt;
					&lt;td&gt;支持边缘触发&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个容易混淆的点：&lt;strong&gt;JDK 自带的 epoll 封装是水平触发（LT）的，而 Netty 通过 JNI 自己重写的 epoll 是边缘触发（ET）的&lt;/strong&gt;，这也是 Netty 在 Linux 上推荐 EpollEventLoop 的原因之一。&lt;/p&gt;
&lt;h3 id=&#34;22-netty-的线程模型&#34;&gt;2.2 Netty 的线程模型&lt;/h3&gt;
&lt;p&gt;Netty 的 Boss/Worker 默认线程数与 CPU 核数挂钩（默认 核数 × 2）。Boss 只关心 &lt;code&gt;OP_ACCEPT&lt;/code&gt;，接收连接后把 &lt;code&gt;SocketChannel&lt;/code&gt; 按策略轮询注册到某个 Worker 的 Selector 上。关键约束：&lt;strong&gt;一个 Channel 在其生命周期内只绑定一个 EventLoop 线程&lt;/strong&gt;，所以处理 Channel 的所有事件都不需要加锁——这是 Netty 高并发能力的重要来源。&lt;/p&gt;
&lt;h3 id=&#34;23-编码和解码tcp-粘包半包问题&#34;&gt;2.3 编码和解码：TCP 粘包/半包问题&lt;/h3&gt;
&lt;p&gt;TCP 是面向字节流的协议，它把数据看成无结构的字节流，根本不知道&amp;quot;一条消息&amp;quot;的边界在哪。于是产生了经典问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;粘包&lt;/strong&gt;：两条消息被合并成一次发送/接收；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;半包（拆包）&lt;/strong&gt;：一条消息被拆成多次接收。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 发送方
        A[&amp;#34;业务对象&amp;#34;] --&amp;gt; B[&amp;#34;编码器 Encoder&amp;lt;br/&amp;gt;对象 → 字节&amp;#34;]
        B --&amp;gt; C[&amp;#34;TCP 输出缓冲区&amp;lt;br/&amp;gt;SO_SNDBUF&amp;#34;]
    end
    C --&amp;gt;|&amp;#34;② TCP 网络传输&amp;lt;br/&amp;gt;（受滑动窗口/拥塞控制影响）&amp;#34;| D[&amp;#34;TCP 输入缓冲区&amp;lt;br/&amp;gt;SO_RCVBUF&amp;#34;]
    subgraph 接收方
        D --&amp;gt; E[&amp;#34;解码器 Decoder&amp;lt;br/&amp;gt;处理粘包/半包&amp;#34;]
        E --&amp;gt; F[&amp;#34;业务对象&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;Netty 的解法是在 Pipeline 里插&lt;strong&gt;解码器&lt;/strong&gt;：固定长度解码器、分隔符解码器、&lt;strong&gt;长度字段解码器（LengthFieldBasedFrameDecoder，最常用）&lt;/strong&gt;——协议头里放一个长度字段，解码器攒够一条完整消息才往下游传。书中第 3 章手写 RPC 时就是用自定义协议（协议头 + 长度 + 内容）解决的。&lt;/p&gt;
&lt;h3 id=&#34;24-序列化protobuf-与-kryo&#34;&gt;2.4 序列化：Protobuf 与 Kryo&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protobuf&lt;/strong&gt;（Google）：跨语言、压缩率高（相同数据比 JSON 小 3~5 倍）、需要预定义 schema，适合 RPC 服务间通信；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kryo&lt;/strong&gt;：专注 Java，使用简单、速度快，但不跨语言，适合 Java 生态内部。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;25-零拷贝少搬一次数据是一次&#34;&gt;2.5 零拷贝：少搬一次数据是一次&lt;/h3&gt;
&lt;p&gt;零拷贝指操作数据时无须把 Buffer 从一个内存区域拷贝到另一个区域。Netty 里主要有三种场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;堆外直接内存收发&lt;/strong&gt;：用堆内存做 Socket 读写时，JVM 会先把堆内数据拷贝到堆外再写入 Socket；Netty 收发直接使用堆外内存，省掉这次拷贝；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CompositeByteBuf 组合视图&lt;/strong&gt;：一条消息被拆成多个数据包时，把多个 Buffer 逻辑上组合成一个（类似数据库视图），而不是物理拷贝拼接；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FileChannel.transferTo()&lt;/strong&gt;：文件传输直接在内核空间完成，不经用户态缓冲区。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;26-背压backpressure让快生产者等一等慢消费者&#34;&gt;2.6 背压（Backpressure）：让快生产者等一等慢消费者&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 生产者
        P1[&amp;#34;消息生产者 A&amp;lt;br/&amp;gt;写入缓存队列&amp;#34;]
        P2[&amp;#34;消息生产者 B&amp;lt;br/&amp;gt;写入缓存队列&amp;#34;]
    end
    P1 &amp;amp; P2 --&amp;gt; Q[&amp;#34;TCP 链路&amp;#34;]
    Q --&amp;gt;|&amp;#34;背压&amp;#34;| C[&amp;#34;消费者&amp;lt;br/&amp;gt;I/O 读取放入缓存队列&amp;#34;]
    C -.-&amp;gt;|&amp;#34;消费速率 &amp;lt; 生产速率时&amp;lt;br/&amp;gt;TCP 窗口缩小 → 通道阻塞&amp;lt;br/&amp;gt;→ 生产者降速&amp;#34;| P1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;机制链条：消费者消费变慢 → TCP 接收缓冲区满 → &lt;strong&gt;TCP 滑动窗口&lt;/strong&gt;变小 → 生产者发送缓冲区写不进去 → 生产者降速。书中还以 &lt;strong&gt;Flink 为例&lt;/strong&gt;讲了它的应用：Flink 基于 Netty 实现了 credit-based（基于信用额度）的反压机制，下游告诉上游&amp;quot;我还能收多少&amp;quot;，从根本上防止快生产慢消费把系统压垮。&lt;/p&gt;
&lt;h2 id=&#34;三分布式-rpc把-netty-组装成一个-dubbo第-3-章&#34;&gt;三、分布式 RPC：把 Netty 组装成一个 Dubbo（第 3 章）&lt;/h2&gt;
&lt;p&gt;这一章是全书第一个大项目：手写一套分布式 RPC。目标是业务方用起来像 SpringMVC/Dubbo 一样——引个 jar 包就行，不用关心 Netty 底层；服务端可动态扩容，不用写死 IP 端口。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 客户端
        A[&amp;#34;业务代码&amp;lt;br/&amp;gt;像调用本地方法一样调用&amp;#34;] --&amp;gt; B[&amp;#34;动态代理&amp;lt;br/&amp;gt;拦截方法调用&amp;#34;]
        B --&amp;gt; C[&amp;#34;协议封装&amp;lt;br/&amp;gt;类名/方法名/参数 + 请求id&amp;#34;]
    end
    subgraph 网络层
        C --&amp;gt; D[&amp;#34;Netty 长连接&amp;lt;br/&amp;gt;自定义协议 + Kryo 序列化&amp;#34;]
    end
    subgraph 服务端
        D --&amp;gt; E[&amp;#34;Netty Server Handler&amp;lt;br/&amp;gt;解码出调用信息&amp;#34;]
        E --&amp;gt; F[&amp;#34;Java 反射&amp;lt;br/&amp;gt;调用 Spring 容器中的实现类&amp;#34;]
    end
    subgraph 注册中心[&amp;#34;Zookeeper 注册中心&amp;#34;]
        G[&amp;#34;服务启动时注册&amp;lt;br/&amp;gt;IP/端口/权重&amp;#34;]
        H[&amp;#34;客户端订阅服务列表&amp;lt;br/&amp;gt;负载均衡选择节点&amp;#34;]
    end
    F --&amp;gt; G
    C --&amp;gt; H&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个关键技术点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;协议设计&lt;/strong&gt;：自定义协议头（魔数 + 长度 + 请求 id）+ 序列化内容，配合 LengthField 解码器解决粘包；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Java 反射解耦&lt;/strong&gt;：服务端 Handler 解析出&amp;quot;类名 + 方法名 + 参数类型 + 参数值&amp;quot;，从 Spring 容器找到实现类反射调用——业务代码和 Netty 的 Handler 逻辑完全解耦；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务注册与发现&lt;/strong&gt;：引入 Zookeeper（对比了 Consul/ETCD/Eureka），服务端启动注册临时节点，客户端监听节点变化做动态感知和负载均衡（可按权重）。这一层替代了传统 Nginx/LVS 的硬负载方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;四核心组件源码剖析第-4-章全书的硬核部分&#34;&gt;四、核心组件源码剖析（第 4 章，全书的硬核部分）&lt;/h2&gt;
&lt;h3 id=&#34;41-nioeventloopgroup-与-nioeventloopnetty-的发动机&#34;&gt;4.1 NioEventLoopGroup 与 NioEventLoop：Netty 的发动机&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;NioEventLoopGroup&lt;/code&gt; 就是线程池，&lt;code&gt;NioEventLoop&lt;/code&gt; 是里面的每条线程，&lt;strong&gt;每条线程一个 Selector，绑定一堆 Channel&lt;/strong&gt;。核心在它的 &lt;code&gt;run()&lt;/code&gt; 方法——一个永不退出的循环：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;run() 死循环开始&amp;#34;] --&amp;gt; B[&amp;#34;select() 轮询 I/O 事件&amp;lt;br/&amp;gt;（带超时，防止空轮询 bug）&amp;#34;]
    B --&amp;gt; C{&amp;#34;有就绪事件？&amp;#34;}
    C -- 是 --&amp;gt; D[&amp;#34;处理 SelectionKey&amp;lt;br/&amp;gt;OP_ACCEPT / OP_READ / OP_WRITE&amp;#34;]
    C -- 否 --&amp;gt; E[&amp;#34;跳过 I/O 处理&amp;#34;]
    D --&amp;gt; F[&amp;#34;执行异步任务队列&amp;lt;br/&amp;gt;（用户提交的任务 + 定时任务）&amp;#34;]
    E --&amp;gt; F
    F --&amp;gt;|&amp;#34;ioRatio 控制 I/O 与任务的时间配比&amp;lt;br/&amp;gt;默认各 50%&amp;#34;| A
    B -.-&amp;gt;|&amp;#34;连续空轮询超过阈值&amp;lt;br/&amp;gt;→ rebuildSelector() 重建 Selector&amp;lt;br/&amp;gt;（规避 JDK epoll 空轮询 bug）&amp;#34;| B&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;源码里的几个精妙优化，作者都点了出来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SelectedSelectionKeySet&lt;/strong&gt;：用数组替换 JDK Selector 内部的 HashSet，遍历就绪事件从哈希遍历变成数组遍历，更快；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selector 重建&lt;/strong&gt;：JDK epoll 有著名的&amp;quot;空轮询 bug&amp;quot;（select 立刻返回但无事件，CPU 100%），Netty 的解法是统计空轮询次数，超阈值就新建一个 Selector 把所有 Channel 迁过去；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ioRatio&lt;/strong&gt;：I/O 处理与异步任务执行的时间比例可调，默认 50:50。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-channel-的继承体系每层抽象都有明确职责&#34;&gt;4.2 Channel 的继承体系：每层抽象都有明确职责&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;Channel 接口&amp;lt;br/&amp;gt;id/eventLoop/pipeline/config&amp;#34;] --&amp;gt; B[&amp;#34;AbstractChannel&amp;lt;br/&amp;gt;模板方法：bind/connect/read/write&amp;lt;br/&amp;gt;持有 Unsafe 和 Pipeline&amp;#34;]
    B --&amp;gt; C[&amp;#34;AbstractNioChannel&amp;lt;br/&amp;gt;对接 NIO：SelectionKey&amp;lt;br/&amp;gt;interestOps/readInterestOp&amp;#34;]
    C --&amp;gt; D[&amp;#34;AbstractNioByteChannel&amp;lt;br/&amp;gt;字节流读写（TCP 数据通道）&amp;#34;]
    C --&amp;gt; E[&amp;#34;AbstractNioMessageChannel&amp;lt;br/&amp;gt;消息读写（接收连接用）&amp;#34;]
    D --&amp;gt; F[&amp;#34;NioSocketChannel&amp;lt;br/&amp;gt;客户端连接通道&amp;lt;br/&amp;gt;doRead/doWrite 具体实现&amp;#34;]
    E --&amp;gt; G[&amp;#34;NioServerSocketChannel&amp;lt;br/&amp;gt;服务端监听通道&amp;lt;br/&amp;gt;doReadMessages = accept 新连接&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;读源码时最值得体会的设计：I/O 操作全部收敛到内部类 &lt;strong&gt;Unsafe&lt;/strong&gt; 里，把&amp;quot;Netty 的 Channel API&amp;quot;和&amp;quot;JDK 底层的 Socket 操作&amp;quot;隔离开；&lt;code&gt;doXxx()&lt;/code&gt; 抽象方法留给子类实现，&lt;code&gt;AbstractChannel&lt;/code&gt; 用模板方法串起流程。书中还点名了 Netty 的性能优化手段：JDK 的 &lt;code&gt;Unsafe&lt;/code&gt;、乐观锁、对象池等。&lt;/p&gt;
&lt;h3 id=&#34;43-bytebuf比-nio-bytebuffer-好用得多的缓冲区&#34;&gt;4.3 ByteBuf：比 NIO ByteBuffer 好用得多的缓冲区&lt;/h3&gt;
&lt;p&gt;JDK 的 &lt;code&gt;ByteBuffer&lt;/code&gt; 只有一个 position 指针，读写模式要 &lt;code&gt;flip()&lt;/code&gt; 来回切换，极易出错。Netty 的 &lt;code&gt;ByteBuf&lt;/code&gt; 用&lt;strong&gt;读写分离的双指针&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph ByteBuf[&amp;#34;ByteBuf 内部结构&amp;#34;]
        A[&amp;#34;[0, readerIndex)&amp;lt;br/&amp;gt;已读区域（可丢弃）&amp;#34;] --&amp;gt; B[&amp;#34;[readerIndex, writerIndex)&amp;lt;br/&amp;gt;可读区域 readableBytes&amp;#34;]
        B --&amp;gt; C[&amp;#34;[writerIndex, capacity)&amp;lt;br/&amp;gt;可写区域 writableBytes&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;继承体系（每层干一件事）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AbstractByteBuf&lt;/code&gt;：读写索引维护、边界检查；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AbstractReferenceCountedByteBuf&lt;/code&gt;：&lt;strong&gt;引用计数&lt;/strong&gt;（&lt;code&gt;retain()&lt;/code&gt;/&lt;code&gt;release()&lt;/code&gt;），配合 &lt;code&gt;ReferenceCountUpdater&lt;/code&gt; 用 CAS 乐观锁更新计数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CompositeByteBuf&lt;/code&gt;：多个 Buffer 的逻辑组合（零拷贝场景 2）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PooledByteBuf&lt;/code&gt;：池化缓冲区，用完归还对象池复用，是第 6 章内存管理的入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;44-内存泄漏检测弱引用--采样的巧思&#34;&gt;4.4 内存泄漏检测：弱引用 + 采样的巧思&lt;/h3&gt;
&lt;p&gt;堆外内存不归 GC 管，忘了 &lt;code&gt;release()&lt;/code&gt; 就泄漏。Netty 的 &lt;code&gt;ResourceLeakDetector&lt;/code&gt; 机制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个 ByteBuf 创建时（按级别&lt;strong&gt;采样&lt;/strong&gt;，不是全量——开销可控）包装成弱引用/虚引用，记录创建时的调用栈；&lt;/li&gt;
&lt;li&gt;ByteBuf 被 GC 时，若引用计数没归零，说明泄漏了；&lt;/li&gt;
&lt;li&gt;从引用队列中捞出泄漏记录，打印带调用栈的告警日志。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个机制本身就是一节生动的 JVM 引用类型教学课——书中还配了弱引用的 JUnit 测试用例帮助理解。&lt;/p&gt;
&lt;h2 id=&#34;五读写请求源码剖析一次-io-的完整旅程第-5-章&#34;&gt;五、读/写请求源码剖析：一次 I/O 的完整旅程（第 5 章）&lt;/h2&gt;
&lt;h3 id=&#34;51-serverbootstrap-启动过程&#34;&gt;5.1 ServerBootstrap 启动过程&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;bind()&lt;/code&gt; 背后是：反射创建 &lt;code&gt;NioServerSocketChannel&lt;/code&gt; → 初始化 Pipeline（插入 &lt;code&gt;ServerBootstrapAcceptor&lt;/code&gt;）→ 注册到 Boss EventLoop → 绑定端口 → 监听 &lt;code&gt;OP_ACCEPT&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;52-接入与读写两个-unsafe-的分工&#34;&gt;5.2 接入与读写：两个 Unsafe 的分工&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant Client as 客户端
    participant Boss as Boss线程(NioMessageUnsafe)
    participant Acceptor as ServerBootstrapAcceptor
    participant Worker as Worker线程(NioByteUnsafe)
    participant Handler as 业务Handler链

    Client-&amp;gt;&amp;gt;Boss: 发起 TCP 连接
    Boss-&amp;gt;&amp;gt;Boss: 监听到 OP_ACCEPT&amp;lt;br/&amp;gt;accept 出 SocketChannel
    Boss-&amp;gt;&amp;gt;Acceptor: fireChannelRead(新连接)
    Acceptor-&amp;gt;&amp;gt;Worker: 设置 options/attrs&amp;lt;br/&amp;gt;注册到 Worker 的 Selector
    Client-&amp;gt;&amp;gt;Worker: 发送数据
    Worker-&amp;gt;&amp;gt;Worker: 监听到 OP_READ&amp;lt;br/&amp;gt;分配 ByteBuf 循环读
    Worker-&amp;gt;&amp;gt;Handler: fireChannelRead(msg)&amp;lt;br/&amp;gt;沿 Pipeline 传播&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Boss 侧用 &lt;strong&gt;NioMessageUnsafe&lt;/strong&gt;（读出来的是&amp;quot;一条消息&amp;quot;= 一个新连接）；Worker 侧用 &lt;strong&gt;NioByteUnsafe&lt;/strong&gt;（读出来的是字节流）——这就是第 4 章两个 AbstractNio 子类分工的实际体现；&lt;/li&gt;
&lt;li&gt;OP_READ 是&lt;strong&gt;循环读&lt;/strong&gt;：一次事件里反复读直到读空，每轮用 &lt;code&gt;RecvByteBufAllocator&lt;/code&gt; 预测该分配多大的缓冲区（第 6 章细讲）；&lt;/li&gt;
&lt;li&gt;写操作先进 &lt;strong&gt;ChannelOutboundBuffer&lt;/strong&gt; 链表（Entry 节点用完回收进对象池），&lt;code&gt;flush0()&lt;/code&gt; 时交给 &lt;code&gt;NioSocketChannel.doWrite()&lt;/code&gt; 批量写（合并成 &lt;code&gt;NIO_BUFFERS&lt;/code&gt; 数组一次 writev），写不完（TCP 缓冲区满）则注册 &lt;code&gt;OP_WRITE&lt;/code&gt; 等可写时再续写——这就是&lt;strong&gt;写半包&lt;/strong&gt;的处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;六内存管理jemalloc-思想在-java-的落地第-6-章最难也最值&#34;&gt;六、内存管理：jemalloc 思想在 Java 的落地（第 6 章，最难也最值）&lt;/h2&gt;
&lt;p&gt;为什么 Netty 要自己管内存？堆外内存的 &lt;code&gt;malloc/free&lt;/code&gt; 系统调用很贵，频繁申请释放是性能杀手。Netty 把 &lt;strong&gt;jemalloc&lt;/strong&gt; 的分配思想搬到了 Java 里，三层结构：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 三层结构[&amp;#34;内存分配的三层结构&amp;#34;]
        A[&amp;#34;① PoolThreadCache&amp;lt;br/&amp;gt;本地线程缓存&amp;lt;br/&amp;gt;每个 I/O 线程一个，免锁&amp;#34;]
        B[&amp;#34;② PoolArena&amp;lt;br/&amp;gt;分配区（arena）&amp;lt;br/&amp;gt;管理一组 PoolChunk&amp;#34;]
        C[&amp;#34;③ 系统内存&amp;lt;br/&amp;gt;堆外/堆内直接申请&amp;#34;]
    end
    A --&amp;gt;|&amp;#34;缓存命中就直接用&amp;lt;br/&amp;gt;不命中向下申请&amp;#34;| B
    B --&amp;gt;|&amp;#34;没有可用 chunk 才申请&amp;#34;| C
    D[&amp;#34;PoolChunk（默认 16MB）&amp;lt;br/&amp;gt;= 2048 个 page（8KB）&amp;#34;] -.属于.- B
    E[&amp;#34;PoolSubpage&amp;lt;br/&amp;gt;把 page 再切成 tiny/small 小块&amp;#34;] -.切分.- D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;分配策略（申请一块内存时）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先查当前线程的 &lt;code&gt;PoolThreadCache&lt;/code&gt;，命中直接返回——&lt;strong&gt;无线程竞争，无锁&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;不命中则找 &lt;code&gt;PoolArena&lt;/code&gt; 分配：大于等于 8KB 走 &lt;code&gt;PoolChunk&lt;/code&gt; 按 page 分配；小于 8KB 走 &lt;code&gt;PoolSubpage&lt;/code&gt; 把一个 page 切成更小的块（tiny/small 规格）；&lt;/li&gt;
&lt;li&gt;Arena 也分不到，才向系统申请新 chunk（默认 16MB），用完放回 Arena 重复利用；&lt;/li&gt;
&lt;li&gt;超大内存（超过 chunk 大小）不池化，直接申请、用完直接回收。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;PoolChunk&lt;/code&gt; 内部用&lt;strong&gt;完全二叉树&lt;/strong&gt;管理 2048 个 page：分配 ≥8KB 时在树上找深度合适的连续节点；&lt;code&gt;PoolSubpage&lt;/code&gt; 用位图（bitmap）记录小块占用。&lt;code&gt;PoolArena&lt;/code&gt; 里维护按使用率分档的 chunk 链表（qInit/q000/q025/q050/q075/q100），让分配尽量集中在少数 chunk 上，提高内存利用率。&lt;/p&gt;
&lt;p&gt;最后一块拼图是 &lt;code&gt;RecvByteBufAllocator&lt;/code&gt;（配合第 5 章的 OP_READ 循环读）：&lt;strong&gt;AdaptiveRecvByteBufAllocator 会自适应预测&lt;/strong&gt;——这次实际读了多少，下次就按这个量级分配缓冲区（默认从 1KB 起步，在 64B ~ 64KB 之间动态调整），避免&amp;quot;每次固定分配 64KB 却只读到 16B&amp;quot;的浪费。&lt;/p&gt;
&lt;p&gt;学习建议（书中原话）：这部分涉及大量多线程并发和位移与或非操作，&lt;strong&gt;把难以理解的代码贴到测试类里单独调试&lt;/strong&gt;是最有效的办法。&lt;/p&gt;
&lt;h2 id=&#34;七时间轮高级应用从心跳检测到-10-亿级任务监控第-7-章&#34;&gt;七、时间轮高级应用：从心跳检测到 10 亿级任务监控（第 7 章）&lt;/h2&gt;
&lt;h3 id=&#34;71-hashedwheeltimer-的原理&#34;&gt;7.1 HashedWheelTimer 的原理&lt;/h3&gt;
&lt;p&gt;IM 系统里有成千上万甚至百万条长连接链路，Netty 自带的心跳检测是给每条链路创建 &lt;code&gt;ScheduledFutureTask&lt;/code&gt; 定时任务——链路多了定时任务就爆炸。时间轮（HashedWheelTimer）是另一种方案：&lt;strong&gt;只用一条额外的 Worker 线程&lt;/strong&gt;，像钟表一样每隔一个 tick 走一格，只把当前格子的任务捞出来执行，没到的根本不碰。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 时间轮[&amp;#34;时间轮（wheelSize 个刻度）&amp;#34;]
        direction LR
        T0[&amp;#34;刻度 0&amp;lt;br/&amp;gt;task 链表&amp;#34;] --- T1[&amp;#34;刻度 1&amp;lt;br/&amp;gt;task 链表&amp;#34;] --- T2[&amp;#34;...&amp;#34;] --- TN[&amp;#34;刻度 N-1&amp;lt;br/&amp;gt;task 链表&amp;#34;]
    end
    P[&amp;#34;指针（tick）&amp;lt;br/&amp;gt;每 tickDuration 走一格&amp;#34;] --&amp;gt; T0
    W[&amp;#34;Worker 线程&amp;lt;br/&amp;gt;tick++&amp;lt;br/&amp;gt;落格 = tick &amp;amp; (wheelSize-1)&amp;#34;] -.驱动.- P&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;先思考三个问题（书中的引导方式很好）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;指针走一轮是多久？&lt;/strong&gt; tickDuration × wheelSize；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用什么容器存任务？&lt;/strong&gt; 每个刻度（bucket）一条链表；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务时间晚于一轮终点放哪？&lt;/strong&gt; 照样按落格公式放进去，任务上记录 &lt;strong&gt;round 数（还要等几圈）&lt;/strong&gt;，指针扫到它时 round 减 1，减到 0 才真正执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;7274-实战外卖订单超时监控系统&#34;&gt;7.2~7.4 实战：外卖订单超时监控系统&lt;/h3&gt;
&lt;p&gt;书中设计的应用场景非常接地气：外卖平台每天上千万订单，用户下单后在规定时间内无人处理就要告警（超时要被投诉、要赔偿）。类似的还有优惠券过期提醒、医院预约提醒——&lt;strong&gt;凡是&amp;quot;与未来一段时间相关&amp;quot;的业务都能用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但 10 亿级任务放内存里必然溢出，于是引出改造方案：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;业务系统&amp;lt;br/&amp;gt;（外卖订单等）&amp;#34;] --&amp;gt;|&amp;#34;上报监控数据&amp;#34;| K[&amp;#34;Kafka 数据源&amp;lt;br/&amp;gt;（处理能力弱时数据留在 Kafka，削峰）&amp;#34;]
    K --&amp;gt; B[&amp;#34;时间轮计算服务&amp;lt;br/&amp;gt;（分布式部署，每分钟百万级吞吐）&amp;#34;]
    B &amp;lt;--&amp;gt; R[&amp;#34;Redis&amp;lt;br/&amp;gt;任务数据按 时间轮节点wheel + 刻度idx 存储&amp;#34;]
    B --&amp;gt;|&amp;#34;到期且未处理（isOver=0）&amp;#34;| C[&amp;#34;告警输出&amp;lt;br/&amp;gt;回调 callBackUrl / 通知相关人员&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;设计要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据源选 Kafka&lt;/strong&gt;：数据量大、要监控&amp;quot;是否被及时处理&amp;quot;，Kafka 天然削峰；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务数据外置 Redis&lt;/strong&gt;：内存里只放时间轮的调度结构，任务内容放 Redis，用&amp;quot;时间轮节点名称 wheel + 刻度编号 idx&amp;quot;做 key 映射，还能直接从 Redis 捞数据做问题定位；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控数据通用化设计&lt;/strong&gt;：&lt;code&gt;dataId&lt;/code&gt;（唯一标识）、&lt;code&gt;sysCode&lt;/code&gt;（系统来源）、&lt;code&gt;detectionTime&lt;/code&gt;（检测时间）、&lt;code&gt;isOver&lt;/code&gt;（是否结束，相当于任务取消）、&lt;code&gt;isCycle&lt;/code&gt;/&lt;code&gt;cycleTime&lt;/code&gt;（支持周期性调度监控）、&lt;code&gt;callBackUrl&lt;/code&gt;（到期回调）——这套字段设计可以直接抄到自己的项目里。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;八问题分析与性能调优第-8-章&#34;&gt;八、问题分析与性能调优（第 8 章）&lt;/h2&gt;
&lt;h3 id=&#34;81-linux-部署&#34;&gt;8.1 Linux 部署&lt;/h3&gt;
&lt;p&gt;把第 3 章的 RPC 服务部署到 Linux：分环境配置文件（&lt;code&gt;server.properties&lt;/code&gt;：服务名、端口、Zookeeper 地址、权重，开发/测试/生产各一份）+ &lt;code&gt;PropertyUtil&lt;/code&gt; 工具类加载 + shell 启动脚本。&lt;/p&gt;
&lt;h3 id=&#34;82-jmeter-秒杀压测&#34;&gt;8.2 JMeter 秒杀压测&lt;/h3&gt;
&lt;p&gt;普通 JMeter TCP 脚本只能压一台服务器，也不用为此加 Nginx/LVS——书中选择的方式是&lt;strong&gt;把 RPC 客户端打成 jar 包嵌入 JMeter&lt;/strong&gt;（实现 &lt;code&gt;AbstractJavaSamplerClient&lt;/code&gt;），直接模拟长连接高并发调用分布式服务，观察 TPS、并发用户数、平均响应耗时。&lt;/p&gt;
&lt;h3 id=&#34;83-常见生产问题排查实战中真能救命的套路&#34;&gt;8.3 常见生产问题排查（实战中真能救命的套路）&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;排查方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;内存溢出&lt;strong&gt;一定会出现&lt;/strong&gt;（跑一段时间就炸）&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;jmap -histo&lt;/code&gt; 排序观察，看哪些对象增长快且不释放&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;内存溢出&lt;strong&gt;偶尔出现&lt;/strong&gt;（很久才发生）&lt;/td&gt;
					&lt;td&gt;启动参数加 &lt;code&gt;-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/xxx.hprof&lt;/code&gt;，溢出时自动快照，用 Eclipse MAT 分析&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;无溢出但响应慢、TPS 极低&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;top -H -p pid&lt;/code&gt; 找到 CPU 最高的线程 id → 转 16 进制小写 → &lt;code&gt;jstack -l pid | grep &#39;线程id&#39;&lt;/code&gt; 定位运行栈&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;84-性能调优三大方向&#34;&gt;8.4 性能调优三大方向&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Linux 系统参数&lt;/strong&gt;：压测常见的&amp;quot;文件句柄数过多&amp;quot;——全局改 &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; 加 &lt;code&gt;fs.file-max=1000000&lt;/code&gt;（支持百万连接），&lt;code&gt;sysctl -p&lt;/code&gt; 生效；局部改 &lt;code&gt;/etc/security/limits.conf&lt;/code&gt; 加 &lt;code&gt;* hard/soft nofile 1000000&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP 参数&lt;/strong&gt;：缓冲区大小、 backlog 队列等内核参数调整；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netty 应用层&lt;/strong&gt;：理解第 6 章后大胆用内存池减少堆外分配次数；用第 7 章的时间轮替换默认心跳检测，减轻 I/O 线程负担。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;书中一个坦诚的结论值得记住：&lt;strong&gt;无复杂业务逻辑时，Netty 不调优性能也已经很好&lt;/strong&gt;——调优的价值在于真实业务（数据库操作、远程调用混入 Service 层）出现瓶颈时，你知道从哪里下手。&lt;/p&gt;
&lt;h2 id=&#34;九全书知识地图与结语&#34;&gt;九、全书知识地图与结语&lt;/h2&gt;
&lt;p&gt;把全书浓缩成一张图：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((Netty))
    应用层
      长连接通信（第1章）
      分布式RPC（第3章 协议+反射+ZK）
      时间轮（第7章 心跳/10亿级任务监控）
    原理层
      多路复用器 epoll/select
      Reactor线程模型 Boss/Worker
      编解码与粘包拆包
      序列化 Protobuf/Kryo
      零拷贝
      背压 TCP窗口/Flink
    源码层
      NioEventLoop run（）循环
      Channel继承体系/Unsafe
      ByteBuf 双指针/引用计数
      内存泄漏检测 弱引用+采样
      读写流程 accept/read/write半包
    内存层
      PoolThreadCache 线程缓存
      PoolArena/PoolChunk 16MB
      PoolSubpage 小块位图
      AdaptiveRecvByteBuf 自适应预测
    运维层
      Linux部署与句柄调优
      JMeter长连接压测
      jmap/MAT/jstack排查三板斧&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;最后给新人的学习路径建议（也是作者的阅读建议）：先跑通第 1 章的长连接示例和第 3 章的 RPC，建立&amp;quot;会用&amp;quot;的信心；再反复啃第 4~6 章源码（作者建议源码至少读 3 遍，直到对 &lt;code&gt;Channel&lt;/code&gt;、&lt;code&gt;Handler&lt;/code&gt;、&lt;code&gt;NioEventLoop&lt;/code&gt;、&lt;code&gt;ByteBuf&lt;/code&gt; 的各个方法都了如指掌）；最后用第 7、8 章的实战项目把知识串起来。Netty 涉及多线程、复杂数据结构与内存管理模型，读完它对并发编程能力的提升是肉眼可见的。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>设计模式全景图：六大原则、23 种 GoF 模式与 J2EE 模式</title>
      <link>https://ftmao.com/blog/posts/design-patterns-intro/</link>
      <pubDate>Tue, 28 May 2019 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/design-patterns-intro/</guid>
      <description>&lt;h1 id=&#34;设计模式全景图六大原则23-种-gof-模式与-j2ee-模式&#34;&gt;设计模式全景图：六大原则、23 种 GoF 模式与 J2EE 模式&lt;/h1&gt;
&lt;p&gt;设计模式是面向对象设计中反复出现问题的&lt;strong&gt;可复用解决方案&lt;/strong&gt;。掌握它们能够帮助我们写出更易扩展、更易维护、更清晰的代码。本文从六大设计原则出发，完整梳理 GoF 23 种设计模式以及 8 种 J2EE 模式，并配合类图、意图说明和 JDK/Spring 中的典型应用，帮助建立系统性的知识地图。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一设计模式的六大原则&#34;&gt;一、设计模式的六大原则&lt;/h2&gt;
&lt;h3 id=&#34;1-开闭原则open-close-principle&#34;&gt;1. 开闭原则（Open Close Principle）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;对扩展开放，对修改关闭。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当程序需要新增功能时，应通过增加新代码实现，而不是修改已有代码。核心手段是抽象化：使用接口和抽象类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[业务调用方] --&amp;gt; B[抽象接口]
    B --&amp;gt; C[旧实现]
    B --&amp;gt; D[新实现]
    style D fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;2-里氏代换原则liskov-substitution-principle&#34;&gt;2. 里氏代换原则（Liskov Substitution Principle）&lt;/h3&gt;
&lt;p&gt;任何基类可以出现的地方，子类一定可以出现。子类替换基类后，程序功能不应受影响。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Animal { +eat() }
    class Dog { +eat() }
    class Cat { +eat() }
    Animal &amp;lt;|-- Dog
    Animal &amp;lt;|-- Cat&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-依赖倒转原则dependence-inversion-principle&#34;&gt;3. 依赖倒转原则（Dependence Inversion Principle）&lt;/h3&gt;
&lt;p&gt;高层模块和低层模块都应依赖抽象；要针对接口编程，而不是针对实现编程。&lt;/p&gt;
&lt;h3 id=&#34;4-接口隔离原则interface-segregation-principle&#34;&gt;4. 接口隔离原则（Interface Segregation Principle）&lt;/h3&gt;
&lt;p&gt;使用多个职责单一的接口，优于使用一个庞大臃肿的接口，降低类之间的耦合。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[大接口] --&amp;gt;|拆分为| B[接口A]
    A --&amp;gt;|拆分为| C[接口B]
    A --&amp;gt;|拆分为| D[接口C]
    B --&amp;gt; E[实现类1]
    C --&amp;gt; F[实现类2]
    D --&amp;gt; G[实现类3]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;5-迪米特法则--最少知道原则demeter-principle&#34;&gt;5. 迪米特法则 / 最少知道原则（Demeter Principle）&lt;/h3&gt;
&lt;p&gt;一个实体应尽量少地与其他实体发生直接交互，降低模块耦合，提高独立性。&lt;/p&gt;
&lt;h3 id=&#34;6-合成复用原则composite-reuse-principle&#34;&gt;6. 合成复用原则（Composite Reuse Principle）&lt;/h3&gt;
&lt;p&gt;优先使用组合/聚合来复用功能，而不是继承。组合更灵活，耦合更低。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Car { -Engine engine; +run() }
    class Engine { +start() }
    Car *-- Engine : 组合&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;二设计模式分类概览&#34;&gt;二、设计模式分类概览&lt;/h2&gt;
&lt;p&gt;设计模式通常分为三大类：创建型、结构型、行为型。J2EE 模式则专注于企业应用和表示层。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((设计模式))
    创建型模式 5种
      工厂模式
      抽象工厂模式
      单例模式
      建造者模式
      原型模式
    结构型模式 8种
      适配器模式
      桥接模式
      过滤器/标准模式
      组合模式
      装饰器模式
      外观模式
      享元模式
      代理模式
    行为型模式 13种 
      责任链模式
      命令模式
      解释器模式
      迭代器模式
      中介者模式
      备忘录模式
      观察者模式
      状态模式
      空对象模式
      策略模式
      模板模式
      访问者模式
    J2EE模式 8种 
      MVC模式
      业务代表模式
      组合实体模式
      数据访问对象模式
      前端控制器模式
      拦截过滤器模式
      服务定位器模式
      传输对象模式&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;三创建型模式creational-patterns&#34;&gt;三、创建型模式（Creational Patterns）&lt;/h2&gt;
&lt;p&gt;创建型模式关注对象的创建过程，隐藏创建逻辑，使程序在判断需要创建哪些对象时更灵活。&lt;/p&gt;
&lt;h3 id=&#34;1-工厂模式factory-pattern&#34;&gt;1. 工厂模式（Factory Pattern）&lt;/h3&gt;
&lt;p&gt;定义一个创建对象的接口，让子类决定实例化哪一个类。工厂方法使类的实例化延迟到子类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Product { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +use() }
    class ConcreteProductA { +use() }
    class ConcreteProductB { +use() }
    class Creator { +factoryMethod() Product }
    class ConcreteCreatorA { +factoryMethod() Product }
    class ConcreteCreatorB { +factoryMethod() Product }
    Product &amp;lt;|.. ConcreteProductA
    Product &amp;lt;|.. ConcreteProductB
    Creator &amp;lt;|-- ConcreteCreatorA
    Creator &amp;lt;|-- ConcreteCreatorB
    Creator ..&amp;gt; Product
    ConcreteCreatorA ..&amp;gt; ConcreteProductA
    ConcreteCreatorB ..&amp;gt; ConcreteProductB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.text.DateFormat.getDateInstance()&lt;/code&gt;、&lt;code&gt;KeyGenerator.getInstance()&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;2-抽象工厂模式abstract-factory-pattern&#34;&gt;2. 抽象工厂模式（Abstract Factory Pattern）&lt;/h3&gt;
&lt;p&gt;提供一个创建一系列相关或相互依赖对象的接口，而无需指定它们具体的类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class AbstractFactory { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +createProductA() +createProductB() }
    class ConcreteFactory1 { +createProductA() +createProductB() }
    class ConcreteFactory2 { +createProductA() +createProductB() }
    class AbstractProductA
    class AbstractProductB
    AbstractFactory &amp;lt;|.. ConcreteFactory1
    AbstractFactory &amp;lt;|.. ConcreteFactory2
    ConcreteFactory1 ..&amp;gt; AbstractProductA
    ConcreteFactory1 ..&amp;gt; AbstractProductB
    ConcreteFactory2 ..&amp;gt; AbstractProductA
    ConcreteFactory2 ..&amp;gt; AbstractProductB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-单例模式singleton-pattern&#34;&gt;3. 单例模式（Singleton Pattern）&lt;/h3&gt;
&lt;p&gt;确保一个类只有一个实例，并提供一个全局访问点。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Singleton {
        -static Singleton instance
        -Singleton()
        +static getInstance() Singleton
    }&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Runtime.getRuntime()&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; 默认 Bean 作用域为单例。&lt;/p&gt;
&lt;h3 id=&#34;4-建造者模式builder-pattern&#34;&gt;4. 建造者模式（Builder Pattern）&lt;/h3&gt;
&lt;p&gt;将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Product { +partA +partB +partC }
    class Builder { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +buildPartA() +buildPartB() +getResult() }
    class ConcreteBuilder { +buildPartA() +buildPartB() +getResult() }
    class Director { +construct(builder) }
    Builder &amp;lt;|.. ConcreteBuilder
    ConcreteBuilder ..&amp;gt; Product
    Director ..&amp;gt; Builder&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;5-原型模式prototype-pattern&#34;&gt;5. 原型模式（Prototype Pattern）&lt;/h3&gt;
&lt;p&gt;通过复制现有对象来创建新对象，而不是新建实例。适用于创建成本较高的对象。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Prototype { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +clone() }
    class ConcretePrototype { +clone() }
    Prototype &amp;lt;|.. ConcretePrototype&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Object.clone()&lt;/code&gt;、&lt;code&gt;Cloneable&lt;/code&gt; 接口。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四结构型模式structural-patterns&#34;&gt;四、结构型模式（Structural Patterns）&lt;/h2&gt;
&lt;p&gt;结构型模式关注对象之间的组合和关系，解决如何构建灵活且可复用的类和对象结构。&lt;/p&gt;
&lt;h3 id=&#34;1-适配器模式adapter-pattern&#34;&gt;1. 适配器模式（Adapter Pattern）&lt;/h3&gt;
&lt;p&gt;将一个类的接口转换成客户希望的另外一个接口，使得原本由于接口不兼容而不能一起工作的类可以协同工作。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Target { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +request() }
    class Adapter { +request() }
    class Adaptee { +specificRequest() }
    Target &amp;lt;|.. Adapter
    Adapter --&amp;gt; Adaptee&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Arrays.asList()&lt;/code&gt;、&lt;code&gt;InputStreamReader&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;2-桥接模式bridge-pattern&#34;&gt;2. 桥接模式（Bridge Pattern）&lt;/h3&gt;
&lt;p&gt;将抽象部分与它的实现部分分离，使它们都可以独立地变化。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Abstraction { -Implementor impl +operation() }
    class RefinedAbstraction { +operation() }
    class Implementor { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +operationImpl() }
    class ConcreteImplementorA { +operationImpl() }
    class ConcreteImplementorB { +operationImpl() }
    Abstraction &amp;lt;|-- RefinedAbstraction
    Abstraction o-- Implementor
    Implementor &amp;lt;|.. ConcreteImplementorA
    Implementor &amp;lt;|.. ConcreteImplementorB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-过滤器模式--标准模式filter--criteria-pattern&#34;&gt;3. 过滤器模式 / 标准模式（Filter / Criteria Pattern）&lt;/h3&gt;
&lt;p&gt;使用不同的标准来过滤一组对象，并通过逻辑运算以解耦的方式把它们连接起来。常用于集合筛选。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Criteria { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +meetCriteria(list) }
    class CriteriaMale { +meetCriteria(list) }
    class CriteriaFemale { +meetCriteria(list) }
    class AndCriteria { +meetCriteria(list) }
    class OrCriteria { +meetCriteria(list) }
    Criteria &amp;lt;|.. CriteriaMale
    Criteria &amp;lt;|.. CriteriaFemale
    Criteria &amp;lt;|.. AndCriteria
    Criteria &amp;lt;|.. OrCriteria
    AndCriteria --&amp;gt; Criteria
    OrCriteria --&amp;gt; Criteria&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;4-组合模式composite-pattern&#34;&gt;4. 组合模式（Composite Pattern）&lt;/h3&gt;
&lt;p&gt;将对象组合成树形结构以表示&amp;quot;部分-整体&amp;quot;的层次结构，使得用户对单个对象和组合对象的使用具有一致性。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Component { +operation() }
    class Leaf { +operation() }
    class Composite { -children +add() +remove() +operation() }
    Component &amp;lt;|-- Leaf
    Component &amp;lt;|-- Composite
    Composite o-- Component&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.awt.Container&lt;/code&gt; 与 &lt;code&gt;Component&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;5-装饰器模式decorator-pattern&#34;&gt;5. 装饰器模式（Decorator Pattern）&lt;/h3&gt;
&lt;p&gt;动态地给一个对象添加一些额外的职责，比生成子类更灵活。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Component { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +operation() }
    class ConcreteComponent { +operation() }
    class Decorator { -Component component +operation() }
    class ConcreteDecoratorA { +operation() }
    class ConcreteDecoratorB { +operation() }
    Component &amp;lt;|.. ConcreteComponent
    Component &amp;lt;|.. Decorator
    Decorator &amp;lt;|-- ConcreteDecoratorA
    Decorator &amp;lt;|-- ConcreteDecoratorB
    Decorator o-- Component&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; Java IO 流 &lt;code&gt;BufferedInputStream&lt;/code&gt;、&lt;code&gt;DataInputStream&lt;/code&gt;、&lt;code&gt;ZipOutputStream&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;6-外观模式facade-pattern&#34;&gt;6. 外观模式（Facade Pattern）&lt;/h3&gt;
&lt;p&gt;为子系统中的一组接口提供一个一致的入口，定义了一个高层接口，使子系统更容易使用。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Facade { +simplifiedOperation() }
    class SubSystemA { +operationA() }
    class SubSystemB { +operationB() }
    class SubSystemC { +operationC() }
    Facade --&amp;gt; SubSystemA
    Facade --&amp;gt; SubSystemB
    Facade --&amp;gt; SubSystemC&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;7-享元模式flyweight-pattern&#34;&gt;7. 享元模式（Flyweight Pattern）&lt;/h3&gt;
&lt;p&gt;运用共享技术有效地支持大量细粒度对象，减少内存占用。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class FlyweightFactory { -pool +getFlyweight(key) }
    class Flyweight { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +operation() }
    class ConcreteFlyweight { +operation() }
    FlyweightFactory o-- Flyweight
    Flyweight &amp;lt;|.. ConcreteFlyweight&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Integer.valueOf()&lt;/code&gt; 缓存（-128-127）、&lt;code&gt;String&lt;/code&gt; 常量池。&lt;/p&gt;
&lt;h3 id=&#34;8-代理模式proxy-pattern&#34;&gt;8. 代理模式（Proxy Pattern）&lt;/h3&gt;
&lt;p&gt;为其他对象提供一种代理以控制对这个对象的访问。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Subject { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +request() }
    class RealSubject { +request() }
    class Proxy { -RealSubject real +request() }
    Subject &amp;lt;|.. RealSubject
    Subject &amp;lt;|.. Proxy
    Proxy --&amp;gt; RealSubject&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; JDK 动态代理 &lt;code&gt;Proxy.newProxyInstance()&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; AOP 使用 JDK 动态代理和 CGLIB。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五行为型模式behavioral-patterns&#34;&gt;五、行为型模式（Behavioral Patterns）&lt;/h2&gt;
&lt;p&gt;行为型模式关注对象之间的通信和交互，解决对象之间的责任分配和算法封装。&lt;/p&gt;
&lt;h3 id=&#34;1-责任链模式chain-of-responsibility-pattern&#34;&gt;1. 责任链模式（Chain of Responsibility Pattern）&lt;/h3&gt;
&lt;p&gt;使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链，并沿着这条链传递请求。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Handler { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +setNext(handler) +handle(request) }
    class ConcreteHandlerA { +handle(request) }
    class ConcreteHandlerB { +handle(request) }
    Handler &amp;lt;|.. ConcreteHandlerA
    Handler &amp;lt;|.. ConcreteHandlerB
    Handler --&amp;gt; Handler : next&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;2-命令模式command-pattern&#34;&gt;2. 命令模式（Command Pattern）&lt;/h3&gt;
&lt;p&gt;将请求封装为对象，从而可以用不同的请求、队列或日志来参数化其他对象，同时支持可撤销操作。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Command { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +execute() }
    class ConcreteCommand { -receiver +execute() }
    class Invoker { -command +setCommand() +invoke() }
    class Receiver { +action() }
    Command &amp;lt;|.. ConcreteCommand
    Invoker --&amp;gt; Command
    ConcreteCommand --&amp;gt; Receiver&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-解释器模式interpreter-pattern&#34;&gt;3. 解释器模式（Interpreter Pattern）&lt;/h3&gt;
&lt;p&gt;给定一个语言，定义它的文法表示，并定义一个解释器，这个解释器使用该表示来解释语言中的句子。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class AbstractExpression { +interpret(context) }
    class TerminalExpression { +interpret(context) }
    class NonterminalExpression { +interpret(context) }
    AbstractExpression &amp;lt;|-- TerminalExpression
    AbstractExpression &amp;lt;|-- NonterminalExpression
    NonterminalExpression o-- AbstractExpression&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; Java 正则表达式 &lt;code&gt;Pattern&lt;/code&gt; 与 &lt;code&gt;Matcher&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;4-迭代器模式iterator-pattern&#34;&gt;4. 迭代器模式（Iterator Pattern）&lt;/h3&gt;
&lt;p&gt;提供一种方法顺序访问一个聚合对象中的各个元素，而又无需暴露该对象的内部表示。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Iterator { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +hasNext() +next() }
    class ConcreteIterator { +hasNext() +next() }
    class Aggregate { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +createIterator() }
    class ConcreteAggregate { +createIterator() }
    Iterator &amp;lt;|.. ConcreteIterator
    Aggregate &amp;lt;|.. ConcreteAggregate
    ConcreteAggregate ..&amp;gt; ConcreteIterator&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.util.Iterator&lt;/code&gt;、&lt;code&gt;java.util.Enumeration&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;5-中介者模式mediator-pattern&#34;&gt;5. 中介者模式（Mediator Pattern）&lt;/h3&gt;
&lt;p&gt;用一个中介对象来封装一系列对象交互，使对象不需要显式地相互引用，从而使其耦合松散。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Mediator { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +send(message, colleague) }
    class ConcreteMediator { +send(message, colleague) }
    class Colleague { -Mediator mediator }
    class ConcreteColleagueA
    class ConcreteColleagueB
    Mediator &amp;lt;|.. ConcreteMediator
    Colleague &amp;lt;|-- ConcreteColleagueA
    Colleague &amp;lt;|-- ConcreteColleagueB
    Colleague --&amp;gt; Mediator
    ConcreteMediator --&amp;gt; ConcreteColleagueA
    ConcreteMediator --&amp;gt; ConcreteColleagueB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;6-备忘录模式memento-pattern&#34;&gt;6. 备忘录模式（Memento Pattern）&lt;/h3&gt;
&lt;p&gt;在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，以便以后恢复。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Originator { +createMemento() +restore(memento) }
    class Memento { -state }
    class Caretaker { -mementos }
    Originator ..&amp;gt; Memento
    Caretaker o-- Memento&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;7-观察者模式observer-pattern&#34;&gt;7. 观察者模式（Observer Pattern）&lt;/h3&gt;
&lt;p&gt;定义对象间的一对多依赖关系，当一个对象状态发生改变时，所有依赖于它的对象都得到通知并自动更新。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Subject { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +registerObserver() +removeObserver() +notifyObservers() }
    class ConcreteSubject { -state +notifyObservers() }
    class Observer { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +update() }
    class ConcreteObserverA { +update() }
    class ConcreteObserverB { +update() }
    Subject &amp;lt;|.. ConcreteSubject
    Observer &amp;lt;|.. ConcreteObserverA
    Observer &amp;lt;|.. ConcreteObserverB
    ConcreteSubject --&amp;gt; Observer&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;java.util.Observer&lt;/code&gt;（已弃用）和 &lt;code&gt;Observable&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring 事件驱动模型 &lt;code&gt;ApplicationEvent&lt;/code&gt;、&lt;code&gt;ApplicationListener&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;8-状态模式state-pattern&#34;&gt;8. 状态模式（State Pattern）&lt;/h3&gt;
&lt;p&gt;允许对象在其内部状态改变时改变它的行为，对象看起来好像修改了它的类。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Context { -State state +request() }
    class State { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +handle() }
    class ConcreteStateA { +handle() }
    class ConcreteStateB { +handle() }
    Context o-- State
    State &amp;lt;|.. ConcreteStateA
    State &amp;lt;|.. ConcreteStateB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;9-空对象模式null-object-pattern&#34;&gt;9. 空对象模式（Null Object Pattern）&lt;/h3&gt;
&lt;p&gt;用一个空对象取代 &lt;code&gt;null&lt;/code&gt; 检查，提供默认的&amp;quot;什么也不做&amp;quot;的行为，避免空指针异常。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Animal { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +makeSound() }
    class Dog { +makeSound() }
    class NullAnimal { +makeSound() }
    Animal &amp;lt;|.. Dog
    Animal &amp;lt;|.. NullAnimal&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;10-策略模式strategy-pattern&#34;&gt;10. 策略模式（Strategy Pattern）&lt;/h3&gt;
&lt;p&gt;定义一系列算法，把它们一个个封装起来，并且使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Strategy { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +execute() }
    class ConcreteStrategyA { +execute() }
    class ConcreteStrategyB { +execute() }
    class Context { -Strategy strategy +setStrategy() +executeStrategy() }
    Strategy &amp;lt;|.. ConcreteStrategyA
    Strategy &amp;lt;|.. ConcreteStrategyB
    Context o-- Strategy&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Comparator&lt;/code&gt; 接口与 &lt;code&gt;Collections.sort()&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;11-模板模式template-pattern&#34;&gt;11. 模板模式（Template Pattern）&lt;/h3&gt;
&lt;p&gt;定义一个操作中的算法骨架，而将一些步骤延迟到子类中。模板方法使得子类可以不改变算法结构即可重定义该算法的某些特定步骤。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class AbstractClass { +templateMethod() +primitiveOperation() }
    class ConcreteClass { +primitiveOperation() }
    AbstractClass &amp;lt;|-- ConcreteClass&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 应用：&lt;/strong&gt; &lt;code&gt;Arrays.sort()&lt;/code&gt; 配合 &lt;code&gt;Comparable&lt;/code&gt;。&lt;strong&gt;Spring 应用：&lt;/strong&gt; &lt;code&gt;RestTemplate&lt;/code&gt;、&lt;code&gt;JmsTemplate&lt;/code&gt;、&lt;code&gt;JpaTemplate&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;12-访问者模式visitor-pattern&#34;&gt;12. 访问者模式（Visitor Pattern）&lt;/h3&gt;
&lt;p&gt;表示一个作用于某对象结构中的各元素的操作，它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class Element { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +accept(visitor) }
    class ConcreteElementA { +accept(visitor) }
    class ConcreteElementB { +accept(visitor) }
    class Visitor { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +visitA() +visitB() }
    class ConcreteVisitor { +visitA() +visitB() }
    Element &amp;lt;|.. ConcreteElementA
    Element &amp;lt;|.. ConcreteElementB
    Visitor &amp;lt;|.. ConcreteVisitor
    ConcreteElementA ..&amp;gt; Visitor
    ConcreteElementB ..&amp;gt; Visitor&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;六j2ee-模式&#34;&gt;六、J2EE 模式&lt;/h2&gt;
&lt;p&gt;J2EE 模式专注于企业应用，尤其是表示层和业务层的架构设计。这些模式由 Sun Java Center 鉴定。&lt;/p&gt;
&lt;h3 id=&#34;1-mvc-模式mvc-pattern&#34;&gt;1. MVC 模式（MVC Pattern）&lt;/h3&gt;
&lt;p&gt;将应用程序分为 Model（模型）、View（视图）、Controller（控制器）三层，实现关注点分离。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    V[View] --&amp;gt; C[Controller]
    C --&amp;gt; M[Model]
    M --&amp;gt; V&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring MVC 是典型实现。&lt;/p&gt;
&lt;h3 id=&#34;2-业务代表模式business-delegate-pattern&#34;&gt;2. 业务代表模式（Business Delegate Pattern）&lt;/h3&gt;
&lt;p&gt;为表示层提供对业务服务的访问抽象，减少表示层与业务层之间的耦合。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Client/Controller] --&amp;gt; B[Business Delegate]
    B --&amp;gt; C[Business Service]
    C --&amp;gt; D[DAO/数据层]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;3-组合实体模式composite-entity-pattern&#34;&gt;3. 组合实体模式（Composite Entity Pattern）&lt;/h3&gt;
&lt;p&gt;将一组相关的持久化对象组合成一个粗粒度实体，减少网络调用次数，提高性能。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class CompositeEntity { -DependentObjectA a -DependentObjectB b +getData() }
    class DependentObjectA
    class DependentObjectB
    CompositeEntity *-- DependentObjectA
    CompositeEntity *-- DependentObjectB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;4-数据访问对象模式data-access-object-pattern&#34;&gt;4. 数据访问对象模式（Data Access Object Pattern）&lt;/h3&gt;
&lt;p&gt;将底层数据访问逻辑与高层业务逻辑分离，提供一个统一的接口访问数据库。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Business Object] --&amp;gt; B[DAO Interface]
    B --&amp;gt; C[DAO Implementation]
    C --&amp;gt; D[Data Source/Database]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring Data JPA、MyBatis Mapper 都是 DAO 模式的具体实现。&lt;/p&gt;
&lt;h3 id=&#34;5-前端控制器模式front-controller-pattern&#34;&gt;5. 前端控制器模式（Front Controller Pattern）&lt;/h3&gt;
&lt;p&gt;提供一个集中的入口点来处理所有请求，通常负责认证、日志、路由等横切关注点。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[所有请求] --&amp;gt; B[Front Controller]
    B --&amp;gt; C[Dispatcher]
    C --&amp;gt; D[具体 Handler]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; &lt;code&gt;DispatcherServlet&lt;/code&gt; 是前端控制器模式的实现。&lt;/p&gt;
&lt;h3 id=&#34;6-拦截过滤器模式intercepting-filter-pattern&#34;&gt;6. 拦截过滤器模式（Intercepting Filter Pattern）&lt;/h3&gt;
&lt;p&gt;在请求到达目标处理器之前，通过一系列过滤器链对请求进行预处理（如编码、认证、日志）。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Request] --&amp;gt; F1[Filter 1]
    F1 --&amp;gt; F2[Filter 2]
    F2 --&amp;gt; F3[Filter N]
    F3 --&amp;gt; T[Target]
    T --&amp;gt; F3
    F3 --&amp;gt; F2
    F2 --&amp;gt; F1
    F1 --&amp;gt; R[Response]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Spring 应用：&lt;/strong&gt; Spring 的 &lt;code&gt;HandlerInterceptor&lt;/code&gt;、Servlet Filter。&lt;/p&gt;
&lt;h3 id=&#34;7-服务定位器模式service-locator-pattern&#34;&gt;7. 服务定位器模式（Service Locator Pattern）&lt;/h3&gt;
&lt;p&gt;提供一个中心化的注册表，用于获取服务实例，减少服务依赖的直接查找。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class ServiceLocator { +getService(name) }
    class Cache { -services }
    class InitialContext { +lookup(name) }
    class Service { &amp;lt;&amp;lt;interface&amp;gt;&amp;gt; +execute() }
    ServiceLocator --&amp;gt; Cache
    ServiceLocator --&amp;gt; InitialContext
    ServiceLocator ..&amp;gt; Service&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;8-传输对象模式transfer-object-pattern&#34;&gt;8. 传输对象模式（Transfer Object Pattern）&lt;/h3&gt;
&lt;p&gt;通过简单的 POJO 对象在不同层之间传输数据，减少远程调用的网络开销，也称为 DTO（Data Transfer Object）。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;classDiagram
    class UserDTO { -id -name -email +getters/setters }
    class UserService { +getUserDTO(id) }
    class UserController { +showUser(dto) }
    UserService ..&amp;gt; UserDTO
    UserController ..&amp;gt; UserDTO&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;七jdkjava-io-与-spring-中的模式速查&#34;&gt;七、JDK、Java IO 与 Spring 中的模式速查&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;JDK 示例&lt;/th&gt;
					&lt;th&gt;Spring 示例&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;工厂模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DateFormat.getDateInstance()&lt;/code&gt;、&lt;code&gt;KeyGenerator.getInstance()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;BeanFactory&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单例模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Runtime.getRuntime()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;默认 Bean 作用域&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;原型模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Object.clone()&lt;/code&gt;、&lt;code&gt;Cloneable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;@Scope(&amp;quot;prototype&amp;quot;)&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;适配器模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Arrays.asList()&lt;/code&gt;、&lt;code&gt;InputStreamReader&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;MVC 的 Controller 适配器&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;装饰器模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;BufferedInputStream&lt;/code&gt;、&lt;code&gt;ZipOutputStream&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;代理模式&lt;/td&gt;
					&lt;td&gt;JDK 动态代理&lt;/td&gt;
					&lt;td&gt;AOP&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;组合模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;java.awt.Container&lt;/code&gt; / &lt;code&gt;Component&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;享元模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Integer.valueOf()&lt;/code&gt;、&lt;code&gt;String&lt;/code&gt; 常量池&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;模板方法模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Arrays.sort()&lt;/code&gt; + &lt;code&gt;Comparable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;RestTemplate&lt;/code&gt;、&lt;code&gt;JpaTemplate&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;策略模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Comparator&lt;/code&gt;、&lt;code&gt;Collections.sort()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ResourceLoader&lt;/code&gt; 策略&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;观察者模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Observer&lt;/code&gt; / &lt;code&gt;Observable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Spring 事件驱动模型&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;解释器模式&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;Pattern&lt;/code&gt; / &lt;code&gt;Matcher&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;SpEL 表达式&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;责任链模式&lt;/td&gt;
					&lt;td&gt;Servlet Filter Chain&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;HandlerInterceptor&lt;/code&gt; 链&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MVC 模式&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
					&lt;td&gt;Spring MVC&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DAO 模式&lt;/td&gt;
					&lt;td&gt;JDBC Template&lt;/td&gt;
					&lt;td&gt;Spring Data JPA / MyBatis&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;前端控制器&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DispatcherServlet&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;java-io-中使用的模式&#34;&gt;Java IO 中使用的模式&lt;/h3&gt;
&lt;p&gt;Java IO 同时使用了&lt;strong&gt;适配器模式&lt;/strong&gt;和&lt;strong&gt;装饰器模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;适配器模式：&lt;/strong&gt; &lt;code&gt;InputStreamReader&lt;/code&gt; 将 &lt;code&gt;InputStream&lt;/code&gt; 字节流适配为 &lt;code&gt;Reader&lt;/code&gt; 字符流。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;装饰器模式：&lt;/strong&gt; &lt;code&gt;BufferedInputStream&lt;/code&gt;、&lt;code&gt;DataInputStream&lt;/code&gt;、&lt;code&gt;ZipOutputStream&lt;/code&gt; 等通过包装 &lt;code&gt;InputStream&lt;/code&gt; 动态增强功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 装饰器模式：逐层包装&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;InputStream fis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileInputStream(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;file.txt&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;InputStream bis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; BufferedInputStream(fis);     &lt;span style=&#34;color:#75715e&#34;&gt;// 增加缓冲&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DataInputStream dis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DataInputStream(bis);      &lt;span style=&#34;color:#75715e&#34;&gt;// 增加基本类型读取&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 适配器模式：字节流转字符流&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Reader reader &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; InputStreamReader(fis, StandardCharsets.&lt;span style=&#34;color:#a6e22e&#34;&gt;UTF_8&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;八如何选择设计模式&#34;&gt;八、如何选择设计模式&lt;/h2&gt;
&lt;p&gt;设计模式不是越复杂越好。选择时可以从以下角度思考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;问题类型&lt;/strong&gt;：是对象创建、结构组织，还是行为交互？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变化方向&lt;/strong&gt;：系统中哪些部分经常变化？把变化的部分封装起来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开闭原则&lt;/strong&gt;：新增功能时是否能不修改已有代码？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂度收益&lt;/strong&gt;：不要为了用模式而增加不必要的抽象。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一张简化的决策地图：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[遇到设计问题] --&amp;gt; B{对象创建复杂?}
    B --&amp;gt;|是| C[创建型模式]
    B --&amp;gt;|否| D{对象结构复杂?}
    D --&amp;gt;|是| E[结构型模式]
    D --&amp;gt;|否| F{对象交互复杂?}
    F --&amp;gt;|是| G[行为型模式]
    F --&amp;gt;|否| H[保持简单，遵循原则]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;设计模式是前人总结的设计经验，核心目标是让软件更易扩展、更易维护、更易理解。掌握它们的关键不是背诵每个类图，而是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;理解六大原则，它们是选择模式的判断依据；&lt;/li&gt;
&lt;li&gt;熟悉 23 种 GoF 模式和 J2EE 模式的适用场景；&lt;/li&gt;
&lt;li&gt;多读优秀源码（JDK、Spring），在真实代码中感受模式的应用；&lt;/li&gt;
&lt;li&gt;避免过度设计，合适才是最好的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从 &lt;code&gt;BeanFactory&lt;/code&gt; 到 &lt;code&gt;DispatcherServlet&lt;/code&gt;，从 Java IO 的装饰器到 Spring AOP 的代理，设计模式无处不在。理解这些模式，能帮助我们在面对复杂系统时做出更清晰的结构选择。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.runoob.com/design-pattern/factory-pattern.html&#34;&gt;菜鸟教程 - 设计模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/dailyprogrammer/articles/12272717.html&#34;&gt;dailyprogrammer - JDK 类库常用的设计模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Design Patterns: Elements of Reusable Object-Oriented Software&lt;/em&gt; (GoF)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
</description>
    </item>
    <item>
      <title>jphotoshop：用 Java Swing 手写一个 Photoshop——架构与算法全解析</title>
      <link>https://ftmao.com/blog/posts/jphotoshop-java-swing-photoshop-clone/</link>
      <pubDate>Fri, 18 May 2018 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/jphotoshop-java-swing-photoshop-clone/</guid>
      <description>&lt;h1 id=&#34;jphotoshop用-java-swing-手写一个-photoshop架构与算法全解析&#34;&gt;jphotoshop：用 Java Swing 手写一个 Photoshop——架构与算法全解析&lt;/h1&gt;
&lt;p&gt;把多年前的一个练手项目整理开源了：&lt;strong&gt;jphotoshop&lt;/strong&gt;，一个用纯 Java Swing 手写的 Photoshop 模拟器。不写不知道，写一个&amp;quot;简化版 PS&amp;quot;几乎把计算机图形学、数据结构和设计模式都过了一遍，这篇文章既是功能介绍，也是一次技术复盘。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源地址：&lt;a href=&#34;https://gitee.com/soulnew/jphotoshop&#34;&gt;https://gitee.com/soulnew/jphotoshop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;技术栈：Java + Swing（Swing Application Framework / JSR-296），NetBeans Ant 工程&lt;/li&gt;
&lt;li&gt;规模：197 个 Java 文件，约 1.8 万行（自研约 1.4 万行），开发周期约半年&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一功能概览一个简化版-ps能做什么&#34;&gt;一、功能概览：一个&amp;quot;简化版 PS&amp;quot;能做什么&lt;/h2&gt;
&lt;p&gt;UI 形态上和老版 PS 一致：&lt;strong&gt;MDI 多文档界面&lt;/strong&gt;——每张图片一个内部编辑窗口，图层面板、导航缩放面板都是可拖动的浮动内窗。&lt;/p&gt;
&lt;h3 id=&#34;11-图层系统&#34;&gt;1.1 图层系统&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;多图层编辑：图像图层、&lt;strong&gt;文字图层&lt;/strong&gt;、浮动选区层（拖动选区内容时的临时层）；&lt;/li&gt;
&lt;li&gt;图层面板带 40×30 &lt;strong&gt;实时缩略图&lt;/strong&gt;，支持拖拽换序（基于 Swing TransferHandler 的 DnD）、复制粘贴为新图层；&lt;/li&gt;
&lt;li&gt;透明区域用经典的灰白棋盘格打底。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;12-选区工具7-个&#34;&gt;1.2 选区工具（7 个）&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;工具&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;魔棒&lt;/td&gt;
					&lt;td&gt;容差可调（默认 23），支持多连通区域一次选中&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;色彩范围&lt;/td&gt;
					&lt;td&gt;全图相似色选取&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多边形套索&lt;/td&gt;
					&lt;td&gt;逐点勾勒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自由套索&lt;/td&gt;
					&lt;td&gt;鼠标轨迹勾勒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;磁性套索&lt;/td&gt;
					&lt;td&gt;局部窗口内吸附边缘（完成度不高）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;贝塞尔选区&lt;/td&gt;
					&lt;td&gt;双击闭合时自动做曲线拟合，生成平滑选区&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;移动工具&lt;/td&gt;
					&lt;td&gt;把选区内容提成浮动层，拖动实时合成，松手落回底层&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;选区还支持：&lt;strong&gt;羽化&lt;/strong&gt;（可调半径）、反选、平移/缩放（AffineTransform）、以及标志性的&lt;strong&gt;蚂蚁线动画&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;13-滤镜24-个分-5-大类&#34;&gt;1.3 滤镜（24 个，分 5 大类）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模糊&lt;/strong&gt;：均值平滑、高斯模糊（3×3 高斯核 &lt;code&gt;{1,2,1;2,4,2;1,2,1}/16&lt;/code&gt;）、中值滤波；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;锐化&lt;/strong&gt;：Sharpen / SharpenMore（拉普拉斯卷积核 &lt;code&gt;{0,-1,0;-1,5,-1;0,-1,0}&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;艺术&lt;/strong&gt;：曝光过度、冰晶、熔岩、暗调（平方衰减）、漫画等 8 种风格化效果；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简单&lt;/strong&gt;：反色、怀旧（sepia 矩阵 &lt;code&gt;0.393/0.769/0.189...&lt;/code&gt;）、灰度；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扭曲&lt;/strong&gt;：挤压、球面化、正/反旋转、正弦波浪、摩尔纹——扭曲类全部带&lt;strong&gt;多段滑块实时预览&lt;/strong&gt;，取消可还原。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;14-图像调整与文字&#34;&gt;1.4 图像调整与文字&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HSB 调整&lt;/strong&gt;：先把整图 RGB 转成 HSB 三维 float 数组，色相/饱和度/亮度三个滑块实时调节再转回 RGB；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对比度&lt;/strong&gt;调整；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文字工具&lt;/strong&gt;：点击图片任意位置就地出现透明输入框 + 字体选择面板，完成后生成为独立的文字图层。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;15-文件与工程&#34;&gt;1.5 文件与工程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;打开 jpg / png / gif / bmp；&lt;/li&gt;
&lt;li&gt;自定义 &lt;strong&gt;&lt;code&gt;.jps&lt;/code&gt; 工程格式&lt;/strong&gt;（类似 PSD）：整个图层模型实现 &lt;code&gt;Serializable&lt;/code&gt;，用 &lt;code&gt;ObjectOutputStream&lt;/code&gt; 套 &lt;code&gt;GZIPOutputStream&lt;/code&gt; 序列化保存，可以随时回来继续编辑；&lt;/li&gt;
&lt;li&gt;复制 / 剪切 / 粘贴、导航缩放窗口、中文界面。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二架构总览&#34;&gt;二、架构总览&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;JPhotoShop 入口&amp;lt;br/&amp;gt;EDT 中构建主窗口&amp;#34;] --&amp;gt; B[&amp;#34;JDesktopPane MDI 主框架&amp;#34;]
    B --&amp;gt; C[&amp;#34;EditFrame 编辑窗口&amp;lt;br/&amp;gt;每张图一个&amp;#34;]
    B --&amp;gt; D[&amp;#34;LayerControl 图层面板&amp;#34;]
    B --&amp;gt; E[&amp;#34;ViewSizeFrame 导航/缩放&amp;#34;]
    C --&amp;gt; F[&amp;#34;EditModel&amp;lt;br/&amp;gt;像素模型 + MemoryImageSource 渲染&amp;#34;]
    F --&amp;gt; G[&amp;#34;ImageModel&amp;lt;br/&amp;gt;LinkedList 图层栈&amp;#34;]
    G --&amp;gt; H[&amp;#34;ImagePaintLayer / TextPaintLayer / TempImageLayer&amp;#34;]
    H --&amp;gt; I[&amp;#34;LayerPlug 混合样式&amp;lt;br/&amp;gt;Alpha 合成&amp;#34;]
    C --&amp;gt; J[&amp;#34;ToolManager&amp;lt;br/&amp;gt;工具代理/状态切换&amp;#34;]
    J --&amp;gt; K[&amp;#34;Tool 抽象类&amp;lt;br/&amp;gt;魔棒/套索/贝塞尔/移动/文字...&amp;#34;]
    C --&amp;gt; L[&amp;#34;FilterManager&amp;lt;br/&amp;gt;properties + 反射装配&amp;#34;]
    L --&amp;gt; M[&amp;#34;PicFilter 滤镜即 Action&amp;#34;]
    M --&amp;gt; N[&amp;#34;Math 包&amp;lt;br/&amp;gt;CoverOp 卷积 / FeatherSide 羽化&amp;lt;br/&amp;gt;FindSide 轮廓追踪 / PointConnection 并查集&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;整体分四层：&lt;strong&gt;UI 层&lt;/strong&gt;（MDI 窗口与面板）、&lt;strong&gt;模型层&lt;/strong&gt;（图层栈与像素数据）、&lt;strong&gt;工具/滤镜层&lt;/strong&gt;（操作入口）、&lt;strong&gt;算法层&lt;/strong&gt;（&lt;code&gt;jphotoshop.Math&lt;/code&gt; 包）。工具和滤镜都通过 properties 配置 + 反射装配，下面详细说。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三技术亮点&#34;&gt;三、技术亮点&lt;/h2&gt;
&lt;h3 id=&#34;31-自绘像素管线int--memoryimagesource&#34;&gt;3.1 自绘像素管线：int[][] + MemoryImageSource&lt;/h3&gt;
&lt;p&gt;核心存储没有用 &lt;code&gt;BufferedImage&lt;/code&gt;，而是直接操作 &lt;strong&gt;&lt;code&gt;int[][]&lt;/code&gt; ARGB 二维数组&lt;/strong&gt;——所有算法（卷积、混合、羽化）都在裸数组上做，直观且没有额外封装开销。&lt;/p&gt;
&lt;p&gt;显示侧用了一个当年很典型的 Swing 高性能技巧：&lt;code&gt;MemoryImageSource&lt;/code&gt; + &lt;code&gt;setAnimated(true)&lt;/code&gt;，像素变化后调 &lt;code&gt;newPixels()&lt;/code&gt; &lt;strong&gt;增量刷新&lt;/strong&gt;屏幕图像，避免每次重绘都重建整个 Image 对象。编辑、拖选区、调滤镜滑块时的实时反馈都靠它撑着。&lt;/p&gt;
&lt;h3 id=&#34;32-链式图层合成缓存&#34;&gt;3.2 链式图层合成缓存&lt;/h3&gt;
&lt;p&gt;多图层合成是最频繁的操作，朴素实现是每次重绘把所有层从头合一遍。这里的做法是给每个图层挂一个&amp;quot;&lt;strong&gt;本层与下面所有层合成结果的缓存&lt;/strong&gt; &lt;code&gt;cacheData&lt;/code&gt;&amp;quot;，层与层之间用 &lt;code&gt;backLayer&lt;/code&gt; 引用连成链：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    BG[&amp;#34;背景层&amp;lt;br/&amp;gt;棋盘格&amp;#34;] --&amp;gt;|&amp;#34;合成&amp;#34;| L1[&amp;#34;图层1&amp;lt;br/&amp;gt;cacheData&amp;#34;]
    L1 --&amp;gt;|&amp;#34;合成&amp;#34;| L2[&amp;#34;图层2&amp;lt;br/&amp;gt;cacheData&amp;#34;]
    L2 --&amp;gt;|&amp;#34;合成&amp;#34;| L3[&amp;#34;图层3&amp;lt;br/&amp;gt;cacheData&amp;#34;]
    L3 --&amp;gt; OUT[&amp;#34;最终显示图像&amp;#34;]
    style L2 fill:#f96
    L2 -. &amp;#34;仅从此层向上重建&amp;#34; .-&amp;gt; L3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;某一层像素变化时，&lt;strong&gt;只从该层向上依次 &lt;code&gt;reBuild&lt;/code&gt;&lt;/strong&gt;，下面的层缓存不动。层数多、图大时，这个收益非常明显。&lt;/p&gt;
&lt;h3 id=&#34;33-手写-alpha-混合&#34;&gt;3.3 手写 Alpha 混合&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AlphaLayerStyle&lt;/code&gt; 实现了逐像素的经典混合公式 &lt;code&gt;C = α·Cf + (1-α)·Cb&lt;/code&gt;，并且处理了&lt;strong&gt;任意偏移（含负坐标裁剪）&lt;/strong&gt;——浮动选区拖到画布边缘外也能正确合成。羽化后的选区掩码是 short 型 alpha 权重，抠图粘贴时按权重衰减，边缘不会出现生硬的锯齿。&lt;/p&gt;
&lt;h3 id=&#34;34-魔棒一条完整的算法链&#34;&gt;3.4 魔棒：一条完整的算法链&lt;/h3&gt;
&lt;p&gt;魔棒是整个项目里算法含量最高的功能，完整链路分三步：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[&amp;#34;用户点击图片某点&amp;#34;] --&amp;gt; B[&amp;#34;Flood Fill 泛洪填充&amp;lt;br/&amp;gt;8邻域 + 容差判断&amp;lt;br/&amp;gt;ΔR+ΔG+ΔB ＜ range&amp;#34;]
    B --&amp;gt; C[&amp;#34;PointConnection 并查集&amp;lt;br/&amp;gt;id 数组 + sz 数组按秩合并&amp;lt;br/&amp;gt;分离多个互不连通的区域&amp;#34;]
    C --&amp;gt; D[&amp;#34;FindSide 8方向轮廓追踪&amp;lt;br/&amp;gt;沿区域边界走一圈&amp;lt;br/&amp;gt;每3步抽稀路径点&amp;#34;]
    D --&amp;gt; E[&amp;#34;GeneralPath 选区路径&amp;#34;]
    E --&amp;gt; F[&amp;#34;蚂蚁线动画&amp;lt;br/&amp;gt;Timer 500ms 切换两条相位差3的虚线&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;泛洪填充用 &lt;code&gt;LinkedList&lt;/code&gt; 当队列，容差判断用曼哈顿距离（&lt;code&gt;|ΔR|+|ΔG|+|ΔB|&lt;/code&gt;），比欧氏距离省开方；&lt;/li&gt;
&lt;li&gt;一次点击可能选中多个互不连通的区域（比如图片里分散的几块蓝天），用&lt;strong&gt;并查集&lt;/strong&gt;把连通域分组，每个域独立生成轮廓；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轮廓追踪&lt;/strong&gt;把面状的 boolean 掩码转成线状的路径点列，这一步直接决定蚂蚁线的平滑程度；&lt;/li&gt;
&lt;li&gt;蚂蚁线动画是两条相位差为 3 的虚线 &lt;code&gt;BasicStroke&lt;/code&gt; 用 500ms 的 &lt;code&gt;Timer&lt;/code&gt; 交替绘制——朴素但效果完全对味。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;35-通用卷积引擎-coverop&#34;&gt;3.5 通用卷积引擎 CoverOp&lt;/h3&gt;
&lt;p&gt;所有卷积类滤镜（模糊、锐化背后是同一个引擎 &lt;code&gt;CoverOp&lt;/code&gt;，提供三种重载：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;全图卷积；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;掩码限定区域卷积&lt;/strong&gt;——滤镜只在选区内生效，这是 PS 的基本体验；&lt;/li&gt;
&lt;li&gt;short alpha 掩码卷积——配合羽化选区做渐变处理。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;RGB 三通道分离卷积 + 边界 clamp，滤镜类只需要给一个卷积核就完事。新增一个模糊变体的成本是十几行代码。&lt;/p&gt;
&lt;h3 id=&#34;36-贝塞尔选区schneider-曲线拟合&#34;&gt;3.6 贝塞尔选区：Schneider 曲线拟合&lt;/h3&gt;
&lt;p&gt;自由手绘的选区轨迹是抖动的折线，直接当选区很难看。贝塞尔选区工具在双击闭合时调用 JHotDraw 的 &lt;code&gt;Bezier.fitCurve&lt;/code&gt;（Schneider 的经典拟合算法），把鼠标点列拟合成平滑的三次贝塞尔曲线段——这就是 PS 里&amp;quot;钢笔工具&amp;quot;的底层原理。&lt;/p&gt;
&lt;h3 id=&#34;37-声明式装配properties--反射的原始插件化&#34;&gt;3.7 声明式装配：properties + 反射的原始&amp;quot;插件化&amp;quot;&lt;/h3&gt;
&lt;p&gt;工具和滤镜都&lt;strong&gt;不写死在代码里&lt;/strong&gt;，而是从 properties 读类名、&lt;code&gt;Class.forName&lt;/code&gt; 反射实例化：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-properties&#34; data-lang=&#34;properties&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# resource/filters.properties&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;filters.Kinds&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;模糊,锐化,艺术效果,简单,扭曲&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;模糊&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Smooth,GaussBlur,Median&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;加一个新滤镜 = 写一个类 + 改一行配置，菜单自动按分类分组生成。工具栏同理（&lt;code&gt;ToolManager.initTool&lt;/code&gt;）。项目里还专门有一个 &lt;code&gt;plug&lt;/code&gt; 包在探索真正的外部插件机制（虽然只是个雏形）。在 2007 年没有 OSGi、没有 IoC 框架依赖的纯 Swing 工程里，这是最务实的可扩展方案。&lt;/p&gt;
&lt;h3 id=&#34;38-工具架构proxy--state-的混合&#34;&gt;3.8 工具架构：Proxy + State 的混合&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Tool&lt;/code&gt; 抽象类把底层 MouseEvent 翻译成语义化回调（&lt;code&gt;click / pressed / dragged / release / rightClick / clickDouble / move&lt;/code&gt;），子类只关心语义。&lt;code&gt;ToolManager&lt;/code&gt; 本身也是 &lt;code&gt;Tool&lt;/code&gt; 的子类，作为&lt;strong&gt;代理&lt;/strong&gt;把事件转发给当前激活的工具——选中哪个工具，事件流就切到哪个状态机上。每个工具还可以自带 &lt;code&gt;getPlugPanel()&lt;/code&gt; 参数面板，选中时挂到选项栏上，和 PS 的交互完全一致。&lt;/p&gt;
&lt;h3 id=&#34;39-扭曲滤镜的预览-取消模式&#34;&gt;3.9 扭曲滤镜的&amp;quot;预览-取消&amp;quot;模式&lt;/h3&gt;
&lt;p&gt;扭曲类滤镜（球面、旋转、波浪……）都是坐标重映射：对每个目标像素做逆极坐标变换，找它在原图的取样点。为了支持滑块&lt;strong&gt;实时预览又能取消还原&lt;/strong&gt;，&lt;code&gt;ContortFilter&lt;/code&gt; 在打开对话框时先备份 &lt;code&gt;tempData&lt;/code&gt;，滑块每次变化都在备份基础上重算，点取消直接把备份拷回——一个廉价的 Memento 模式。配套的 &lt;code&gt;MultiSlider&lt;/code&gt; 多段滑块控件也是自绘的。&lt;/p&gt;
&lt;h3 id=&#34;310-滤镜即-action&#34;&gt;3.10 滤镜即 Action&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PicFilter extends AbstractAction&lt;/code&gt;，滤镜本身就是可以挂进菜单/工具栏的 Action：&lt;code&gt;actionPerformed&lt;/code&gt; 里自动取当前窗口、当前图层、当前选区掩码，还处理了&lt;strong&gt;选区坐标系到图层坐标系的换算&lt;/strong&gt;（&lt;code&gt;start -= layerStart&lt;/code&gt;），执行完触发 &lt;code&gt;rePaintEdit&lt;/code&gt; 刷新。框架把脏活都干了，滤镜作者只需要关注像素算法。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四诚实的复盘留下了什么遗憾&#34;&gt;四、诚实的复盘：留下了什么遗憾&lt;/h2&gt;
&lt;p&gt;开源的代码就是当年的样子，包括未完成的部分，如实列出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;撤销/重做是空架子&lt;/strong&gt;：&lt;code&gt;UndoRedoManager&lt;/code&gt; 的骨架搭好了，但 &lt;code&gt;propertyChange&lt;/code&gt; 三个分支全是空的——这是当时没啃下来的硬骨头，现在看应该用 Command 模式重做，而不是当初设想的 property 监听方案；&lt;/li&gt;
&lt;li&gt;历史面板、通道面板、外部插件机制都只有雏形；&lt;/li&gt;
&lt;li&gt;磁性套索完成度不高；&lt;/li&gt;
&lt;li&gt;若干小 bug，比如 &lt;code&gt;FindSide&lt;/code&gt; 里找最近点写成了找最远点（&lt;code&gt;if (temp &amp;gt; MinLength)&lt;/code&gt; 应为 &lt;code&gt;&amp;lt;&lt;/code&gt;）、&lt;code&gt;&amp;quot;undo&amp;quot;.endsWith(...)&lt;/code&gt; 应为 &lt;code&gt;startsWith&lt;/code&gt;、几处类名 typo（&lt;code&gt;WaveFiler&lt;/code&gt;、&lt;code&gt;getSize&lt;/code&gt; 拼写）——权当留给读者的&amp;quot;考古彩蛋&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五写在最后&#34;&gt;五、写在最后&lt;/h2&gt;
&lt;p&gt;写 jphotoshop 的半年收获远超预期：并查集、轮廓追踪、卷积、贝塞尔拟合这些算法不再是教科书上的名词，而是要亲手调到能跑的东西；合成缓存、声明式装配、Action 抽象这些设计，也都是被真实需求逼出来的。如果你也想扎实练一次&amp;quot;算法 + 工程&amp;quot;的综合功，手写一个图像编辑器是我能想到的最好题目之一。&lt;/p&gt;
&lt;p&gt;源码在 &lt;a href=&#34;https://gitee.com/soulnew/jphotoshop&#34;&gt;https://gitee.com/soulnew/jphotoshop&lt;/a&gt;，欢迎 Star、Fork，更欢迎把那个空着的 &lt;code&gt;UndoRedoManager&lt;/code&gt; 补上。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Java 高并发编程实战：同步工具、锁与线程池</title>
      <link>https://ftmao.com/blog/posts/java-concurrency-guide/</link>
      <pubDate>Wed, 28 Mar 2018 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/java-concurrency-guide/</guid>
      <description>&lt;h1 id=&#34;java-高并发编程实战同步工具锁与线程池&#34;&gt;Java 高并发编程实战：同步工具、锁与线程池&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;高并发程序的核心不是“让线程跑得多快”，而是&lt;strong&gt;让多个线程在共享可变状态下安全、可预期地协作&lt;/strong&gt;。JDK 的 &lt;code&gt;java.util.concurrent&lt;/code&gt;（JUC）包已经为我们提供了大量经过打磨的同步工具，善用它们比手写 &lt;code&gt;wait/notify&lt;/code&gt; 要可靠得多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一高层次思想并发编程在解决什么问题&#34;&gt;一、高层次思想：并发编程在解决什么问题？&lt;/h2&gt;
&lt;p&gt;并发编程的本质是&lt;strong&gt;约束&lt;/strong&gt;：当多个线程访问同一组可变资源时，通过某种机制保证以下三件事至少满足其一：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;互斥&lt;/strong&gt;：同一时刻只有一个线程能访问临界区。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可见性&lt;/strong&gt;：一个线程修改的状态，其他线程能正确看到。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有序性&lt;/strong&gt;：指令不会被编译器或 CPU 乱序到破坏程序语义。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[共享可变状态] --&amp;gt;|互斥| B[锁 / 信号量]
    A --&amp;gt;|可见性| C[volatile / happens-before]
    A --&amp;gt;|有序性| D[内存屏障 / 同步原语]
    B --&amp;gt; E[正确的并发程序]
    C --&amp;gt; E
    D --&amp;gt; E&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;首选策略金字塔&lt;/strong&gt;（从推荐到不推荐）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[不可变对象 &amp;amp; 线程封闭] --&amp;gt; B[JUC 并发集合 / 线程池]
    B --&amp;gt; C[高级同步工具 CountDownLatch Semaphore 等]
    C --&amp;gt; D[ReentrantLock &amp;amp; Condition]
    D --&amp;gt; E[synchronized]
    E --&amp;gt; F[手写 wait/notify]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #1&lt;/strong&gt;：不到万不得已不要手写 &lt;code&gt;wait/notify&lt;/code&gt;，极容易写出遗漏 &lt;code&gt;notify&lt;/code&gt;、虚假唤醒、持锁休眠等问题。JUC 工具类已经帮你封装好了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二线程状态与原语对照&#34;&gt;二、线程状态与原语对照&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方法&lt;/th&gt;
					&lt;th&gt;调用者&lt;/th&gt;
					&lt;th&gt;是否释放锁&lt;/th&gt;
					&lt;th&gt;进入状态&lt;/th&gt;
					&lt;th&gt;唤醒条件&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Thread.sleep(ms)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;当前线程&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;TIMED_WAITING&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;时间到&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Thread.yield()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;当前线程&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;RUNNABLE&lt;/code&gt;（让出 CPU）&lt;/td&gt;
					&lt;td&gt;OS 调度&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;t.join()&lt;/code&gt; / &lt;code&gt;t.join(ms)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;当前线程等 t&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;WAITING&lt;/code&gt; / &lt;code&gt;TIMED_WAITING&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;t 结束或超时&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;obj.wait()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;当前线程&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;是&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;WAITING&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;notify/notifyAll&lt;/code&gt; 或超时&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;obj.notify()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;当前线程&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;唤醒一个等待线程&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;stateDiagram-v2
    [*] --&amp;gt; 新建: new Thread()
    新建 --&amp;gt; 就绪: start()
    就绪 --&amp;gt; 运行: 调度
    运行 --&amp;gt; 就绪: yield / 时间片到
    运行 --&amp;gt; 阻塞: 等待锁
    阻塞 --&amp;gt; 就绪: 获得锁
    运行 --&amp;gt; 等待: wait / join / await
    等待 --&amp;gt; 就绪: notify / 超时 / 被等待线程结束
    运行 --&amp;gt; 超时等待: sleep / wait(timeout)
    超时等待 --&amp;gt; 就绪: 时间到
    运行 --&amp;gt; 终止: 执行完毕&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #2&lt;/strong&gt;：&lt;code&gt;sleep&lt;/code&gt; 不释放锁，若在 synchronized 块内 sleep，其他线程会被长时间阻塞。需要“让出锁去等”时，用 &lt;code&gt;wait/Condition.await&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三多用-juc-同步类少用-waitnotify&#34;&gt;三、多用 JUC 同步类，少用 wait/notify&lt;/h2&gt;
&lt;h3 id=&#34;31-semaphore信号量&#34;&gt;3.1 Semaphore（信号量）&lt;/h3&gt;
&lt;p&gt;信号量维护一组“许可”，控制同时访问某资源的线程数量。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Semaphore semaphore &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Semaphore(3); &lt;span style=&#34;color:#75715e&#34;&gt;// 最多 3 个线程并发&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    semaphore.&lt;span style=&#34;color:#a6e22e&#34;&gt;acquire&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 访问受限资源&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;finally&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    semaphore.&lt;span style=&#34;color:#a6e22e&#34;&gt;release&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[线程1] --&amp;gt;|acquire| S[许可池 3]
    B[线程2] --&amp;gt;|acquire| S
    C[线程3] --&amp;gt;|acquire| S
    D[线程4] --&amp;gt;|acquire 阻塞| S
    S --&amp;gt;|release| S&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #3&lt;/strong&gt;：&lt;code&gt;release()&lt;/code&gt; 一定要放在 &lt;code&gt;finally&lt;/code&gt; 里，否则异常会导致许可泄漏，最终所有线程都拿不到许可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;32-countdownlatch-与-cyclicbarrier&#34;&gt;3.2 CountDownLatch 与 CyclicBarrier&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;CountDownLatch&lt;/th&gt;
					&lt;th&gt;CyclicBarrier&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;方向&lt;/td&gt;
					&lt;td&gt;一个线程等待其他线程&lt;/td&gt;
					&lt;td&gt;多个线程互相等待&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可复用&lt;/td&gt;
					&lt;td&gt;否（一次性）&lt;/td&gt;
					&lt;td&gt;是（可 reset）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;计数归零后&lt;/td&gt;
					&lt;td&gt;门闩打开，直接退出&lt;/td&gt;
					&lt;td&gt;所有线程同时继续&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph CountDownLatch
        M[主线程 await] -.-&amp;gt;|计数=0| M2[继续执行]
        W1[工作线程1] --&amp;gt;|countDown| C[计数器]
        W2[工作线程2] --&amp;gt;|countDown| C
    end

    subgraph CyclicBarrier
        T1[线程A await] --&amp;gt; B[屏障]
        T2[线程B await] --&amp;gt; B
        T3[线程C await] --&amp;gt; B
        B --&amp;gt;|全部到达| Go[一起继续]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #4&lt;/strong&gt;：不要把 &lt;code&gt;CountDownLatch&lt;/code&gt; 当循环栅栏用，计数到 0 后无法重置；需要循环同步用 &lt;code&gt;CyclicBarrier&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;33-exchanger&#34;&gt;3.3 Exchanger&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Exchanger&lt;/code&gt; 让两个线程在某个同步点交换数据，适合生产者-消费者配对场景，但使用频率较低。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四volatile可见性与有序性但不保证原子性&#34;&gt;四、volatile：可见性与有序性，但不保证原子性&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt; 的三条语义：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可见性&lt;/strong&gt;：写操作会刷入主内存，读操作会从主内存刷新。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有序性&lt;/strong&gt;：禁止指令重排序（happens-before）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不保证原子性&lt;/strong&gt;：复合操作（如 &lt;code&gt;i++&lt;/code&gt;）仍需要锁或原子类。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[线程A 写 volatile flag=true] --&amp;gt;| happens-before | B[线程B 读 flag]
    B --&amp;gt; C[线程B 看到 flag 之前所有写入]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 错误示例：volatile 不能保证 i++ 原子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    count&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 读-改-写，非原子&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 正确做法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; AtomicInteger count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; AtomicInteger(0);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;increment&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    count.&lt;span style=&#34;color:#a6e22e&#34;&gt;incrementAndGet&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #5&lt;/strong&gt;：只有在&lt;strong&gt;单次写、多次读、且不依赖前值&lt;/strong&gt;的场景下才用 &lt;code&gt;volatile&lt;/code&gt;（如状态标志 &lt;code&gt;volatile boolean running = true&lt;/code&gt;）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五死锁原因与破解&#34;&gt;五、死锁：原因与破解&lt;/h2&gt;
&lt;h3 id=&#34;51-典型死锁&#34;&gt;5.1 典型死锁&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    T1[线程1] --&amp;gt;|持有 A 锁| B[等待 B 锁]
    T2[线程2] --&amp;gt;|持有 B 锁| A[等待 A 锁]
    B --&amp;gt; T2
    A --&amp;gt; T1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 错误：锁顺序不一致&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;devoid &lt;span style=&#34;color:#a6e22e&#34;&gt;transfer&lt;/span&gt;(Account a, Account b, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; amount) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;synchronized&lt;/span&gt;(a) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;synchronized&lt;/span&gt;(b) { ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;52-常见诱因&#34;&gt;5.2 常见诱因&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不一致的加锁顺序&lt;/strong&gt;：两个线程以不同顺序获取多把锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在同步块内调用外部同步方法&lt;/strong&gt;：引入了不可控的锁依赖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持锁等待 I/O / 网络 / 用户输入&lt;/strong&gt;：放大了死锁窗口。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;53-破解方案&#34;&gt;5.3 破解方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全局统一锁顺序&lt;/strong&gt;：先按对象哈希值排序再加锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用 &lt;code&gt;tryLock&lt;/code&gt; 超时获取锁&lt;/strong&gt;，失败则回退并重试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少锁粒度&lt;/strong&gt;，避免在同步块内调用外部方法（Open Call）。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 ReentrantLock 的 tryLock 避免死锁&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (lockA.&lt;span style=&#34;color:#a6e22e&#34;&gt;tryLock&lt;/span&gt;(1, TimeUnit.&lt;span style=&#34;color:#a6e22e&#34;&gt;SECONDS&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (lockB.&lt;span style=&#34;color:#a6e22e&#34;&gt;tryLock&lt;/span&gt;(1, TimeUnit.&lt;span style=&#34;color:#a6e22e&#34;&gt;SECONDS&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 执行业务&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            } &lt;span style=&#34;color:#66d9ef&#34;&gt;finally&lt;/span&gt; { lockB.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;(); }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;finally&lt;/span&gt; { lockA.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;(); }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #6&lt;/strong&gt;：死锁往往不是锁本身的问题，而是&lt;strong&gt;锁的顺序和依赖关系&lt;/strong&gt;的问题。设计阶段先画锁依赖图，能发现大部分隐患。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六公平锁-vs-非公平锁&#34;&gt;六、公平锁 vs 非公平锁&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ReentrantLock fairLock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ReentrantLock(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;);   &lt;span style=&#34;color:#75715e&#34;&gt;// 公平&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ReentrantLock unfairLock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ReentrantLock(&lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;); &lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 非公平&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;（&lt;/span&gt;默认&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 公平锁
        Q[等待队列 T1→T2→T3] --&amp;gt;|按顺序| L[锁]
    end
    subgraph 非公平锁
        N[新线程] --&amp;gt;|插队尝试| L2[锁]
        Q2[队列 T1→T2] -.-&amp;gt;|可能继续等待| L2
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;公平锁&lt;/strong&gt;：按请求顺序分配锁，减少饥饿，但上下文切换多、吞吐低。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非公平锁&lt;/strong&gt;：允许“插队”，若锁恰好可用，新线程可直接获取，减少唤醒延迟；但老线程可能长时间等待。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #7&lt;/strong&gt;：默认选&lt;strong&gt;非公平锁&lt;/strong&gt;即可，性能更好。除非业务明确要求严格的调度公平性（如资源分配器），否则不必打开公平模式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七condition比-waitnotify-更精确的线程协作&#34;&gt;七、Condition：比 wait/notify 更精确的线程协作&lt;/h2&gt;
&lt;p&gt;一个 &lt;code&gt;ReentrantLock&lt;/code&gt; 可以创建多个 &lt;code&gt;Condition&lt;/code&gt;，分别管理不同的等待队列。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    L[ReentrantLock] --&amp;gt; C1[Condition A 队列]
    L --&amp;gt; C2[Condition B 队列]
    L --&amp;gt; C3[Condition C 队列]
    C1 --&amp;gt;|signal| W1[唤醒指定线程]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PrintABC&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Lock lock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ReentrantLock();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Condition a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lock.&lt;span style=&#34;color:#a6e22e&#34;&gt;newCondition&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Condition b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lock.&lt;span style=&#34;color:#a6e22e&#34;&gt;newCondition&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Condition c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lock.&lt;span style=&#34;color:#a6e22e&#34;&gt;newCondition&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; index &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(String name, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; expect, Condition wait, Condition next) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Thread(() &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            lock.&lt;span style=&#34;color:#a6e22e&#34;&gt;lock&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (index &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; expect) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    wait.&lt;span style=&#34;color:#a6e22e&#34;&gt;await&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                index &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (expect &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; 1) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; 3;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                next.&lt;span style=&#34;color:#a6e22e&#34;&gt;signal&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (InterruptedException e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentThread&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;interrupt&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            } &lt;span style=&#34;color:#66d9ef&#34;&gt;finally&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                lock.&lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }).&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        PrintABC p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; PrintABC();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        p.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;A&amp;#34;&lt;/span&gt;, 0, p.&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;, p.&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        p.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;B&amp;#34;&lt;/span&gt;, 1, p.&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;, p.&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        p.&lt;span style=&#34;color:#a6e22e&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C&amp;#34;&lt;/span&gt;, 2, p.&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;, p.&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #8&lt;/strong&gt;：&lt;code&gt;Condition.await/signal&lt;/code&gt; 与 &lt;code&gt;wait/notify&lt;/code&gt; 一样，必须在持有锁的线程中调用，且 &lt;code&gt;await&lt;/code&gt; 要用 &lt;code&gt;while&lt;/code&gt; 循环而不是 &lt;code&gt;if&lt;/code&gt;，防止虚假唤醒。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八线程池不要裸开线程&#34;&gt;八、线程池：不要裸开线程&lt;/h2&gt;
&lt;h3 id=&#34;81-常见工厂方法&#34;&gt;8.1 常见工厂方法&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方法&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
					&lt;th&gt;隐患&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;newSingleThreadExecutor&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;单线程，按提交顺序执行&lt;/td&gt;
					&lt;td&gt;无界队列，任务堆积会 OOM&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;newFixedThreadPool(n)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;固定线程数&lt;/td&gt;
					&lt;td&gt;同样使用无界队列&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;newCachedThreadPool&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;线程可复用、可无限创建&lt;/td&gt;
					&lt;td&gt;高并发下线程数爆炸&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;newScheduledThreadPool&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;支持定时/周期任务&lt;/td&gt;
					&lt;td&gt;注意任务异常终止&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;82-threadpoolexecutor-核心参数&#34;&gt;8.2 ThreadPoolExecutor 核心参数&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    T[提交任务] --&amp;gt;|核心线程数未满| C[创建核心线程]
    T --&amp;gt;|队列未满| Q[进入阻塞队列]
    T --&amp;gt;|队列满且线程数&amp;lt;max| M[创建非核心线程]
    T --&amp;gt;|队列满且线程数=max| R[拒绝策略]
    C --&amp;gt; E[执行]
    Q --&amp;gt; E
    M --&amp;gt; E&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ThreadPoolExecutor executor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ThreadPoolExecutor(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    4,                      &lt;span style=&#34;color:#75715e&#34;&gt;// 核心线程数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    8,                      &lt;span style=&#34;color:#75715e&#34;&gt;// 最大线程数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    60L, TimeUnit.&lt;span style=&#34;color:#a6e22e&#34;&gt;SECONDS&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;// 非核心线程空闲存活时间&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ArrayBlockingQueue&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;(100), &lt;span style=&#34;color:#75715e&#34;&gt;// 有界队列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ThreadFactoryBuilder().&lt;span style=&#34;color:#a6e22e&#34;&gt;setNameFormat&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;biz-pool-%d&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ThreadPoolExecutor.&lt;span style=&#34;color:#a6e22e&#34;&gt;CallerRunsPolicy&lt;/span&gt;() &lt;span style=&#34;color:#75715e&#34;&gt;// 拒绝策略&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #9&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;生产环境尽量直接创建 &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;，不要用 Executors 的便捷工厂（无界队列/无限线程是 OOM 元凶）。&lt;/li&gt;
&lt;li&gt;任务抛异常会导致线程被回收重建，建议任务内部 &lt;code&gt;try/catch&lt;/code&gt; 并记录。&lt;/li&gt;
&lt;li&gt;应用关闭前调用 &lt;code&gt;shutdown()&lt;/code&gt; 或 &lt;code&gt;shutdownNow()&lt;/code&gt;，否则 JVM 可能不会退出。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九阻塞队列&#34;&gt;九、阻塞队列&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;队列&lt;/th&gt;
					&lt;th&gt;有界性&lt;/th&gt;
					&lt;th&gt;实现&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ArrayBlockingQueue&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;有界&lt;/td&gt;
					&lt;td&gt;数组 + ReentrantLock&lt;/td&gt;
					&lt;td&gt;高吞吐、需要指定容量&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;LinkedBlockingQueue&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可选有界&lt;/td&gt;
					&lt;td&gt;链表 + 两把锁（take/put）&lt;/td&gt;
					&lt;td&gt;默认无界，小心 OOM&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;PriorityBlockingQueue&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;无界&lt;/td&gt;
					&lt;td&gt;堆&lt;/td&gt;
					&lt;td&gt;按优先级出队&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DelayQueue&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;无界&lt;/td&gt;
					&lt;td&gt;堆&lt;/td&gt;
					&lt;td&gt;元素到延迟时间才能出队&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;SynchronousQueue&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;零容量&lt;/td&gt;
					&lt;td&gt;无缓冲区&lt;/td&gt;
					&lt;td&gt;一手交钱一手交货&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;LinkedTransferQueue&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;无界&lt;/td&gt;
					&lt;td&gt;无锁 CAS&lt;/td&gt;
					&lt;td&gt;支持 transfer 直接传递&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;LinkedBlockingDeque&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;无界&lt;/td&gt;
					&lt;td&gt;双向链表&lt;/td&gt;
					&lt;td&gt;支持两端操作&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    P[生产者 put] --&amp;gt; Q[阻塞队列]
    Q --&amp;gt; C[消费者 take]
    P -.-&amp;gt;|队列满| W1[阻塞]
    C -.-&amp;gt;|队列空| W2[阻塞]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #10&lt;/strong&gt;：无界阻塞队列在生产者快、消费者慢时会把内存撑爆。优先使用 &lt;code&gt;ArrayBlockingQueue&lt;/code&gt; 或指定容量的 &lt;code&gt;LinkedBlockingQueue&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十自旋锁reentrantlock-与-synchronized&#34;&gt;十、自旋锁、ReentrantLock 与 synchronized&lt;/h2&gt;
&lt;h3 id=&#34;101-自旋锁&#34;&gt;10.1 自旋锁&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SpinLock&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; AtomicReference&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Thread&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; owner &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; AtomicReference&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lock&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Thread t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentThread&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;owner.&lt;span style=&#34;color:#a6e22e&#34;&gt;compareAndSet&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, t)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 自旋等待，不进入内核态&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;unlock&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Thread t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentThread&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        owner.&lt;span style=&#34;color:#a6e22e&#34;&gt;compareAndSet&lt;/span&gt;(t, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[互斥锁 sleep] --&amp;gt;|上下文切换| B[内核阻塞]
    C[自旋锁 CAS] --&amp;gt;|用户态循环| D[CPU 忙等]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;优点：没有线程上下文切换开销。&lt;/li&gt;
&lt;li&gt;缺点：持锁时间长会浪费 CPU；&lt;strong&gt;同一线程不可重入&lt;/strong&gt;，递归调用会死锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;102-reentrantlock-与-synchronized-对比&#34;&gt;10.2 ReentrantLock 与 synchronized 对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;能力&lt;/th&gt;
					&lt;th&gt;synchronized&lt;/th&gt;
					&lt;th&gt;ReentrantLock&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;可重入&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可中断&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;lockInterruptibly()&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;超时获取&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;tryLock(timeout)&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;公平锁&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;可选&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多条件队列&lt;/td&gt;
					&lt;td&gt;一个 wait set&lt;/td&gt;
					&lt;td&gt;多个 Condition&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;性能&lt;/td&gt;
					&lt;td&gt;JDK6+ 优化后接近&lt;/td&gt;
					&lt;td&gt;接近&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #11&lt;/strong&gt;：&lt;code&gt;ReentrantLock&lt;/code&gt; 必须显式 &lt;code&gt;unlock()&lt;/code&gt;，否则即使异常也会死锁；务必放在 &lt;code&gt;finally&lt;/code&gt; 中。&lt;code&gt;synchronized&lt;/code&gt; 则由 JVM 保证自动释放。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;103-偏向锁&#34;&gt;10.3 偏向锁&lt;/h3&gt;
&lt;p&gt;偏向锁是 JVM 对&lt;strong&gt;无竞争同步&lt;/strong&gt;的优化：第一次获取锁时把线程 ID 记录到对象头，之后同一线程再进入无需 CAS。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：JDK 15 开始偏向锁默认禁用（&lt;code&gt;-XX:+UseBiasedLocking&lt;/code&gt; 已废弃），因为现代应用中锁竞争更频繁，撤销偏向锁的开销反而可能大于收益。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一线程安全的-singleton&#34;&gt;十一、线程安全的 Singleton&lt;/h2&gt;
&lt;h3 id=&#34;111-饿汉式类加载时初始化&#34;&gt;11.1 饿汉式（类加载时初始化）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Singleton&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Singleton INSTANCE &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Singleton();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Singleton&lt;/span&gt;() {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; Singleton &lt;span style=&#34;color:#a6e22e&#34;&gt;getInstance&lt;/span&gt;() { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; INSTANCE; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;借助类加载机制保证线程安全，简单可靠；若实例创建开销大且未必使用，则浪费资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;112-静态内部类推荐&#34;&gt;11.2 静态内部类（推荐）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Singleton&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Singleton&lt;/span&gt;() {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Holder&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;final&lt;/span&gt; Singleton INSTANCE &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Singleton();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; Singleton &lt;span style=&#34;color:#a6e22e&#34;&gt;getInstance&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Holder.&lt;span style=&#34;color:#a6e22e&#34;&gt;INSTANCE&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[调用 getInstance] --&amp;gt;|首次触发| B[加载 Holder 类]
    B --&amp;gt; C[初始化 INSTANCE]
    C --&amp;gt; D[返回唯一实例]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;延迟加载 + 类加载保证线程安全，是大多数场景下的最佳实践。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;113-双重检查锁dcl-volatile&#34;&gt;11.3 双重检查锁（DCL）+ volatile&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Singleton&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Singleton&lt;/span&gt;() {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; Singleton instance;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; Singleton &lt;span style=&#34;color:#a6e22e&#34;&gt;getInstance&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (instance &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;synchronized&lt;/span&gt; (Singleton.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (instance &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    instance &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Singleton(); &lt;span style=&#34;color:#75715e&#34;&gt;// 禁止指令重排&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; instance;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #12&lt;/strong&gt;：DCL 必须加 &lt;code&gt;volatile&lt;/code&gt;，否则 &lt;code&gt;new Singleton()&lt;/code&gt; 的指令重排会导致其他线程读到未构造完成的对象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十二synchronized-的底层行为&#34;&gt;十二、synchronized 的底层行为&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;synchronized&lt;/code&gt; 可以修饰方法或代码块，锁对象分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实例方法：当前实例 &lt;code&gt;this&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;静态方法：类的 &lt;code&gt;Class&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;代码块：括号里指定的对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    O[对象头 Mark Word] --&amp;gt;|无锁| A[无锁状态]
    A --&amp;gt;|第一个线程获取| B[偏向锁]
    B --&amp;gt;|出现竞争| C[轻量级锁 CAS]
    C --&amp;gt;|竞争加剧| D[重量级锁 操作系统 Mutex]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #13&lt;/strong&gt;：不要把 &lt;code&gt;synchronized&lt;/code&gt; 当成“只要包住就安全”的万金油。锁的对象要选对，锁的粒度要尽量小，避免锁住整个方法或无关代码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十三避坑速查表&#34;&gt;十三、避坑速查表&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;不要这样做&lt;/th&gt;
					&lt;th&gt;推荐做法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;手写 &lt;code&gt;wait/notify&lt;/code&gt; 做复杂协调&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;CountDownLatch&lt;/code&gt;、&lt;code&gt;CyclicBarrier&lt;/code&gt;、&lt;code&gt;Semaphore&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;用 &lt;code&gt;volatile&lt;/code&gt; 做 &lt;code&gt;i++&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;AtomicInteger&lt;/code&gt; 或 &lt;code&gt;LongAdder&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;裸 &lt;code&gt;new Thread()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Executors.newFixedThreadPool&lt;/code&gt; 无脑用&lt;/td&gt;
					&lt;td&gt;自定义有界队列 + 拒绝策略&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;在锁里调外部同步方法 / 阻塞 I/O&lt;/td&gt;
					&lt;td&gt;缩小锁范围，使用 Open Call&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;不一致的加锁顺序&lt;/td&gt;
					&lt;td&gt;全局排序或使用 &lt;code&gt;tryLock&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DCL 不加 &lt;code&gt;volatile&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;必须加 &lt;code&gt;volatile&lt;/code&gt;，或改用静态内部类&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自旋锁里递归加锁&lt;/td&gt;
					&lt;td&gt;使用可重入锁 &lt;code&gt;ReentrantLock&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;忽略 &lt;code&gt;InterruptedException&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;恢复中断标志 &lt;code&gt;Thread.currentThread().interrupt()&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;线程池任务吞掉异常&lt;/td&gt;
					&lt;td&gt;任务内部 &lt;code&gt;try/catch&lt;/code&gt; 或使用 &lt;code&gt;Future.get()&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十四总结&#34;&gt;十四、总结&lt;/h2&gt;
&lt;p&gt;Java 高并发编程的演进路径，其实是从“手动管理线程和锁”走向“使用高层抽象”。牢记下面三句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;能不变就不变&lt;/strong&gt;：优先使用不可变对象、线程封闭和 JUC 并发集合。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能用高层就不用底层&lt;/strong&gt;：用 &lt;code&gt;Semaphore&lt;/code&gt;、&lt;code&gt;CountDownLatch&lt;/code&gt;、&lt;code&gt;Executor&lt;/code&gt; 替代 &lt;code&gt;wait/notify&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;锁是最后手段&lt;/strong&gt;：如果必须用锁，确保加锁顺序一致、粒度最小、持有时间最短。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这三点落地到代码里，配合上面的图例和示例，就能避开大多数并发陷阱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;参考与延伸阅读&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;《Java 并发编程实战》（Java Concurrency in Practice）&lt;/li&gt;
&lt;li&gt;《Java 并发编程的艺术》&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/package-summary.html&#34;&gt;JDK 文档：java.util.concurrent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>JVM 内功修炼：运行时数据区、GC 演进与现代调优实践</title>
      <link>https://ftmao.com/blog/posts/jvm-internals-guide/</link>
      <pubDate>Wed, 28 Mar 2018 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/jvm-internals-guide/</guid>
      <description>&lt;h1 id=&#34;jvm-内功修炼运行时数据区gc-演进与现代调优实践&#34;&gt;JVM 内功修炼：运行时数据区、GC 演进与现代调优实践&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;JVM 是 Java 程序员逃不掉的「操作系统」。懂它不是为了背八股，而是为了在 OOM、Full GC、CPU 飙升时，能一眼定位根因并给出调优方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一高层次思想jvm-在帮你解决什么问题&#34;&gt;一、高层次思想：JVM 在帮你解决什么问题？&lt;/h2&gt;
&lt;p&gt;JVM 的核心职责可以抽象成三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一次编译，到处运行&lt;/strong&gt;：通过字节码 + 解释/编译执行，屏蔽底层 OS 与硬件差异。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动内存管理&lt;/strong&gt;：让开发者专注于业务，而不是像 C/C++ 那样手动 malloc/free。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时优化&lt;/strong&gt;：JIT 编译、逃逸分析、内联、锁消除等，把「解释执行」慢慢优化成「本地机器码执行」。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[.java 源码] --&amp;gt;|javac| B[字节码 .class]
    B --&amp;gt;|类加载子系统| C[方法区/Metaspace]
    C --&amp;gt;|解释器| D[逐行解释执行]
    C --&amp;gt;|JIT 编译器| E[本地机器码]
    D --&amp;gt; F[执行引擎]
    E --&amp;gt; F
    F --&amp;gt; G[操作系统]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #1&lt;/strong&gt;：不要只把 JVM 当「黑盒」。线上 90% 的 Java 性能问题，根源都在堆外内存泄漏、GC 停顿过长、Metaspace 膨胀、线程栈溢出这四类。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二运行时数据区新版-jdk-的版图&#34;&gt;二、运行时数据区：新版 JDK 的版图&lt;/h2&gt;
&lt;p&gt;不同虚拟机的运行时数据区实现略有差异，但都遵循《Java 虚拟机规范》。现代视角下，区域可划分为「线程私有」和「线程共享」两类：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    subgraph 线程私有
        A[程序计数器 PC Register]
        B[虚拟机栈 JVM Stack]
        C[本地方法栈 Native Method Stack]
    end
    subgraph 线程共享
        D[堆 Heap]
        E[方法区 Method Area / Metaspace]
        F[直接内存 Direct Memory]
    end
    A --&amp;gt; G[线程执行上下文]
    B --&amp;gt; G
    C --&amp;gt; G
    D --&amp;gt; H[几乎所有对象实例]
    E --&amp;gt; I[类元数据/常量/静态变量]
    F --&amp;gt; J[NIO / 堆外内存]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;21-程序计数器program-counter-register&#34;&gt;2.1 程序计数器（Program Counter Register）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当前线程执行字节码的&lt;strong&gt;行号指示器&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;分支、循环、跳转、异常处理、线程恢复都依赖它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程私有&lt;/strong&gt;，生命周期与线程一致。&lt;/li&gt;
&lt;li&gt;唯一一个在《Java 虚拟机规范》中&lt;strong&gt;没有规定 OOM&lt;/strong&gt; 的区域。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;22-java-虚拟机栈jvm-stack&#34;&gt;2.2 Java 虚拟机栈（JVM Stack）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;每个方法执行时创建一个&lt;strong&gt;栈帧&lt;/strong&gt;（Stack Frame）。&lt;/li&gt;
&lt;li&gt;栈帧里包含：局部变量表、操作数栈、动态链接、方法返回地址。&lt;/li&gt;
&lt;li&gt;局部变量表中的基本数据类型和对象引用在编译期大小就确定。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[JVM Stack] --&amp;gt; B[栈帧 Frame 1]
    A --&amp;gt; C[栈帧 Frame 2]
    A --&amp;gt; D[栈帧 Frame 3]
    B --&amp;gt; B1[局部变量表]
    B --&amp;gt; B2[操作数栈]
    B --&amp;gt; B3[动态链接]
    B --&amp;gt; B4[方法返回地址]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #2&lt;/strong&gt;：&lt;code&gt;-Xss&lt;/code&gt; 设置线程栈大小。递归过深、方法调用链过长会抛出 &lt;code&gt;StackOverflowError&lt;/code&gt;；如果线程创建太多，总栈空间超过限制，会抛出 &lt;code&gt;OutOfMemoryError: unable to create new native thread&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;23-本地方法栈native-method-stack&#34;&gt;2.3 本地方法栈（Native Method Stack）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;与虚拟机栈作用相同，只是为 Native 方法服务。&lt;/li&gt;
&lt;li&gt;HotSpot 中把虚拟机栈和本地方法栈合二为一了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;24-java-堆java-heap&#34;&gt;2.4 Java 堆（Java Heap）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JVM 管理的&lt;strong&gt;最大一块内存&lt;/strong&gt;，被所有线程共享。&lt;/li&gt;
&lt;li&gt;几乎所有对象实例和数组都在这里分配。&lt;/li&gt;
&lt;li&gt;现代 GC 基本都采用&lt;strong&gt;分代收集&lt;/strong&gt;，逻辑上分为新生代、老年代。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Java Heap] --&amp;gt; B[Young Generation 新生代]
    A --&amp;gt; C[Old Generation 老年代]
    B --&amp;gt; D[Eden 区]
    B --&amp;gt; E[Survivor From]
    B --&amp;gt; F[Survivor To]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #3&lt;/strong&gt;：不要把「堆」和「栈」对立记忆。对象在堆里，引用在栈里；但 HotSpot 的&lt;strong&gt;逃逸分析 + 标量替换&lt;/strong&gt;会把未逃逸的小对象直接分配在栈上，减轻 GC 压力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;25-方法区method-area与-metaspace&#34;&gt;2.5 方法区（Method Area）与 Metaspace&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;存储类信息、常量、静态变量、JIT 编译后的代码缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JDK 7 之前&lt;/strong&gt;：方法区在 PermGen（永久代）中，大小固定，&lt;code&gt;-XX:MaxPermSize&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JDK 8 及以后&lt;/strong&gt;：PermGen 被移除，方法区使用本地内存中的 &lt;strong&gt;Metaspace&lt;/strong&gt;，默认只受限于系统内存，用 &lt;code&gt;-XX:MaxMetaspaceSize&lt;/code&gt; 控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;版本&lt;/th&gt;
					&lt;th&gt;实现&lt;/th&gt;
					&lt;th&gt;配置参数&lt;/th&gt;
					&lt;th&gt;常见问题&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;JDK 7-&lt;/td&gt;
					&lt;td&gt;PermGen&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;-XX:PermSize&lt;/code&gt; / &lt;code&gt;-XX:MaxPermSize&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;动态生成类过多导致 &lt;code&gt;OutOfMemoryError: PermGen space&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;JDK 8+&lt;/td&gt;
					&lt;td&gt;Metaspace&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;-XX:MetaspaceSize&lt;/code&gt; / &lt;code&gt;-XX:MaxMetaspaceSize&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;类加载器泄漏导致 Metaspace 持续增长&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #4&lt;/strong&gt;：Metaspace 默认无上限，服务运行久了可能把容器内存撑爆。线上建议显式设置 &lt;code&gt;-XX:MaxMetaspaceSize&lt;/code&gt;，配合监控告警。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;26-直接内存direct-memory&#34;&gt;2.6 直接内存（Direct Memory）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不属于 JVM 堆，但会被 JVM 直接使用，常见于 NIO 的 &lt;code&gt;DirectByteBuffer&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;分配和回收成本较高，但读写 I/O 时避免了堆内外内存拷贝（Zero Copy）。&lt;/li&gt;
&lt;li&gt;默认大小约等于 &lt;code&gt;-Xmx&lt;/code&gt;，可用 &lt;code&gt;-XX:MaxDirectMemorySize&lt;/code&gt; 控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #5&lt;/strong&gt;：Netty、RocketMQ、Elasticsearch 等中间件大量使用堆外内存。出现 &lt;code&gt;OutOfMemoryError: Direct buffer memory&lt;/code&gt; 时，先检查是否有 &lt;code&gt;ByteBuf&lt;/code&gt; / &lt;code&gt;DirectByteBuffer&lt;/code&gt; 未释放，而不是盲目加堆内存。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三垃圾回收从算法到收集器演进&#34;&gt;三、垃圾回收：从算法到收集器演进&lt;/h2&gt;
&lt;h3 id=&#34;31-判断对象是否存活&#34;&gt;3.1 判断对象是否存活&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;引用计数法&lt;/strong&gt;：Python 用，但 Java 不用，因为解决不了循环引用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可达性分析（Reachability Analysis）&lt;/strong&gt;：从 GC Roots 出发，不可达则回收。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见 GC Roots：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚拟机栈中局部变量表引用的对象。&lt;/li&gt;
&lt;li&gt;方法区中类静态属性、常量引用的对象。&lt;/li&gt;
&lt;li&gt;本地方法栈中 JNI 引用的对象。&lt;/li&gt;
&lt;li&gt;被同步锁持有的对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[GC Roots] --&amp;gt; B[栈帧局部变量]
    A --&amp;gt; C[静态变量]
    A --&amp;gt; D[常量]
    A --&amp;gt; E[JNI 引用]
    A --&amp;gt; F[锁持有对象]
    B --&amp;gt; G[可达对象]
    C --&amp;gt; G
    D --&amp;gt; H[不可达对象&amp;lt;br/&amp;gt;等待回收]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;32-四种基础-gc-算法&#34;&gt;3.2 四种基础 GC 算法&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;算法&lt;/th&gt;
					&lt;th&gt;思想&lt;/th&gt;
					&lt;th&gt;优点&lt;/th&gt;
					&lt;th&gt;缺点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;标记-清除&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;标记无用对象，然后清除&lt;/td&gt;
					&lt;td&gt;简单，不需要移动对象&lt;/td&gt;
					&lt;td&gt;效率低，产生内存碎片&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;复制&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;内存分两块，活着的对象复制到另一块&lt;/td&gt;
					&lt;td&gt;无碎片，回收效率高&lt;/td&gt;
					&lt;td&gt;内存利用率只有一半&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;标记-整理&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;标记后把存活对象移到一端，清理边界外&lt;/td&gt;
					&lt;td&gt;无碎片，内存利用率高&lt;/td&gt;
					&lt;td&gt;移动对象开销大&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;分代收集&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按对象存活周期分代，不同代用不同算法&lt;/td&gt;
					&lt;td&gt;兼顾吞吐与停顿&lt;/td&gt;
					&lt;td&gt;需要维护复杂的跨代引用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;33-经典垃圾收集器&#34;&gt;3.3 经典垃圾收集器&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;收集器&lt;/th&gt;
					&lt;th&gt;工作区域&lt;/th&gt;
					&lt;th&gt;算法&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
					&lt;th&gt;状态&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Serial&lt;/td&gt;
					&lt;td&gt;新生代&lt;/td&gt;
					&lt;td&gt;复制&lt;/td&gt;
					&lt;td&gt;单线程，简单高效&lt;/td&gt;
					&lt;td&gt;客户端模式 / 单核&lt;/td&gt;
					&lt;td&gt;可用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ParNew&lt;/td&gt;
					&lt;td&gt;新生代&lt;/td&gt;
					&lt;td&gt;复制&lt;/td&gt;
					&lt;td&gt;Serial 的多线程版&lt;/td&gt;
					&lt;td&gt;配合 CMS&lt;/td&gt;
					&lt;td&gt;随 CMS 移除&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Parallel Scavenge&lt;/td&gt;
					&lt;td&gt;新生代&lt;/td&gt;
					&lt;td&gt;复制&lt;/td&gt;
					&lt;td&gt;吞吐量优先&lt;/td&gt;
					&lt;td&gt;后台计算&lt;/td&gt;
					&lt;td&gt;可用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Serial Old&lt;/td&gt;
					&lt;td&gt;老年代&lt;/td&gt;
					&lt;td&gt;标记-整理&lt;/td&gt;
					&lt;td&gt;单线程&lt;/td&gt;
					&lt;td&gt;客户端模式&lt;/td&gt;
					&lt;td&gt;可用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Parallel Old&lt;/td&gt;
					&lt;td&gt;老年代&lt;/td&gt;
					&lt;td&gt;标记-整理&lt;/td&gt;
					&lt;td&gt;多线程吞吐量优先&lt;/td&gt;
					&lt;td&gt;后台计算&lt;/td&gt;
					&lt;td&gt;可用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CMS&lt;/td&gt;
					&lt;td&gt;老年代&lt;/td&gt;
					&lt;td&gt;标记-清除&lt;/td&gt;
					&lt;td&gt;低停顿，并发收集&lt;/td&gt;
					&lt;td&gt;互联网低延迟服务&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;JDK 14 已移除&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;G1&lt;/td&gt;
					&lt;td&gt;整堆&lt;/td&gt;
					&lt;td&gt;标记-整理 + 复制&lt;/td&gt;
					&lt;td&gt;可控停顿， region 化&lt;/td&gt;
					&lt;td&gt;大堆、低延迟&lt;/td&gt;
					&lt;td&gt;JDK 9-22 默认&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ZGC&lt;/td&gt;
					&lt;td&gt;整堆&lt;/td&gt;
					&lt;td&gt;标记-复制/整理&lt;/td&gt;
					&lt;td&gt;亚毫秒停顿，TB 级堆&lt;/td&gt;
					&lt;td&gt;极低延迟&lt;/td&gt;
					&lt;td&gt;JDK 23+ 默认（分代）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Shenandoah&lt;/td&gt;
					&lt;td&gt;整堆&lt;/td&gt;
					&lt;td&gt;标记-复制/整理&lt;/td&gt;
					&lt;td&gt;低停顿，独立于分代&lt;/td&gt;
					&lt;td&gt;低延迟&lt;/td&gt;
					&lt;td&gt;可用，JDK 24 实验分代&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Serial] --&amp;gt; B[ParNew]
    B --&amp;gt; C[Parallel Scavenge]
    C --&amp;gt; D[G1]
    D --&amp;gt; E[ZGC / Shenandoah]
    style E fill:#f9f,stroke:#333&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;34-分代模型的工作流程&#34;&gt;3.4 分代模型的工作流程&lt;/h3&gt;
&lt;p&gt;新生代默认占堆的 1/3，老年代占 2/3。新生代内部 Eden:From Survivor:To Survivor 默认约为 8:1:1。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant Eden
    participant From as Survivor From
    participant To as Survivor To
    participant Old as Old Generation
    Note over Eden,Old: Minor GC 触发
    Eden-&amp;gt;&amp;gt;To: 存活对象复制到 To
    From-&amp;gt;&amp;gt;To: 存活对象复制到 To
    From-&amp;gt;&amp;gt;Old: 年龄超过阈值的对象晋升
    Note over Eden,From: 清空 Eden 和 From
    To-&amp;gt;&amp;gt;From: To 变 From，From 变 To&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #6&lt;/strong&gt;：不要把 Survivor 区看作「两个对称缓存区」。它们在一次 Minor GC 后会&lt;strong&gt;交换身份&lt;/strong&gt;，始终有一个是空的，用于下一次复制目标。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;35-g1-收集器里程碑式的设计&#34;&gt;3.5 G1 收集器：里程碑式的设计&lt;/h3&gt;
&lt;p&gt;G1（Garbage First）把堆划分为多个大小相等的 Region，每个 Region 动态扮演 Eden、Survivor 或 Old 角色。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[G1 Heap Regions] --&amp;gt; B[Eden Region]
    A --&amp;gt; C[Survivor Region]
    A --&amp;gt; D[Old Region]
    A --&amp;gt; E[Humongous Region&amp;lt;br/&amp;gt;大对象]
    A --&amp;gt; F[Free Region]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;G1 的核心优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可预测的停顿时间模型&lt;/strong&gt;：通过 &lt;code&gt;-XX:MaxGCPauseMillis&lt;/code&gt; 设定目标停顿。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;整体基于标记-整理，局部基于复制&lt;/strong&gt;：不会产生大面积内存碎片。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回收优先级按垃圾最多的 Region 来&lt;/strong&gt;：Garbage First 名字来源。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #7&lt;/strong&gt;：G1 的 &lt;code&gt;-XX:MaxGCPauseMillis&lt;/code&gt; 不是越小越好。设得太低（如 50ms）会导致回收 Region 太少、GC 频率飙升，反而降低吞吐。通常建议 100-200ms。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;36-现代低延迟-gczgc-与-shenandoah&#34;&gt;3.6 现代低延迟 GC：ZGC 与 Shenandoah&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;ZGC&lt;/th&gt;
					&lt;th&gt;Shenandoah&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;设计目标&lt;/td&gt;
					&lt;td&gt;亚毫秒级停顿，支持 TB 级堆&lt;/td&gt;
					&lt;td&gt;低停顿，独立于分代&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;并发能力&lt;/td&gt;
					&lt;td&gt;标记、转移、重定位几乎都并发&lt;/td&gt;
					&lt;td&gt;并发标记、并发压缩&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;着色指针&lt;/td&gt;
					&lt;td&gt;使用&lt;/td&gt;
					&lt;td&gt;不使用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;分代&lt;/td&gt;
					&lt;td&gt;JDK 21 引入分代 ZGC，JDK 23 起为默认&lt;/td&gt;
					&lt;td&gt;JDK 24 引入实验性分代 Shenandoah&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;适用版本&lt;/td&gt;
					&lt;td&gt;JDK 11+（生产推荐 JDK 17+）&lt;/td&gt;
					&lt;td&gt;OpenJDK 12+&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[传统 GC&amp;lt;br/&amp;gt;Serial/Parallel/CMS/G1] --&amp;gt;|停顿目标 ms~百 ms 级| B[G1]
    B --&amp;gt;|停顿目标 &amp;lt; 10ms| C[ZGC / Shenandoah]
    C --&amp;gt;|JDK 21+| D[分代 ZGC]
    D --&amp;gt;|JDK 23+| E[分代 ZGC 默认]
    C --&amp;gt;|JDK 24+| F[分代 Shenandoah 实验]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;高性能建议 #1&lt;/strong&gt;：如果应用对延迟敏感（如金融交易、在线游戏、实时推荐），JDK 17 下优先考虑 &lt;strong&gt;G1&lt;/strong&gt;；JDK 21+ 可直接启用 &lt;strong&gt;分代 ZGC&lt;/strong&gt;（&lt;code&gt;-XX:+UseZGC -XX:+ZGenerational&lt;/code&gt;），在吞吐和延迟之间取得更好平衡；升级到 &lt;strong&gt;JDK 23+&lt;/strong&gt; 后分代 ZGC 已是默认，无需额外参数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;37-jdk-21222324-的-gc-演进大事记&#34;&gt;3.7 JDK 21/22/23/24 的 GC 演进大事记&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;JDK 版本&lt;/th&gt;
					&lt;th&gt;GC 相关变化&lt;/th&gt;
					&lt;th&gt;影响&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JDK 21&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://openjdk.org/jeps/439&#34;&gt;JEP 439&lt;/a&gt; 正式发布&lt;strong&gt;分代 ZGC&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;默认仍是 G1，但可显式启用 &lt;code&gt;-XX:+ZGenerational&lt;/code&gt; 获得更低延迟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JDK 22&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://openjdk.org/jeps/423&#34;&gt;JEP 423&lt;/a&gt; G1 Region Pinning&lt;/td&gt;
					&lt;td&gt;G1 在 JNI 临界区内无需禁用 GC，降低延迟毛刺&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JDK 23&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://openjdk.org/jeps/474&#34;&gt;JEP 474&lt;/a&gt; &lt;strong&gt;分代 ZGC 成为默认&lt;/strong&gt;；非分代 ZGC 废弃&lt;/td&gt;
					&lt;td&gt;新默认 GC 从 G1 切换到分代 ZGC，亚毫秒级停顿更易获得&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JDK 23&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://bugs.openjdk.org/browse/JDK-8322630&#34;&gt;JDK-8322630&lt;/a&gt; ZGC 延迟优化&lt;/td&gt;
					&lt;td&gt;减少 GC 根节点扫描抖动&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JDK 24&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://openjdk.org/jeps/490&#34;&gt;JEP 490&lt;/a&gt; 移除非分代 ZGC&lt;/td&gt;
					&lt;td&gt;简化 ZGC 维护，统一使用分代模式&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JDK 24&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://openjdk.org/jeps/404&#34;&gt;JEP 404&lt;/a&gt; &lt;strong&gt;分代 Shenandoah（实验性）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Shenandoah 也走向分代，未来低延迟 GC 竞争更激烈&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JDK 24&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;https://openjdk.org/jeps/475&#34;&gt;JEP 475&lt;/a&gt; G1 Late Barrier Expansion&lt;/td&gt;
					&lt;td&gt;优化 G1 写屏障，提升编译和运行时性能&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;timeline
    title GC 默认与里程碑
    section JDK 8
        默认 : Parallel Scavenge + Parallel Old
    section JDK 9
        默认 : G1
    section JDK 11
        ZGC 实验性
    section JDK 15
        ZGC 生产可用
    section JDK 17
        G1 默认，ZGC/Shenandoah 成熟
    section JDK 21
        分代 ZGC 正式发布（仍非默认）
    section JDK 23
        分代 ZGC 成为默认 GC
    section JDK 24
        非分代 ZGC 移除；分代 Shenandoah 实验&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #7.5&lt;/strong&gt;：网上有说法称 &amp;ldquo;JDK 21 默认 GC 是 ZGC&amp;rdquo;，这是&lt;strong&gt;错误&lt;/strong&gt;的。JDK 21 默认仍是 &lt;strong&gt;G1&lt;/strong&gt;；分代 ZGC 只是正式发布，需要显式开启。真正的默认切换发生在 &lt;strong&gt;JDK 23&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四引用类型不只是强引用&#34;&gt;四、引用类型：不只是强引用&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;引用类型&lt;/th&gt;
					&lt;th&gt;回收时机&lt;/th&gt;
					&lt;th&gt;使用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;强引用&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;永不回收，即使 OOM&lt;/td&gt;
					&lt;td&gt;普通对象&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;软引用 SoftReference&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;内存不足前回收&lt;/td&gt;
					&lt;td&gt;缓存，如图片缓存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;弱引用 WeakReference&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;下一次 GC 时回收&lt;/td&gt;
					&lt;td&gt;防止内存泄漏，如 &lt;code&gt;WeakHashMap&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;虚引用 PhantomReference&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;随时可能被回收，必须配合 ReferenceQueue&lt;/td&gt;
					&lt;td&gt;跟踪对象被回收的事件，做资源后置清理&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[强引用] --&amp;gt;|最强| B[不会被回收]
    C[软引用] --&amp;gt;|内存紧张| D[回收]
    E[弱引用] --&amp;gt;|下次 GC| F[回收]
    G[虚引用] --&amp;gt;|无法获取对象| H[仅回收通知]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #8&lt;/strong&gt;：不要把软引用当「万能缓存」。在内存紧张时软引用会被批量回收，导致缓存命中率骤降。高并发缓存建议用 Caffeine 等带 TTL 和淘汰策略的专业缓存框架。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五类加载机制双亲委派与破坏&#34;&gt;五、类加载机制：双亲委派与破坏&lt;/h2&gt;
&lt;h3 id=&#34;51-类加载器层次&#34;&gt;5.1 类加载器层次&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[自定义类加载器] --&amp;gt;|委托| B[应用程序类加载器 AppClassLoader]
    B --&amp;gt;|委托| C[平台类加载器 PlatformClassLoader]
    C --&amp;gt;|委托| D[启动类加载器 BootstrapClassLoader]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：JDK 9 引入模块化系统后，原来的 &lt;strong&gt;Extension ClassLoader&lt;/strong&gt; 被改名为 &lt;strong&gt;Platform ClassLoader&lt;/strong&gt;，负责加载平台模块（如 &lt;code&gt;java.sql&lt;/code&gt;、&lt;code&gt;java.xml&lt;/code&gt; 等）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;52-双亲委派模型&#34;&gt;5.2 双亲委派模型&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;类加载器收到加载请求，先不自己加载。&lt;/li&gt;
&lt;li&gt;向上委托给父加载器。&lt;/li&gt;
&lt;li&gt;父加载器无法加载时，子加载器才尝试加载。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #9&lt;/strong&gt;：Tomcat、SPI、OSGi 都&lt;strong&gt;破坏了双亲委派&lt;/strong&gt;。如果你的项目出现 &lt;code&gt;NoSuchMethodError&lt;/code&gt; / &lt;code&gt;ClassCastException: cannot cast to the same class&lt;/code&gt;，很可能是同一个类被不同类加载器加载了两次。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六jit-编译与执行优化&#34;&gt;六、JIT 编译与执行优化&lt;/h2&gt;
&lt;p&gt;Java 代码先被解释器执行，热点代码会被 JIT 编译成本地机器码。&lt;/p&gt;
&lt;h3 id=&#34;61-hotspot-的两层编译器&#34;&gt;6.1 HotSpot 的两层编译器&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;编译器&lt;/th&gt;
					&lt;th&gt;别称&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;C1&lt;/td&gt;
					&lt;td&gt;Client Compiler&lt;/td&gt;
					&lt;td&gt;编译快，优化简单，适合启动速度和 GUI&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;C2&lt;/td&gt;
					&lt;td&gt;Server Compiler&lt;/td&gt;
					&lt;td&gt;编译慢，激进优化，适合长期运行的服务端&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;JDK 7 引入&lt;strong&gt;分层编译&lt;/strong&gt;（Tiered Compilation），JDK 8 默认开启：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;0: 解释执行
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1~3: C1 编译，带简单/复杂优化
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4: C2 编译，激进优化&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;62-常见-jit-优化&#34;&gt;6.2 常见 JIT 优化&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;优化&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;方法内联&lt;/td&gt;
					&lt;td&gt;把短方法体直接嵌入调用处，减少栈帧开销&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;逃逸分析&lt;/td&gt;
					&lt;td&gt;判断对象是否逃出方法/线程作用域&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;标量替换&lt;/td&gt;
					&lt;td&gt;把对象拆成基本类型分配在栈上&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;锁消除&lt;/td&gt;
					&lt;td&gt;确认对象只被单线程访问时去掉同步锁&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;锁粗化&lt;/td&gt;
					&lt;td&gt;把相邻的同步块合并，减少加解锁次数&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;高性能建议 #2&lt;/strong&gt;：不要过度纠结「每个对象都在堆上分配」。现代 JVM 的逃逸分析已经很强，写代码时优先保证&lt;strong&gt;对象作用域最小化&lt;/strong&gt;、&lt;strong&gt;避免无意义的全局引用&lt;/strong&gt;，让 JIT 有优化空间。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七现代-jvm-前沿特性jdk-21&#34;&gt;七、现代 JVM 前沿特性（JDK 21+）&lt;/h2&gt;
&lt;p&gt;除了 GC，JDK 21 及之后版本还在内存模型、执行模型和 Native 互操作上做了大量演进，这些也会间接影响 JVM 调优思路。&lt;/p&gt;
&lt;h3 id=&#34;71-虚拟线程virtual-threadsjep-444--jdk-21&#34;&gt;7.1 虚拟线程（Virtual Threads，JEP 444 / JDK 21）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;虚拟线程是 JVM 管理的轻量级线程，由 JVM 调度到少量平台线程（Carrier Thread）上运行。&lt;/li&gt;
&lt;li&gt;创建成本极低（KB 级栈），适合高并发 I/O 密集型应用。&lt;/li&gt;
&lt;li&gt;对 JVM 数据区的影响：虚拟线程的栈帧仍存在于 Java 堆中（作为 &lt;code&gt;Continuation&lt;/code&gt; 对象），而不是传统的线程栈。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[平台线程 Platform Thread] --&amp;gt;|1:N 映射| B[虚拟线程 Virtual Thread]
    B --&amp;gt;|挂载| C[Carrier Thread]
    C --&amp;gt;|运行在| D[OS 线程]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #9.5&lt;/strong&gt;：虚拟线程虽好，但不要和 &lt;code&gt;synchronized&lt;/code&gt; 块、本地方法、文件 I/O 混用导致 &amp;ldquo;Pinning&amp;rdquo;，会让载体线程被卡住，降低并发收益。优先使用 &lt;code&gt;ReentrantLock&lt;/code&gt; 和异步 I/O。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;72-foreign-function--memory-apijep-454--jdk-22&#34;&gt;7.2 Foreign Function &amp;amp; Memory API（JEP 454 / JDK 22）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;正式替代 &lt;code&gt;sun.misc.Unsafe&lt;/code&gt; 进行堆外内存操作。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;Arena&lt;/code&gt; 管理堆外内存生命周期，更安全、更可移植。&lt;/li&gt;
&lt;li&gt;对直接内存调优的影响：未来 &lt;code&gt;DirectByteBuffer&lt;/code&gt; 的底层实现会逐步迁移到 FFM API。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-project-lilliput压缩对象头&#34;&gt;7.3 Project Lilliput：压缩对象头&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;目标是把 HotSpot 的对象头从 96/128 bit 压缩到 64 bit。&lt;/li&gt;
&lt;li&gt;可以显著降低内存占用，提升缓存命中率。&lt;/li&gt;
&lt;li&gt;JDK 24 起部分功能已集成（如 64-bit object headers）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;74-project-valhalla值类型value-classes--primitive-classes&#34;&gt;7.4 Project Valhalla：值类型（Value Classes / Primitive Classes）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;引入 &amp;ldquo;值对象&amp;rdquo;，避免对象头和引用开销。&lt;/li&gt;
&lt;li&gt;未来 &lt;code&gt;int[]&lt;/code&gt; 和 &lt;code&gt;Integer[]&lt;/code&gt; 之间的性能鸿沟会缩小。&lt;/li&gt;
&lt;li&gt;目前处于预览阶段，持续关注即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;75-project-leydenjava-静态镜像&#34;&gt;7.5 Project Leyden：Java 静态镜像&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;目标是通过 &amp;ldquo;静态图像&amp;rdquo; 加速 Java 启动时间。&lt;/li&gt;
&lt;li&gt;类似 GraalVM Native Image，但保持完整 Java 语义和 JVM 运行时能力。&lt;/li&gt;
&lt;li&gt;对 Serverless、CLI 工具、云原生场景意义重大。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;八避坑指南线上常见问题&#34;&gt;八、避坑指南：线上常见问题&lt;/h2&gt;
&lt;h3 id=&#34;81-慎用-systemgc&#34;&gt;8.1 慎用 &lt;code&gt;System.gc()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;只是「建议」JVM 进行 Full GC，不保证执行。&lt;/li&gt;
&lt;li&gt;很多框架（如某些老版本 JDBC）会调用它，导致不可控停顿。&lt;/li&gt;
&lt;li&gt;线上建议关闭：&lt;code&gt;-XX:+DisableExplicitGC&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;82-finalize-已过时&#34;&gt;8.2 finalize() 已过时&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JDK 9 起标记为 &lt;code&gt;@Deprecated&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对象回收前不保证一定执行 &lt;code&gt;finalize()&lt;/code&gt;，且执行顺序不确定。&lt;/li&gt;
&lt;li&gt;资源清理用 &lt;code&gt;try-with-resources&lt;/code&gt; 或 &lt;code&gt;Cleaner&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;83-大对象与-humongous&#34;&gt;8.3 大对象与 Humongous&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;G1 中超过 Region 大小一半的对象叫 Humongous Object，直接进老年代，增加 GC 负担。&lt;/li&gt;
&lt;li&gt;避免一次性创建超大数组或字符串。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;84-动态代理与-cglib-的-metaspace-泄漏&#34;&gt;8.4 动态代理与 CGLIB 的 Metaspace 泄漏&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Spring AOP、MyBatis、Dubbo 大量生成代理类。&lt;/li&gt;
&lt;li&gt;如果 ClassLoader 没释放，代理类元数据会持续占用 Metaspace。&lt;/li&gt;
&lt;li&gt;常见于热部署、OSGi、动态脚本场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;85-大堆--full-gc-的雪崩&#34;&gt;8.5 大堆 + Full GC 的雪崩&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;堆内存越大，G1 的 Mixed GC、CMS 的 Full GC 停顿越长。&lt;/li&gt;
&lt;li&gt;超过 32GB 时，指针压缩会失效，对象头变大，反而浪费内存。&lt;/li&gt;
&lt;li&gt;建议堆大小控制在 16GB-32GB 之间，再大的堆考虑横向拆分服务或换 ZGC。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九高性能调优建议&#34;&gt;九、高性能调优建议&lt;/h2&gt;
&lt;h3 id=&#34;91-选择-gc-与-jdk-版本&#34;&gt;9.1 选择 GC 与 JDK 版本&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;推荐配置&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;通用 Web 服务，JDK 17-22&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;-XX:+UseG1GC -XX:MaxGCPauseMillis=200&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;通用 Web 服务，&lt;strong&gt;JDK 23+&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;默认已是分代 ZGC，一般无需改动&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;延迟敏感，JDK 21-22&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;-XX:+UseZGC -XX:+ZGenerational&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;延迟敏感，JDK 23+&lt;/td&gt;
					&lt;td&gt;默认分代 ZGC，无需显式开启&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;批处理/大数据，吞吐优先&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;-XX:+UseParallelGC&lt;/code&gt; 或 G1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;容器环境&lt;/td&gt;
					&lt;td&gt;显式设置 &lt;code&gt;-Xmx&lt;/code&gt;、&lt;code&gt;-Xms&lt;/code&gt;、&lt;code&gt;-XX:MaxMetaspaceSize&lt;/code&gt;，并开启 &lt;code&gt;-XX:+UseContainerSupport&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;92-参数模板&#34;&gt;9.2 参数模板&lt;/h3&gt;
&lt;h4 id=&#34;jdk-17-22-通用-web-服务g1&#34;&gt;JDK 17-22 通用 Web 服务（G1）&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms4g -Xmx4g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseG1GC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxGCPauseMillis&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar app.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h4 id=&#34;jdk-23-通用-web-服务默认分代-zgc&#34;&gt;JDK 23+ 通用 Web 服务（默认分代 ZGC）&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms4g -Xmx4g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;100m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar app.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;：JDK 23 起默认就是分代 ZGC，不需要 &lt;code&gt;-XX:+UseZGC&lt;/code&gt;。如果仍想显式启用，可保留；如果想用回 G1，使用 &lt;code&gt;-XX:+UseG1GC&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;93-监控与诊断三板斧&#34;&gt;9.3 监控与诊断三板斧&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;工具&lt;/th&gt;
					&lt;th&gt;用途&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jstat -gcutil &amp;lt;pid&amp;gt; 1000&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;实时查看 GC 频率与内存占比&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jmap -dump:format=b,file=... &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;生成堆快照，MAT 分析内存泄漏&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jstack &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;打印线程栈，排查死锁与 CPU 热点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GC 日志 / &lt;code&gt;-Xlog:gc*&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;分析 GC 原因与停顿时间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;async-profiler&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;低开销 CPU/内存/锁分析&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;高性能建议 #3&lt;/strong&gt;：调优前一定先&lt;strong&gt;定目标&lt;/strong&gt;——是降低停顿？提高吞吐？还是减少内存？没有目标的调优就是瞎调。先用 GC 日志和监控找到瓶颈，再改参数，而不是反向操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十jvm-问题排查实战发现--定位--修复&#34;&gt;十、JVM 问题排查实战：发现 → 定位 → 修复&lt;/h2&gt;
&lt;p&gt;线上 JVM 问题通常表现为：服务卡顿、响应 P99 飙高、CPU 打满、内存持续增长、频繁 OOM。解决它们的核心思路是：&lt;strong&gt;先量化，再猜测&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;101-问题发现的四层入口&#34;&gt;10.1 问题发现的四层入口&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[业务层] --&amp;gt;|接口超时/P99 飙升| B[应用层]
    B --&amp;gt;|Full GC 频繁/GC 时间长| C[JVM 层]
    C --&amp;gt;|堆/栈/Metaspace 异常| D[系统层]
    D --&amp;gt;|容器 OOMKilled/CPU 限流| E[基础设施层]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;入口&lt;/th&gt;
					&lt;th&gt;典型现象&lt;/th&gt;
					&lt;th&gt;常见根因&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;业务监控&lt;/td&gt;
					&lt;td&gt;接口 RT 上涨、错误率增加&lt;/td&gt;
					&lt;td&gt;GC 停顿、线程阻塞、锁竞争&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;APM / Trace&lt;/td&gt;
					&lt;td&gt;某段链路偶发耗时几百 ms&lt;/td&gt;
					&lt;td&gt;Full GC、Metaspace 扩容、类加载&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;容器监控&lt;/td&gt;
					&lt;td&gt;Pod 内存触顶被 OOMKilled&lt;/td&gt;
					&lt;td&gt;堆外内存泄漏、堆太大、Metaspace 无上限&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GC 日志&lt;/td&gt;
					&lt;td&gt;GC 频率异常、停顿时间超标&lt;/td&gt;
					&lt;td&gt;收集器选择不当、堆大小不合理、大对象过多&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;JVM 告警&lt;/td&gt;
					&lt;td&gt;Heap 使用率 &amp;gt; 85%、FGC 次数突增&lt;/td&gt;
					&lt;td&gt;内存泄漏、流量突增、缓存失控&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;102-排查工具箱&#34;&gt;10.2 排查工具箱&lt;/h3&gt;
&lt;h4 id=&#34;1021-命令行工具适合快速止血&#34;&gt;10.2.1 命令行工具（适合快速止血）&lt;/h4&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;命令&lt;/th&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;输出重点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jps -lvm&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;找到 Java 进程 PID&lt;/td&gt;
					&lt;td&gt;主类名、启动参数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jstat -gcutil &amp;lt;pid&amp;gt; 1000&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;实时监控 GC 与内存&lt;/td&gt;
					&lt;td&gt;S0/S1/E/O/M 各区使用率、YGC/FGC 次数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jmap -heap &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;查看堆配置与概览&lt;/td&gt;
					&lt;td&gt;新生代/老年代大小、收集器&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jmap -histo &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;查看对象直方图&lt;/td&gt;
					&lt;td&gt;哪个类实例数/占用最多&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jmap -dump:format=b,file=... &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;生成堆 dump&lt;/td&gt;
					&lt;td&gt;配合 MAT / JProfiler 分析&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jstack &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;打印线程栈&lt;/td&gt;
					&lt;td&gt;死锁、CPU 热点线程、Blocked 线程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jcmd &amp;lt;pid&amp;gt; VM.native_memory summary&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;查看 Native Memory Tracking&lt;/td&gt;
					&lt;td&gt;堆外内存、Metaspace、Symbol 区占用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;1022-可视化--专业工具&#34;&gt;10.2.2 可视化 / 专业工具&lt;/h4&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;工具&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;MAT (Eclipse Memory Analyzer)&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;分析堆 dump&lt;/td&gt;
					&lt;td&gt;大对象、内存泄漏、支配树、Leak Suspects&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;JProfiler / YourKit&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;全链路 JVM 分析&lt;/td&gt;
					&lt;td&gt;CPU、内存、线程、GC 一体化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;async-profiler&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;生产环境火焰图&lt;/td&gt;
					&lt;td&gt;低开销，适合线上持续采样&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;GCViewer / GCEasy&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;解析 GC 日志&lt;/td&gt;
					&lt;td&gt;可视化 GC 事件、停顿分布&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Prometheus + Grafana&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;长期监控与告警&lt;/td&gt;
					&lt;td&gt;自定义 JVM 指标大盘&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Arthas&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;线上实时诊断&lt;/td&gt;
					&lt;td&gt;无需重启，支持 trace、watch、thread、heapdump&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;103-典型问题排查流程&#34;&gt;10.3 典型问题排查流程&lt;/h3&gt;
&lt;h4 id=&#34;场景-1cpu-飙升&#34;&gt;场景 1：CPU 飙升&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 1. 找到 CPU 占用最高的线程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ps -mp &amp;lt;pid&amp;gt; -o THREAD,tid,time | sort -rn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 2. 把 tid 转成十六进制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;printf &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%x\n&amp;#34;&lt;/span&gt; &amp;lt;tid&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 3. 用 jstack 查看对应线程栈&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jstack &amp;lt;pid&amp;gt; | grep -A &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt; &amp;lt;hex_tid&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;常见根因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;死循环或复杂正则&lt;/li&gt;
&lt;li&gt;Young GC 过于频繁&lt;/li&gt;
&lt;li&gt;JIT 编译器 C2 线程忙碌（启动期正常）&lt;/li&gt;
&lt;li&gt;大量 &lt;code&gt;ThreadLocal&lt;/code&gt; 未清理&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;场景-2内存持续增长--oom&#34;&gt;场景 2：内存持续增长 / OOM&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 1. 确认哪个区域增长&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jstat -gcutil &amp;lt;pid&amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 2. 开启 Native Memory Tracking&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jcmd &amp;lt;pid&amp;gt; VM.native_memory baseline
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jcmd &amp;lt;pid&amp;gt; VM.native_memory summary.diff
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 3. 触发堆 dump（若还能响应）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jmap -dump:live,format&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;b,file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/tmp/heap.hprof &amp;lt;pid&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;常见根因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;堆：缓存无淘汰、大对象、集合只增不减&lt;/li&gt;
&lt;li&gt;Metaspace：动态代理/CGLIB 类加载器泄漏&lt;/li&gt;
&lt;li&gt;堆外：Netty &lt;code&gt;ByteBuf&lt;/code&gt; 未释放、NIO DirectBuffer 堆积&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;场景-3接口偶发卡顿--p99-高&#34;&gt;场景 3：接口偶发卡顿 / P99 高&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;打开 GC 日志，看是否有 &lt;strong&gt;长停顿&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;jstack&lt;/code&gt; 抓取卡顿时刻线程栈，看是否有大量 &lt;code&gt;BLOCKED&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;async-profiler&lt;/code&gt; 采集 CPU 火焰图，定位热点方法。&lt;/li&gt;
&lt;li&gt;检查是否有 &lt;code&gt;System.gc()&lt;/code&gt;、Metaspace 扩容、类加载抖动。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;104-jvm-监控告警建议&#34;&gt;10.4 JVM 监控告警建议&lt;/h3&gt;
&lt;p&gt;建议把以下指标接入 Prometheus + Grafana，并设置合理阈值：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;指标&lt;/th&gt;
					&lt;th&gt;告警阈值建议&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jvm_memory_used_bytes / jvm_memory_max_bytes&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt; 85%&lt;/td&gt;
					&lt;td&gt;堆/非堆内存使用率&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jvm_gc_pause_seconds_sum&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;P99 &amp;gt; 200ms&lt;/td&gt;
					&lt;td&gt;GC 停顿时间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jvm_gc_memory_allocated_bytes_total&lt;/code&gt; 增长率&lt;/td&gt;
					&lt;td&gt;突增 2 倍以上&lt;/td&gt;
					&lt;td&gt;对象分配速率异常&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jvm_classes_loaded_total&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;持续增长&lt;/td&gt;
					&lt;td&gt;类泄漏嫌疑&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;process_cpu_usage&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt; 80% 持续 5min&lt;/td&gt;
					&lt;td&gt;CPU 压力&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;jvm_threads_live&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;突增或接近上限&lt;/td&gt;
					&lt;td&gt;线程泄漏&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;容器维度 &lt;code&gt;container_memory_working_set_bytes&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&amp;gt; 90% limit&lt;/td&gt;
					&lt;td&gt;容器 OOMKilled 预警&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[应用埋点] --&amp;gt;|Micrometer| B[Prometheus]
    C[GC 日志] --&amp;gt;|Loki / Filebeat| D[日志平台]
    B --&amp;gt; E[Grafana 大盘]
    D --&amp;gt; E
    E --&amp;gt;|阈值触发| F[AlertManager]
    F --&amp;gt;|通知| G[钉钉/企业微信/Slack]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;十一不同场景的-jvm-配置策略&#34;&gt;十一、不同场景的 JVM 配置策略&lt;/h2&gt;
&lt;h3 id=&#34;111-配置前的通用原则&#34;&gt;11.1 配置前的通用原则&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先压测，再调优&lt;/strong&gt;：没有真实流量数据，调参都是拍脑袋。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Xms = Xmx&lt;/strong&gt;：避免运行时堆扩容带来的 GC 抖动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metaspace 必须设上限&lt;/strong&gt;：容器环境下尤其重要。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GC 日志一定要开&lt;/strong&gt;：&lt;code&gt;-Xlog:gc*&lt;/code&gt;（JDK 9+）成本很低，信息无价。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容器环境开启 Container Support&lt;/strong&gt;：&lt;code&gt;-XX:+UseContainerSupport&lt;/code&gt;（JDK 8u191+/10+ 默认开启）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要堆内存无限大&lt;/strong&gt;：超过 32GB 指针压缩失效，性价比骤降。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;112-场景一高并发-web--api-服务&#34;&gt;11.2 场景一：高并发 Web / API 服务&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特征&lt;/strong&gt;：QPS 高、对象生命周期短、响应延迟敏感。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：降低 GC 停顿、控制 P99。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推荐配置（JDK 17-22，G1）&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms4g -Xmx4g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseG1GC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxGCPauseMillis&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:InitiatingHeapOccupancyPercent&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;35&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;100m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar app.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;推荐配置（JDK 23+，默认分代 ZGC）&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms4g -Xmx4g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;100m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar app.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;调优要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新生代尽量大，让短生命周期对象在 Young GC 回收。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-XX:MaxGCPauseMillis&lt;/code&gt; 根据业务容忍度设置，100-200ms 常见。&lt;/li&gt;
&lt;li&gt;监控 &lt;code&gt;jvm_gc_pause_seconds_sum&lt;/code&gt;，若频繁触发 Mixed GC，考虑增大 &lt;code&gt;-XX:InitiatingHeapOccupancyPercent&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;113-场景二高流量--削峰填谷消息队列消费者&#34;&gt;11.3 场景二：高流量 / 削峰填谷（消息队列消费者）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特征&lt;/strong&gt;：突发流量、消息处理批量化、吞吐优先、允许一定延迟。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：提高吞吐，避免 OOM 和 Full GC。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推荐配置&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms8g -Xmx8g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseG1GC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxGCPauseMillis&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;300&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:G1HeapRegionSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;16m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:InitiatingHeapOccupancyPercent&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;100m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar consumer.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;调优要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;堆可以比 Web 服务稍大，因为要缓冲突发消息。&lt;/li&gt;
&lt;li&gt;批量消费时注意控制单次拉取消息数，避免一次创建过多对象。&lt;/li&gt;
&lt;li&gt;如果业务允许更高延迟，可考虑 &lt;code&gt;-XX:+UseParallelGC&lt;/code&gt; 追求极致吞吐。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;114-场景三低延迟金融--交易--实时推荐&#34;&gt;11.4 场景三：低延迟金融 / 交易 / 实时推荐&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特征&lt;/strong&gt;：P99 要求 &amp;lt; 10ms，任何 GC 停顿都可能导致业务损失。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：亚毫秒级 GC 停顿。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推荐配置（JDK 21+）&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms16g -Xmx16g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseZGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+ZGenerational &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;100m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar trading.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;JDK 23+ 可简化为&lt;/strong&gt;（默认就是分代 ZGC）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms16g -Xmx16g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;100m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar trading.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;调优要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;堆 &amp;gt; 16GB 时 ZGC 优势更明显。&lt;/li&gt;
&lt;li&gt;避免大对象（Humongous），ZGC 对大对象分配路径不如小对象高效。&lt;/li&gt;
&lt;li&gt;监控 &lt;code&gt;jvm_gc_pause_seconds_max&lt;/code&gt;，正常应在 1ms 以内。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;115-场景四大数据--批处理--etl&#34;&gt;11.5 场景四：大数据 / 批处理 / ETL&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特征&lt;/strong&gt;：长时间运行、对象存活久、吞吐优先、延迟不敏感。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：最大化吞吐，减少总 GC 时间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推荐配置&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms16g -Xmx16g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;1g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseParallelGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:ParallelGCThreads&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseLargePages &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;100m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar etl.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;调优要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parallel GC 在纯吞吐场景下通常优于 G1。&lt;/li&gt;
&lt;li&gt;大堆 + 大页（LargePages）减少 TLB miss。&lt;/li&gt;
&lt;li&gt;如果批处理任务会频繁 Full GC，考虑分阶段释放引用或使用堆外缓存。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;116-场景五容器--k8s-微服务&#34;&gt;11.6 场景五：容器 / K8s 微服务&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特征&lt;/strong&gt;：资源受限、弹性伸缩、多实例共享节点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：稳定、不 OOMKilled、启动快。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推荐配置&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseContainerSupport &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:InitialRAMPercentage&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;70.0 &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxRAMPercentage&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;70.0 &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;128m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseG1GC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxGCPauseMillis&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xlog:gc*:file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/gc.log:time,uptime,level,tags:filecount&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;5,filesize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;50m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar service.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;调优要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;-XX:MaxRAMPercentage&lt;/code&gt; 替代固定 &lt;code&gt;-Xmx&lt;/code&gt;，适配不同 Limit。&lt;/li&gt;
&lt;li&gt;容器 memory limit 要预留 10%-20% 给堆外内存、Metaspace、线程栈、JVM 自身。&lt;/li&gt;
&lt;li&gt;不要把 &lt;code&gt;-Xmx&lt;/code&gt; 设成容器 limit 的 100%，否则容易被 OOMKilled。&lt;/li&gt;
&lt;li&gt;JDK 23+ 在容器里可直接用默认分代 ZGC。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;117-场景六spring-boot--微服务网关&#34;&gt;11.7 场景六：Spring Boot / 微服务网关&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特征&lt;/strong&gt;：启动时大量类加载、反射多、动态代理多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：启动快 + 运行稳。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推荐配置&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;java &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Xms2g -Xmx2g &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;256m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxMetaspaceSize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;512m &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+UseG1GC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:MaxGCPauseMillis&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;150&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+AlwaysPreTouch &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+DisableExplicitGC &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:+HeapDumpOnOutOfMemoryError &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -XX:HeapDumpPath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/log/app/heapdump.hprof &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -Dspring.backgroundpreinitializer.ignore&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;true &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -jar gateway.jar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;调优要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Boot 启动时 Metaspace 会快速上涨，初始值不要设太小。&lt;/li&gt;
&lt;li&gt;如果启动慢，可用 &lt;code&gt;-XX:+TieredCompilation&lt;/code&gt; 配合 &lt;code&gt;-XX:TieredStopAtLevel=1&lt;/code&gt; 加速启动（牺牲峰值性能）。&lt;/li&gt;
&lt;li&gt;网关避免在请求路径创建大对象，优先复用对象和缓冲区。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十二一句话总结&#34;&gt;十二、一句话总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内存&lt;/strong&gt;：堆内分代、堆外 Direct、Metaspace 存类元数据；虚拟线程栈帧在堆中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GC&lt;/strong&gt;：Serial → Parallel → CMS → G1 → &lt;strong&gt;分代 ZGC&lt;/strong&gt;；JDK 21 分代 ZGC 正式发布，&lt;strong&gt;JDK 23 起成为默认 GC&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引用&lt;/strong&gt;：强 &amp;gt; 软 &amp;gt; 弱 &amp;gt; 虚，缓存别滥用软引用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：JDK 17-22 优先 G1；JDK 23+ 默认分代 ZGC；延迟极致用 ZGC，吞吐优先用 Parallel。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排查&lt;/strong&gt;：监控先行 → 日志定位 → 工具分析 → 压测验证 → 持续观察。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;场景&lt;/strong&gt;：Web 服务控延迟、消息队列保吞吐、金融交易用 ZGC、批处理用 Parallel、容器环境留余量。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;JVM 调优没有银弹，但有方法论：&lt;strong&gt;理解数据区 → 选对收集器 → 设定合理目标 → 用日志验证 → 持续监控&lt;/strong&gt;。把这套流程跑熟，面试八股和线上救火就都不慌了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;《深入理解 Java 虚拟机》（第三版）周志明&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.oracle.com/en/java/javase/21/gctuning/&#34;&gt;OpenJDK HotSpot Virtual Machine Garbage Collection Tuning Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/jeps/333&#34;&gt;JEP 333: ZGC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/jeps/439&#34;&gt;JEP 439: Generational ZGC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/jeps/474&#34;&gt;JEP 474: ZGC Generational Mode by Default&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/jeps/490&#34;&gt;JEP 490: Remove the Non-Generational ZGC Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/jeps/404&#34;&gt;JEP 404: Generational Shenandoah (Experimental)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/jeps/444&#34;&gt;JEP 444: Virtual Threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/jeps/454&#34;&gt;JEP 454: Foreign Function &amp;amp; Memory API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openjdk.org/projects/lilliput/&#34;&gt;Project Lilliput&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
