<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>分布式系统 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 13 Jul 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>Netflix 服务拓扑系统笔记：实时依赖地图是怎么被生产环境捶出来的</title>
      <link>https://ftmao.com/blog/posts/netflix-service-topology-at-scale/</link>
      <pubDate>Mon, 13 Jul 2026 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/netflix-service-topology-at-scale/</guid>
      <description>&lt;h1 id=&#34;netflix-服务拓扑系统笔记实时依赖地图是怎么被生产环境捶出来的&#34;&gt;Netflix 服务拓扑系统笔记：实时依赖地图是怎么被生产环境捶出来的&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：&lt;a href=&#34;https://netflixtechblog.com/building-service-topology-at-scale-architecture-challenges-and-lessons-learned-f4b792f3f0d8&#34;&gt;Building Service Topology at Scale: Architecture, Challenges, and Lessons Learned&lt;/a&gt;
目标读者：做过或准备做流式数据管线的后端工程师；被 Kafka 消费滞后、JVM GC、热点节点折磨过的人读起来会格外有体感。
阅读目标：搞清楚这套系统的三个核心决策（流式优先、三级聚合管线、动态一致性哈希）各自解决了什么问题，以及 V1 到 V2 连环踩坑的顺序——那个顺序比任何单个方案都值得记住。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;开场白比正文值钱&#34;&gt;〇、开场白比正文值钱&lt;/h2&gt;
&lt;p&gt;这个系列第一篇讲的是 what 和 why：Netflix 工程师需要统一的实时服务依赖视图来加速排障、评估爆炸半径，方案是把 eBPF 网络流、IPC 指标、分布式追踪三个数据源做成物理隔离的三层图。这篇讲的是 how——工程现实。&lt;/p&gt;
&lt;p&gt;作者的开场自白很诚实：&lt;strong&gt;V1 在本地环境完美运行，上生产就是另一个故事&lt;/strong&gt;。Kafka 消费者落后，实例内存耗尽，有的节点流量是其他节点的 100 倍，GC 停顿吃掉的 CPU 比业务逻辑还多。这篇文章不是成功故事，是一趟踩坑复盘。&lt;/p&gt;
&lt;h2 id=&#34;一流式优先新鲜度不是锦上添花&#34;&gt;一、流式优先：新鲜度不是锦上添花&lt;/h2&gt;
&lt;p&gt;传统拓扑系统是批处理：按小时或按天聚合，存全量快照。小规模没问题，但凌晨三点出事故时，一小时前的依赖图是考古，不是可观测性。&lt;/p&gt;
&lt;p&gt;决策是 streaming-first：多区域 Kafka 流和 IPC 指标以 Server-Sent Events 持续摄入，反应式管线处理，拓扑更新延迟在几十分钟量级，对比批处理的小时级、天级。事故响应、变更验证这些场景等不了下一个小时级批次。&lt;/p&gt;
&lt;p&gt;流式处理绕不开一个根本问题：下游慢了怎么办？三个传统方案都不行——无界队列会缓冲到内存爆掉；丢弃式流控会让拓扑残缺，丢的正是连接信息；批处理来不及。他们的答案是&lt;strong&gt;背压&lt;/strong&gt;：Stage 3 写图数据库写不动，通知 Stage 2 减速，Stage 2 通知 Stage 1，Stage 1 让 Kafka consumer 暂停拉取，数据就在 Kafka 里等着。任何一级被打爆（流量尖峰、GC 停顿、外部系统变慢），整条管线自动降到可持续的速率。数据晚到几秒几分钟，但既不丢也不崩。代价是反应式流本身的复杂度，这个后面会还回来。&lt;/p&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;：eBPF 流日志，存图数据库分区。覆盖全，但没有应用上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPC 层&lt;/strong&gt;：应用指标，存另一个与网络层隔离的图数据库。端点信息丰富，但只覆盖插桩了的服务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;追踪层&lt;/strong&gt;：分布式追踪，存列式存储（Parquet）。是真实请求路径，但有采样。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;物理隔离的意义是各层可以按自己的吞吐、查询模式和演进节奏独立优化。查询时并行查相关存储再合并结果，做到亚秒延迟。&lt;/p&gt;
&lt;h2 id=&#34;三三级聚合管线一个架构解两个题&#34;&gt;三、三级聚合管线：一个架构解两个题&lt;/h2&gt;
&lt;p&gt;这是全文的核心。网络流日志有个根本缺陷：&lt;strong&gt;它只能看到单个网络跳，看不到应用级连接&lt;/strong&gt;。云环境里流量很少直连，中间隔着负载均衡器、NAT 网关、API 网关、代理。流日志里 App A 到 LB 是一条流，LB 到 App B 是另一条流，但工程师要的是 App A 到 App B 这条逻辑依赖。&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
    K[&amp;#34;多区域 Kafka&amp;lt;br/&amp;gt;eBPF 流日志&amp;#34;] --&amp;gt; S1[&amp;#34;Stage 1 初始聚合&amp;lt;br/&amp;gt;过滤非法流&amp;lt;br/&amp;gt;5 分钟时间窗聚合&amp;lt;br/&amp;gt;一致性哈希分发&amp;#34;]
    S1 --&amp;gt;|&amp;#34;SSE 流&amp;#34;| S2[&amp;#34;Stage 2 中间件解析&amp;lt;br/&amp;gt;按中间件分组&amp;lt;br/&amp;gt;双跳拼成直连边&amp;lt;br/&amp;gt;再次一致性哈希&amp;#34;]
    S2 --&amp;gt;|&amp;#34;SSE 流&amp;#34;| S3[&amp;#34;Stage 3 最终聚合与富化&amp;lt;br/&amp;gt;查 KV 补元数据&amp;lt;br/&amp;gt;转成图实体&amp;lt;br/&amp;gt;限流写库&amp;#34;]
    S3 --&amp;gt; DB[&amp;#34;图数据库&amp;#34;]
    S3 -.-&amp;gt;|&amp;#34;背压: 写不动就减速&amp;#34;| S2
    S2 -.-&amp;gt;|&amp;#34;背压传播&amp;#34;| S1
    S1 -.-&amp;gt;|&amp;#34;暂停拉取&amp;lt;br/&amp;gt;数据留在 Kafka&amp;#34;| K
    style S2 fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个要点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么三级而不是一级？数据局部性。&lt;/strong&gt; 要把 App A 到 LB 加 LB 到 App B 拼成 App A 到 App B，必须让同一个中间件的所有流落在同一台实例上做 join。而 Stage 1 里流是按 Kafka 分区散在各实例上的。所以 Stage 2 的关键职能是按中间件标识重新分发——经典 map-reduce：Stage 1 做 map，Stage 2 做 shuffle 加 reduce，Stage 3 做最终聚合。&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 RAW[&amp;#34;解析前 两条单跳流&amp;#34;]
        A1[&amp;#34;App A&amp;#34;] --&amp;gt; LB[&amp;#34;Load Balancer&amp;#34;]
        LB --&amp;gt; B1[&amp;#34;App B&amp;#34;]
    end
    subgraph RES[&amp;#34;解析后 逻辑依赖&amp;#34;]
        A2[&amp;#34;App A&amp;#34;] --&amp;gt;|&amp;#34;合并双跳指标&amp;lt;br/&amp;gt;记录途经的中间件&amp;#34;| B2[&amp;#34;App B&amp;#34;]
    end
    RAW ==&amp;gt;|&amp;#34;按中间件 shuffle 到同一实例&amp;lt;br/&amp;gt;join 入流与出流&amp;#34;| RES
    style RES fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;为什么三级而不是两级？&lt;/strong&gt; 他们最初就是两级，测试能跑，生产翻车：中间件解析要求把某个热门中间件的所有流集中到一台实例，热点应用的实例成了热节点；而数据富化（查外部存储拿健康状态和元数据）又意味着最忙的实例同时 I/O 最重。拆成三级后，解析（计算密集）和富化持久化（I/O 密集）隔离，每条流被分发、解析、再分发、最后持久化——&lt;strong&gt;负载被两轮哈希打散&lt;/strong&gt;，即使中间件流量是平均值的 100 倍，也没有单实例成为瓶颈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么 SSE 而不是 gRPC？&lt;/strong&gt; 最初用的 gRPC，结果序列化开销、连接池管理、流式响应的内存压力吃掉的 CPU 比业务逻辑还多。消息队列则白白增加基础设施复杂度。SSE 轻量、HTTP 基础、和反应式流的背压天然集成。教训原话：行业最佳实践不普遍适用，测量，别假设。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么 IPC 层一级就够？&lt;/strong&gt; IPC 指标本来就是应用级的，没有中间件要解析；而且数据从源头就按一致性哈希分对了。这带出一条普适原则：&lt;strong&gt;数据的分区方式决定处理架构&lt;/strong&gt;——进来时分对了就直接聚合，分不对（比如网络流需要中间件解析）就得加 shuffle 级。&lt;/p&gt;
&lt;h2 id=&#34;四动态一致性哈希白嫖现有基础设施&#34;&gt;四、动态一致性哈希：白嫖现有基础设施&lt;/h2&gt;
&lt;p&gt;ASG 动态扩缩容时怎么决定哪个实例收哪个 aggregator？传统做法假设静态集群，扩缩容要显式再平衡、协调服务或手工搬数据。他们的做法：每个实例查服务注册中心拿当前健康的实例列表，排序（保证所有实例视图一致），然后用 &lt;code&gt;findOwnerInstance(aggregator.primaryKey)&lt;/code&gt; 路由。注册中心本来就为健康检查跟踪着 ASG 成员，拿它当集群成员的事实来源，&lt;strong&gt;动态成员资格是白来的&lt;/strong&gt;。一致性哈希保证成员变化时大部分 aggregator 留在原实例，排序列表保证视图一致。扩容时新实例立即接到自己那份流量，部署时 aggregator 无缝漂移到健康实例，全程无协调协议、无人工干预。&lt;/p&gt;
&lt;h2 id=&#34;五v1-的四个坑一个连一个&#34;&gt;五、V1 的四个坑，一个连一个&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;坑一：Kafka 消费滞后。&lt;/strong&gt; lag 从秒涨到分钟再到小时。插桩后发现四个原因：分区数对消费组规模偏少、单次 fetch 拉取记录太少、socket 缓冲区没按吞吐调、跨区域读有额外延迟。对症：加分区、调大每次 fetch 的记录数（拿单条延迟换吞吐）、加大接收缓冲。结果 lag 降到峰值时也在一分钟内。&lt;strong&gt;然后新瓶颈暴露了&lt;/strong&gt;：实例自己吃不下更高的摄入速率——修一个瓶颈必然暴露下一个，这是分布式系统的常态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坑二：热点节点与数据放大。&lt;/strong&gt; 最严重的问题。认证、推荐这类热门服务被几百个服务调用，流记录多几个数量级；最初按目的服务做一致性哈希归属，于是拥有热门服务的实例流量是别人的 100 倍。更糟的是洗牌时的数据放大：10 个实例都有本地客户端调某个服务，各自把 aggregator 发给 owner，owner 要合并 10 份。async-profiler 加 heap dump 的结论很清楚：热实例 CPU 大头在 GC，内存压力导致 GC 抖动，抖动吃 CPU，CPU 慢了内存压力更大——恶性循环直到实例宕机，负载转移引发级联故障。解法就是前面那套三级管线：Stage 1 先在内存里做 5 分钟窗口的在线聚合，原始流尽快丢弃被 GC，网络上传的只有压缩后的聚合结果；再经两轮哈希打散。为一个需求（中间件解析）做的架构决策，顺手解了另一个问题（负载不均）——作者说这叫有益的副作用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坑三：内存与 GC。&lt;/strong&gt; 修完热点还有高堆占用。原因有三：对象要等 5 分钟窗口攒完、数据在各级间做不必要的类型转换、以及——最有争议的一条——按 Scala 最佳实践用不可变数据结构做 aggregator，每次更新都造新对象，每秒百万记录下垃圾回收器直接被淹没。务实选择：&lt;strong&gt;热路径上改用可变 aggregator&lt;/strong&gt;（别处保持不可变），堆分配降了 50% 以上，GC 停顿从几百毫秒降到几十毫秒。教训：最佳实践是起点不是戒律，要用测量来背书，并且清醒地知道自己交换了什么。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坑四：反应式流的复杂度。&lt;/strong&gt; Pekko Streams 管线会莫名其妙 stall，没有堆栈可查；&lt;code&gt;.async&lt;/code&gt; 操作符一开始用错，过度并行的流开销大于收益。应对：投入时间真正学懂需求驱动模型、把流图简化为线性流优先、在流边界加监控（缓冲区大小、元素吞吐、背压事件）、团队内部沉淀文档。强力抽象需要投资，别假设自己会了。&lt;/p&gt;
&lt;h2 id=&#34;六v2把能跑的打磨成生产级&#34;&gt;六、V2：把能跑的打磨成生产级&lt;/h2&gt;
&lt;p&gt;V1 解决了架构级问题，V2 是持续优化，模式都一样——生产暴露假设，profiling 定位根因，定点修复：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;aggregator 全程直达&lt;/strong&gt;：之前每级之间都把 aggregator 转成完整图实体，其实下级只需要压缩状态。改为只在 Stage 3 落库前转换一次，砍掉两次中间转换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列化统一 JSON&lt;/strong&gt;：自定义序列化导致难以复现的偶发错误。JSON 比二进制略低效，但人可读让调试容易得多，一致性直接消灭了一整类 bug。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流参数调优&lt;/strong&gt;：继续调并行度、缓冲区、&lt;code&gt;.async&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;：应用健康、归属等元数据在 Stage 3 落库前补齐，避免查询时的 join 开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;七时间旅行任意时刻的拓扑重建&#34;&gt;七、时间旅行：任意时刻的拓扑重建&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;事故发生时调用图长什么样？&amp;ldquo;全量快照存储成本爆炸，事件溯源回放太慢。方案是三件套组合：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;时间窗 aggregator 快照&lt;/strong&gt;：每个 aggregator 带 &lt;code&gt;startTs&lt;/code&gt; 和 &lt;code&gt;endTs&lt;/code&gt;，按 entity_id 加时间戳存进图数据库，相当于每 5 分钟一个检查点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性级变更追踪&lt;/strong&gt;：图数据库只存变化过的属性加时间戳，比全实体拷贝省得多，还提供了窗口边界之内的精度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询时重建&lt;/strong&gt;：查时间范围内的 mutation 历史，按序应用，重建拓扑状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;外加一个聪明设计：&lt;strong&gt;查询时再聚合&lt;/strong&gt;复用摄入时的同一套 aggregator 类，可以做没预计算过的任意 groupby 维度（可用性层级、业务域、部署集群），支持探索式分析而不撑爆存储。&lt;/p&gt;
&lt;h2 id=&#34;八原文给的三条通用教训&#34;&gt;八、原文给的三条通用教训&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规模定性地改变一切&lt;/strong&gt;：每秒 100 次可行的东西在每秒 10 万次失败，变化不是线性的是质变的。不可变结构、单级聚合、gRPC，在小规模下全是对的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次只优化一个瓶颈&lt;/strong&gt;：修 Kafka lag 暴露热点，修热点暴露 GC，修 GC 暴露序列化。这不是失败，是复杂系统的本性。按影响排序，修透一个再修下一个。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分布是扩展性的关键&lt;/strong&gt;：一致性哈希能分散负载，但挡不住数据本身的幂律分布倾斜。倾斜数据在规模下要多级处理、逐级重新分布。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;工程实践联想&#34;&gt;工程实践联想&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;背压是降级策略，不是性能特性。&lt;/strong&gt; 它的价值不在快，在于&amp;quot;慢了也不死不丢&amp;rdquo;。做异步管线时先想清楚：下游慢了，数据去哪？答案如果是&amp;quot;内存里堆着&amp;quot;或&amp;quot;丢掉&amp;rdquo;，早晚要还。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据分区方式决定架构形状。&lt;/strong&gt; IPC 一级聚合、网络流三级聚合，差异全在数据进来时分没分对。设计管线前先问：处理这个 key 需要的数据，天然在一起吗？不在，就认命加 shuffle 级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对&amp;quot;最佳实践&amp;quot;保持工程师式的不敬。&lt;/strong&gt; 不可变数据结构、gRPC、二进制序列化，三个正确常识在这套系统里全被推翻了——但每次推翻都有 profiling 数据背书，且范围克制（可变只用于热路径）。违反常识不可怕，凭感觉违反才可怕。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化是一条链，不是一次战役。&lt;/strong&gt; 修一个瓶颈暴露下一个，这是吞吐上升的必然结果。所以别指望&amp;quot;性能优化专项&amp;quot;一劳永逸，把 profiling 工具链（async-profiler、heap dump、GC 日志）和边界监控变成日常设施才是正解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;白嫖现有基础设施是高级的架构品味。&lt;/strong&gt; 拿服务注册中心当集群成员来源，零协调成本拿到动态一致性哈希。新组件引入前先盘一遍手里已有的东西能替代什么——他们连消息队列都因此省掉了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;本篇小结&#34;&gt;本篇小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心架构&lt;/strong&gt;：流式优先加背压（Kafka 当缓冲池，慢下来但不丢不崩）；网络、IPC、追踪三层物理隔离存储，查询时并行合并。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三级聚合管线&lt;/strong&gt;：Stage 1 时间窗聚合、Stage 2 按中间件 shuffle 并解析双跳、Stage 3 富化持久化。一个设计同时解决中间件解析和热点节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键妥协&lt;/strong&gt;：热路径可变数据结构（堆分配降 50%+）、gRPC 换 SSE、序列化统一 JSON——全是测量驱动的反常识决策。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时间旅行&lt;/strong&gt;：窗口快照加属性级 mutation 加查询时重建，任意时刻拓扑可查，还能查询时再聚合做任意维度 groupby。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方法论&lt;/strong&gt;：规模改变结论；瓶颈是瀑布，修一个露一个；优化是连续过程，测量是唯一指南针。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原文：&lt;a href=&#34;https://netflixtechblog.com/building-service-topology-at-scale-architecture-challenges-and-lessons-learned-f4b792f3f0d8&#34;&gt;Building Service Topology at Scale: Architecture, Challenges, and Lessons Learned&lt;/a&gt;（Netflix TechBlog，作者 Parth Jain、Rakesh Sukumar、Yingwu Zhao、Renzo Sanchez-Silva、Nathan Fisher）。&lt;/li&gt;
&lt;li&gt;文中涉及的关键技术：&lt;a href=&#34;https://pekko.apache.org/&#34;&gt;Apache Pekko&lt;/a&gt;（反应式流实现，Akka fork）、&lt;a href=&#34;https://github.com/async-profiler/async-profiler&#34;&gt;async-profiler&lt;/a&gt;（JVM 采样分析器，热点与 GC 根因定位的主力工具）、&lt;a href=&#34;https://www.reactive-streams.org/&#34;&gt;Reactive Streams&lt;/a&gt;（背压协议规范）。&lt;/li&gt;
&lt;li&gt;系列第一篇介绍多源拓扑的 what 与 why，第三篇将讲追踪层与统一查询，均在 Netflix TechBlog 连载。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Netflix 实时服务拓扑图笔记：把「谁依赖谁」从考古变成活地图</title>
      <link>https://ftmao.com/blog/posts/netflix-service-topology-map/</link>
      <pubDate>Fri, 29 May 2026 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/netflix-service-topology-map/</guid>
      <description>&lt;h1 id=&#34;netflix-实时服务拓扑图笔记把谁依赖谁从考古变成活地图&#34;&gt;Netflix 实时服务拓扑图笔记：把「谁依赖谁」从考古变成活地图&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：&lt;a href=&#34;https://netflixtechblog.com/from-silos-to-service-topology-why-netflix-built-a-real-time-service-map-0165ba13a7bc&#34;&gt;From Silos to Service Topology: Why Netflix Built a Real-Time Service Map&lt;/a&gt;（Netflix Tech Blog，2026-05-29，作者 Parth Jain 等）
目标读者：维护过微服务系统、半夜被告警叫醒过、对着一堆监控面板猜「到底谁挂了」的后端工程师。
阅读目标：搞清楚 Netflix 为什么认为「依赖关系」值得单独立项做一套系统，三路数据源怎么互补，以及那套「网络中间人解析」管线在解决什么问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一问题信号很多地图没有&#34;&gt;一、问题：信号很多，地图没有&lt;/h2&gt;
&lt;p&gt;原文开场是经典的凌晨三点剧本：工程师被 page 醒，某个关键服务错误率升高，用户观影体验受损。在几千个微服务支撑的系统里，这时候要回答的问题其实非常朴素，但谁也答不快。&lt;/p&gt;
&lt;p&gt;Netflix 做了一件值得学的事：&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;li&gt;为什么这个服务在我的指标里显示为「Unknown」？&lt;/li&gt;
&lt;li&gt;最近我的调用链路上有什么变化，能解释这个行为吗？&lt;/li&gt;
&lt;/ul&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;h2 id=&#34;二踩过的坑五条教训&#34;&gt;二、踩过的坑：五条教训&lt;/h2&gt;
&lt;p&gt;Netflix 不是第一次干这件事。原文坦承他们试过外部图数据库、商业平台、各种存储和内部原型，每轮迭代换来一条教训：&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;：能装几千节点的方案，到 Netflix 的服务数量和流量规模会撞墙。&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;/p&gt;
&lt;h2 id=&#34;三核心设计三个真相来源三张物理隔离的图&#34;&gt;三、核心设计：三个真相来源，三张物理隔离的图&lt;/h2&gt;
&lt;p&gt;这是全文最有分量的一个架构决策。Netflix 的结论是：&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 SRC[&amp;#34;三路数据源&amp;#34;]
        EB[&amp;#34;eBPF 网络流&amp;lt;br/&amp;gt;内核态抓真实连接&amp;#34;]
        IPC[&amp;#34;IPC 指标&amp;lt;br/&amp;gt;应用埋点 gRPC GraphQL REST&amp;#34;]
        TR[&amp;#34;端到端追踪&amp;lt;br/&amp;gt;采样后的真实请求路径&amp;#34;]
    end
    subgraph G[&amp;#34;三张物理隔离的图&amp;#34;]
        G1[&amp;#34;网络图&amp;lt;br/&amp;gt;图数据库分区一&amp;lt;br/&amp;gt;覆盖全 但无应用语义&amp;#34;]
        G2[&amp;#34;应用图&amp;lt;br/&amp;gt;图数据库分区二&amp;lt;br/&amp;gt;有端点细节 但只覆盖已埋点服务&amp;#34;]
        G3[&amp;#34;请求图&amp;lt;br/&amp;gt;列式存储&amp;lt;br/&amp;gt;真实行为 但有采样盲区&amp;#34;]
    end
    EB --&amp;gt; G1
    IPC --&amp;gt; G2
    TR --&amp;gt; G3
    G1 --&amp;gt; U[&amp;#34;统一视图&amp;lt;br/&amp;gt;并行查询三路 合并节点与边&amp;lt;br/&amp;gt;亚秒级返回&amp;#34;]
    G2 --&amp;gt; U
    G3 --&amp;gt; U
    style U fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三路的价值与短板正好互补：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;eBPF 网络流（网络层）&lt;/strong&gt;：在内核层抓连接记录，&lt;strong&gt;不管应用有没有埋点，每个服务都在图上&lt;/strong&gt;——这是完整性的地基。短板是没有应用上下文：你知道 A 连了 B 的 IP 和协议，但不知道调的是哪个 API 端点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPC 指标（应用层）&lt;/strong&gt;：埋点服务发出的进程间通信指标，能看到具体端点、错误率、延迟分布、协议细节。短板是&lt;strong&gt;只覆盖做了埋点的服务&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;端到端追踪（请求层）&lt;/strong&gt;：不是「A 可以调 B」，而是「A 在处理这个具体请求时确实调了 B」，能捕获条件分支和特性开关带来的真实运行时行为。短板是&lt;strong&gt;采样&lt;/strong&gt;——冷门代码路径在聚合视图里可能看不见。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工程实现上有个细节值得注意：三张图&lt;strong&gt;物理隔离&lt;/strong&gt;——网络层和 IPC 层各在图数据库的一个分区，追踪层用列式存储（为分析型查询优化）。隔离换来两个好处：各层独立演进，查询时可以三路并行、最后合并结果，即使三层全合并也能做到亚秒返回。&lt;/p&gt;
&lt;p&gt;这个设计翻译过来就是一句话：&lt;strong&gt;完整性靠网络流兜底，语义靠 IPC 补齐，行为真实性靠追踪验证&lt;/strong&gt;。任何单源方案都在这三者里缺一角。&lt;/p&gt;
&lt;h2 id=&#34;四数据管线三级聚合与中间人解析&#34;&gt;四、数据管线：三级聚合与「中间人解析」&lt;/h2&gt;
&lt;p&gt;架构层面，流日志从 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;flowchart LR
    K[&amp;#34;多区域 Kafka&amp;lt;br/&amp;gt;持续消费流日志&amp;#34;] --&amp;gt; P[&amp;#34;Pekko Streams&amp;lt;br/&amp;gt;分布式处理 自动分区&amp;lt;br/&amp;gt;天然背压&amp;#34;]
    P --&amp;gt; S1[&amp;#34;第一级 初始聚合&amp;lt;br/&amp;gt;从 Kafka 进来的原始流&amp;#34;]
    S1 --&amp;gt; S2[&amp;#34;第二级 中间人解析&amp;lt;br/&amp;gt;识别 LB NAT 网关 代理&amp;lt;br/&amp;gt;把两跳拼回一跳&amp;#34;]
    S2 --&amp;gt; S3[&amp;#34;第三级 最终聚合&amp;lt;br/&amp;gt;叠加健康状态&amp;#34;]
    S3 --&amp;gt; DB[&amp;#34;图数据库&amp;lt;br/&amp;gt;建在分布式 KV 之上&amp;lt;br/&amp;gt;高吞吐多跳遍历&amp;#34;]
    DB --&amp;gt; API[&amp;#34;gRPC API&amp;lt;br/&amp;gt;多跳遍历 按层过滤&amp;lt;br/&amp;gt;分页 亚秒响应&amp;#34;]
    style S2 fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个点值得展开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;处理框架用 Apache Pekko Streams&lt;/strong&gt;（Akka 的 fork），看重的是分布式容错、自动把负载分区到 Auto Scaling Group、以及响应式流自带的背压处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三级聚合里最聪明的是第二级&lt;/strong&gt;。网络流日志记录的是逐跳的物理连接：&lt;code&gt;App A → 负载均衡器 → App B&lt;/code&gt;，或者经过 NAT 网关。工程师要的不是这个，是 &lt;code&gt;App A → App B&lt;/code&gt; 这条应用级直连边。第二级负责识别出负载均衡器、NAT 网关、API 网关、代理这些「网络中间人」，把它们的入向流和出向流拼起来，还原出真正的应用到应用路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分三级还有个副作用：防热点&lt;/strong&gt;。当某些应用或中间人的流量是其他节点的上百倍时，把聚合分散到多个阶段执行，热点不会压垮单点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;查询侧是 gRPC API：多跳遍历、按可用性等级（Tier 0、Tier 1 之类）和业务域过滤、大结果集分页、亚秒响应。UI 和 API 并重——&lt;strong&gt;工程师用界面排查，自动化系统（韧性框架、爆炸半径计算器、事故响应机器人）走 API&lt;/strong&gt;。&lt;/p&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;/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;：Platform Modernization 团队用它验证直播关键服务的整条依赖链都打了正确的可用性等级标记——拓扑成了合规校验的数据源。&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;p&gt;最后这条「时间旅行」是我觉得最有味道的：事故排查的黄金问题「&lt;strong&gt;故障开始前，我的依赖图变了什么&lt;/strong&gt;」终于被变成一条查询，而不是靠翻部署记录和记忆。&lt;/p&gt;
&lt;h2 id=&#34;六活的地图和下一步&#34;&gt;六、活的地图，和下一步&lt;/h2&gt;
&lt;p&gt;Netflix 反复强调「living map」这个词：新调用关系出现就近实时上图，调用停止的边会从图里淡出，部署改变行为就改变图。关键承诺是——&lt;strong&gt;工程师可以相信图上画的就是现实，而不是某个人对架构的想象&lt;/strong&gt;。&lt;/p&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;strong&gt;拓扑图是这类智能自动化的知识图谱地基&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第三条值得划线：他们不是在说「接个 LLM 上去问答」，而是在说「先把依赖关系建成结构化、实时、可查询的图，智能体才有爬的地面」。这个顺序是对的。&lt;/p&gt;
&lt;h2 id=&#34;七工程实践联想&#34;&gt;七、工程实践联想&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;立项之前先做求助记录考古&lt;/strong&gt;。Netflix 拿四年的支持工单说话，高频问题清单就是需求文档。多数内部平台立项靠的是平台团队自己的痛感，而不是用户的——「我觉得你需要」和「数据说你在问」做出来的东西，采纳率天差地别。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「信号够多」不等于「可观测」&lt;/strong&gt;。指标、日志、追踪三件套回答的都是单点或单次的问题，依赖关系这种结构性知识需要单独建模、单独存储。如果你的团队排查故障还在靠「老张知道这条链路」，那就是缺这一层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多源互补是处理「讲不全」问题的标准答案&lt;/strong&gt;。任何单一采集手段都有盲区：埋点有覆盖率问题，内核抓包有语义问题，采样有冷门路径问题。与其追求一个完美的源，不如让几个不完美的源互相兜底——这个思路对做 AI Agent 的上下文采集同样成立。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「中间人解析」是被低估的脏活&lt;/strong&gt;。基础设施里到处是 LB、网关、代理，原始数据永远带着这些噪音。谁能把物理路径还原成逻辑路径，谁的数据才可行动。做链路分析、做 RAG 的数据清洗，都会遇到同构的问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图建好了，Agent 才有事可做&lt;/strong&gt;。Netflix 把自动根因分析放在路线图的末尾而不是开头，因为智能体的前提是有一张实时、准确、可编程查询的图。这对「AI + 运维」的喧嚣是一盆冷水：先修地基，再谈智能。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;本篇小结&#34;&gt;本篇小结&lt;/h2&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;：三路数据源各建一图、物理隔离、并行查询后合并——eBPF 网络流保完整性，IPC 指标保应用语义，分布式追踪保真实行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管线&lt;/strong&gt;：多区域 Kafka → Pekko Streams → 三级聚合（核心是第二级的网络中间人解析，把 LB/NAT/代理的两跳还原成应用级直连边）→ 图数据库（建在分布式 KV 上）→ 亚秒级 gRPC API。&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;：这是系列第一篇，下一篇讲规模化落地的工程硬题——Kafka 消费延迟、GC 停顿、响应式流调试、热点治理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://netflixtechblog.com/from-silos-to-service-topology-why-netflix-built-a-real-time-service-map-0165ba13a7bc&#34;&gt;From Silos to Service Topology: Why Netflix Built a Real-Time Service Map&lt;/a&gt;（Netflix Technology Blog，2026-05-29，原文）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ebpf.io/&#34;&gt;eBPF 官方文档&lt;/a&gt;（内核态可编程观测技术，网络层数据源的基础）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pekko.apache.org/&#34;&gt;Apache Pekko&lt;/a&gt;（Akka 的开源 fork，文中分布式流处理框架）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://opentelemetry.io/docs/concepts/signals/traces/&#34;&gt;OpenTelemetry Tracing&lt;/a&gt;（分布式追踪的通用概念背景，文中请求层数据源对应物）&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《设计数据密集型应用》学习总结：存量、流量、回路——每个设计决策都是三者的权衡</title>
      <link>https://ftmao.com/blog/bookstore/ddia-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/ddia-book-notes/</guid>
      <description>&lt;h1 id=&#34;设计数据密集型应用学习总结存量流量回路每个设计决策都是三者的权衡&#34;&gt;《设计数据密集型应用》学习总结：存量、流量、回路——每个设计决策都是三者的权衡&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写过 CRUD、用过 MySQL/Redis/Kafka，但说不清它们&amp;quot;为什么这么设计&amp;quot;的工程师。&lt;br&gt;
参考书籍：Martin Kleppmann《Designing Data-Intensive Applications》（DDIA，那只野猪书）&lt;br&gt;
阅读目标：不背目录，而是拿到一个能套在任何存储/中间件上的分析框架——存量、流量、回路。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面别背组件背权衡&#34;&gt;0. 写在前面：别背组件，背权衡&lt;/h2&gt;
&lt;p&gt;市面上的&amp;quot;中间件教程&amp;quot;有个通病：教你 MySQL 怎么用、Redis 怎么用、Kafka 怎么用，但从不告诉你&lt;strong&gt;为什么它们长得这么不一样&lt;/strong&gt;。同样是存数据，Redis 敢把数据放内存，MySQL 不敢；同样是日志，Kafka 能当数据库用，而大多数人只拿它当管道。&lt;/p&gt;
&lt;p&gt;DDIA 这本书最值钱的地方，就是把这些&amp;quot;为什么&amp;quot;串成了一条线。读完我把它提炼成三个词：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;存量（State）&lt;/strong&gt;：数据怎么&lt;strong&gt;存&lt;/strong&gt;——存储引擎（LSM/B-Tree）、编码格式、行存列存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流量（Flow）&lt;/strong&gt;：数据怎么&lt;strong&gt;动&lt;/strong&gt;——复制、分区、派生数据、流处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回路（Loop）&lt;/strong&gt;：系统怎么&lt;strong&gt;自我纠错&lt;/strong&gt;——事务、隔离级别、共识、线性一致性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话理解：&lt;strong&gt;任何一个数据系统的设计决策，都是在存量成本、流量吞吐、回路强度三者之间做取舍。&lt;/strong&gt; 你要强一致（回路强），就得牺牲可用性或延迟（流量贵）；你要写入快（流量大），就得接受读时合并或后台压缩的代价（存量结构变复杂）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 0&lt;/strong&gt;：读完这本书最大的误区是&amp;quot;上来就选最强的一致性&amp;quot;。Kleppmann 自己说过大意如此的话：一致性模型的选择应该由&lt;strong&gt;业务能不能容忍异常&lt;/strong&gt;决定，而不是由工程师的洁癖决定。能容忍就用最终一致，性能白捡。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;和博客已有的 Redis/MySQL/MQ 笔记是&amp;quot;原理 vs 用法&amp;quot;的关系：那些笔记讲&amp;quot;这个命令怎么用、这个参数怎么调&amp;quot;，这篇讲&amp;quot;这个组件为什么被设计成这样&amp;quot;。建议搭配阅读——看完这篇再回头看 Redis 的 RDB/AOF、MySQL 的 InnoDB redo log、Kafka 的副本协议，会有&amp;quot;原来如此&amp;quot;的感觉。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一部分-存量数据怎么存&#34;&gt;第一部分 存量：数据怎么存&lt;/h2&gt;
&lt;h3 id=&#34;1-lsm-tree-vs-b-tree写的快和读的快不可兼得&#34;&gt;1. LSM-Tree vs B-Tree：写的快和读的快，不可兼得&lt;/h3&gt;
&lt;p&gt;数据库索引结构就两大流派，区别一句话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;B-Tree&lt;/strong&gt;：读的时候顺藤摸瓜，树的高度就是磁盘寻道次数。写入是&amp;quot;就地更新&amp;quot;——找到那一页，改掉。MySQL InnoDB 就是这个。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSM-Tree&lt;/strong&gt;：写入只往内存里的有序结构（Memtable）追加，内存写满了就整体刷到磁盘变成一个 &lt;strong&gt;SSTable&lt;/strong&gt;（Sorted String Table，一段有序的文件）。读取时从最新的 SSTable 往最旧的依次找。LevelDB、RocksDB、Cassandra、HBase 都是这个。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SSTable 合并（compaction）是 LSM 的灵魂&lt;/strong&gt;。因为同一个 key 的新旧版本散落在多个 SSTable 里，后台必须定期把它们归并排序、去重、丢掉被删除的墓碑标记，否则文件越积越多，读放大失控。&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
    W[&amp;#34;写入请求&amp;#34;] --&amp;gt; L[&amp;#34;WAL 追加日志&amp;lt;br/&amp;gt;（崩溃恢复用）&amp;#34;]
    L --&amp;gt; M[&amp;#34;Memtable&amp;lt;br/&amp;gt;（内存有序表）&amp;#34;]
    M --&amp;gt;|&amp;#34;写满阈值&amp;#34;| S1[&amp;#34;SSTable 1&amp;lt;br/&amp;gt;（不可变文件）&amp;#34;]
    M --&amp;gt;|&amp;#34;再写满&amp;#34;| S2[&amp;#34;SSTable 2&amp;#34;]
    M --&amp;gt;|&amp;#34;又写满&amp;#34;| S3[&amp;#34;SSTable 3&amp;#34;]
    S1 --&amp;gt; C[&amp;#34;后台 Compaction&amp;lt;br/&amp;gt;归并排序 + 去重 + 清墓碑&amp;#34;]
    S2 --&amp;gt; C
    S3 --&amp;gt; C
    C --&amp;gt; S4[&amp;#34;合并后的大 SSTable&amp;lt;br/&amp;gt;（旧文件丢弃）&amp;#34;]
    R[&amp;#34;读取请求&amp;#34;] --&amp;gt; M
    R --&amp;gt; S3
    R --&amp;gt; S2
    R --&amp;gt; S1
    R --&amp;gt;|&amp;#34;配合布隆过滤器&amp;lt;br/&amp;gt;跳过不含该 key 的文件&amp;#34;| MISS[&amp;#34;快速判不存在&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;小数字例子感受写放大：假设每写 1 GB 数据，经过 3 层 compaction，每层都要重写一遍，实际磁盘写入可能是 5~10 GB。这就是&amp;quot;写放大&amp;quot;，也是 RocksDB 调优的核心战场。&lt;/p&gt;
&lt;p&gt;LSM 读取慢怎么补救？靠&lt;strong&gt;布隆过滤器（Bloom filter）&lt;/strong&gt;：一个很小的内存位图，能告诉你&amp;quot;这个 key 肯定不在这个 SSTable 里&amp;quot;（可能有误报，但绝不漏报）。查一个不存在的 key 时，布隆过滤器直接拦掉，不用真的去翻磁盘文件——这对&amp;quot;查缓存未命中再查库&amp;quot;的场景是救命优化。Cassandra、HBase、RocksDB 全都内置。&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 style=&#34;text-align: left&#34;&gt;维度&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;B-Tree&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;LSM-Tree&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;写入&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;就地更新，写放大相对低但随机 IO&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;顺序追加，吞吐高但有 compaction 写放大&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;读取&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;稳定，一次查找几层树&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;要查多层文件，靠布隆过滤器和缓存补救&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;磁盘碎片&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;页分裂会产生碎片&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;文件整体重写，天然紧凑&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;典型代表&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;InnoDB、PostgreSQL&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;RocksDB、Cassandra、HBase&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：B-Tree 是&amp;quot;图书馆书架&amp;quot;，书按位置摆好，找书快、插书要挪位置；LSM 是&amp;quot;先记在便签上，攒一批再上架&amp;quot;，记便签飞快，但定期要整理书架，找书得翻几摞便签。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;2-wal所有存储引擎的保命符&#34;&gt;2. WAL：所有存储引擎的&amp;quot;保命符&amp;quot;&lt;/h3&gt;
&lt;p&gt;LSM 写 Memtable、B-Tree 改数据页，都是先动内存。断电怎么办？答案是 &lt;strong&gt;WAL（Write-Ahead Log，预写日志）&lt;/strong&gt;：&lt;strong&gt;任何修改先追加一条日志到磁盘，再改内存结构&lt;/strong&gt;。崩溃后重放日志恢复。&lt;/p&gt;
&lt;p&gt;这个思想无处不在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;InnoDB 的 redo log 是 WAL；&lt;/li&gt;
&lt;li&gt;PostgreSQL 的 WAL 直接就叫 WAL；&lt;/li&gt;
&lt;li&gt;Redis 的 AOF 本质也是 WAL；&lt;/li&gt;
&lt;li&gt;Kafka 的消息日志、ZooKeeper 的事务日志，全是同一个套路。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：WAL 就是把&amp;quot;随机写&amp;quot;变成&amp;quot;顺序写&amp;quot;——磁盘顺序追加比随机写快几个数量级，顺便白捡了崩溃恢复能力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;3-行存-vs-列存oltp-与-olap-的分水岭&#34;&gt;3. 行存 vs 列存：OLTP 与 OLAP 的分水岭&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;行存&lt;/strong&gt;：一行的所有列连续存放。适合&amp;quot;查一个人的全部信息&amp;quot;（点查、事务），即 OLTP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;列存&lt;/strong&gt;：一列的所有值连续存放。适合&amp;quot;统计一百万人的平均年龄&amp;quot;（聚合、分析），即 OLAP。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么列存分析快？小数字例子：一张 100 列的表，分析查询只用 5 列。行存要把 100 列全部从磁盘读出来再丢弃 95 列；列存只读那 5 列，IO 直接省 95%。而且单列数据类型相同，压缩率极高（比如&amp;quot;性别&amp;quot;列去重后就两个值，位图编码一下几乎不占空间）。&lt;/p&gt;
&lt;p&gt;代表：Parquet、ORC、ClickHouse、DuckDB 是列存；MySQL、PostgreSQL 是行存。Snowflake、BigQuery 这类数仓全部站在列存一边。&lt;/p&gt;
&lt;h3 id=&#34;4-编码与演进-schema-变了老数据怎么办&#34;&gt;4. 编码与演进： schema 变了，老数据怎么办&lt;/h3&gt;
&lt;p&gt;存量还有个隐藏维度：&lt;strong&gt;数据格式会随时间演化&lt;/strong&gt;。JSON 灵活但没约束；Protocol Buffers / Thrift / Avro 这类二进制编码通过&lt;strong&gt;字段编号&lt;/strong&gt;实现前后兼容——新增字段给新编号，删掉字段不动旧编号，新老代码就能和平共处。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 1&lt;/strong&gt;：滚动升级时&amp;quot;新代码读老数据&amp;quot;和&amp;quot;老代码读新数据&amp;quot;会同时发生。所以兼容性必须是&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;p&gt;单台机器存不下、扛不住、会宕机——这是&amp;quot;流量篇&amp;quot;要解决的三个问题。对应三板斧：&lt;strong&gt;复制&lt;/strong&gt;（同一份数据放多台机器）、&lt;strong&gt;分区&lt;/strong&gt;（不同数据放不同机器）、&lt;strong&gt;派生数据&lt;/strong&gt;（数据从一个系统流向另一个系统）。&lt;/p&gt;
&lt;h3 id=&#34;5-复制主从多主无主三种政治体制&#34;&gt;5. 复制：主从、多主、无主，三种政治体制&lt;/h3&gt;
&lt;p&gt;复制的目标是可用性和读扩展，但引入了全书最核心的矛盾：&lt;strong&gt;副本之间怎么保持一致&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主从复制（单主）&lt;/strong&gt;：所有写请求走 Leader，Leader 把变更日志同步给 Follower。读可以分摊到 Follower。简单、直觉，MySQL、PostgreSQL、MongoDB（副本集）、Kafka 分区副本都是这个思路。&lt;/p&gt;
&lt;p&gt;但&amp;quot;Follower 的数据是旧版本&amp;quot;这件事，会带来一连串用户可感知的诡异现象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;你刚发的朋友圈自己刷新后消失了&lt;/strong&gt; → 需要&lt;strong&gt;读己之写（read-your-writes）&lt;/strong&gt;：用户写完之后，读请求必须走 Leader 或带版本号校验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;你的余额从 100 变成 80 又变回 100&lt;/strong&gt;（读到了更旧的副本）→ 需要&lt;strong&gt;单调读（monotonic reads）&lt;/strong&gt;：一个用户的连续读请求固定打到同一个副本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;聊天里先看到回复再看到原帖&lt;/strong&gt;（分区的数据流向不同副本速度不同）→ 需要&lt;strong&gt;一致前缀读（consistent prefix reads）&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;sequenceDiagram
    participant C as 客户端
    participant L as Leader（主库）
    participant F1 as Follower 1（同步副本）
    participant F2 as Follower 2（异步副本）

    C-&amp;gt;&amp;gt;L: 写 x = 1
    L-&amp;gt;&amp;gt;L: 写本地 WAL 并提交
    L-&amp;gt;&amp;gt;F1: 同步复制：等 F1 确认
    F1--&amp;gt;&amp;gt;L: ACK
    L--&amp;gt;&amp;gt;C: 写入成功
    L-&amp;gt;&amp;gt;F2: 异步复制：后台慢慢发
    Note over F2: 此时 F2 还是旧值 x = 0&amp;lt;br/&amp;gt;复制延迟窗口
    C-&amp;gt;&amp;gt;F2: 读 x
    F2--&amp;gt;&amp;gt;C: x = 0（读到旧数据！）
    Note over C,F2: 这就是&amp;#34;读己之写&amp;#34;被破坏的瞬间&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;同步 vs 异步是个硬权衡&lt;/strong&gt;：全同步，任何一个副本宕机写入就卡死；全异步，Leader 宕机时没同步完的写入直接丢失。实践中的折中是&amp;quot;半同步&amp;quot;——一个副本同步、其余异步（MySQL 的 semi-sync 就是这个）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多主复制&lt;/strong&gt;：多个节点都能接受写，彼此同步。适合多数据中心就近写入，但&lt;strong&gt;写冲突&lt;/strong&gt;是噩梦——两个机房同时改同一条数据，谁说了算？常见解法：LWW（最后写入者胜，靠时间戳，会丢数据）、CRDT（无冲突数据结构）、应用层解决。CouchDB 走这条路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;无主复制（Dynamo 风格）&lt;/strong&gt;：没有 Leader，写入时客户端把数据发给 N 个副本中的任意 W 个，读的时候从 R 个里读，只要 &lt;strong&gt;W + R &amp;gt; N&lt;/strong&gt; 就能保证读到最新值（法定人数，quorum）。经典参数 N=3, W=2, R=2。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;经典案例：Amazon Dynamo 论文与购物车&lt;/strong&gt;。2007 年 Amazon 发表 Dynamo 论文，动机极其务实：&lt;strong&gt;购物车绝不能拒绝写入&lt;/strong&gt;——用户加购物车被拒，就是真金白银的损失。所以 Dynamo 选择永远可写（AP），哪怕网络分区时也接受写入，事后再合并冲突版本（著名的&amp;quot;向量时钟&amp;quot;就出自这篇论文）。这篇论文直接催生了 Riak、Cassandra、Voldemort 一整代 NoSQL。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：单主是&amp;quot;独裁&amp;quot;，多主是&amp;quot;联邦&amp;quot;，无主是&amp;quot;全民投票&amp;quot;。越民主写入越自由，但事后摆平分歧的成本越高。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;6-分区数据切蛋糕的两种刀法&#34;&gt;6. 分区：数据切蛋糕的两种刀法&lt;/h3&gt;
&lt;p&gt;单机存不下就分区（分片）。两种切法：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;按 key 范围分区&lt;/strong&gt;：字典式切分，A&lt;del&gt;M 一个分区、N&lt;/del&gt;Z 一个分区。范围查询（&amp;ldquo;查 7 月的所有订单&amp;rdquo;）天然高效，但&lt;strong&gt;热点&lt;/strong&gt;问题明显——如果 key 是时间戳，所有新写入全砸在最后一个分区上。Bigtable、HBase 用这套。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;按键的哈希分区&lt;/strong&gt;：key 过一遍哈希函数打散。负载均匀，但范围查询要 scatter-gather 到所有分区，效率骤降。Cassandra、DynamoDB 用这套（Cassandra 实际上是&amp;quot;哈希定分区 + 分区内按 key 排序&amp;quot;的混合体）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;再平衡（rebalancing）&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 扩容前[&amp;#34;扩容前：3 节点，每节点 4 个虚拟分区&amp;#34;]
        N1[&amp;#34;节点 A&amp;lt;br/&amp;gt;v0 v1 v2 v3&amp;#34;]
        N2[&amp;#34;节点 B&amp;lt;br/&amp;gt;v4 v5 v6 v7&amp;#34;]
        N3[&amp;#34;节点 C&amp;lt;br/&amp;gt;v8 v9 v10 v11&amp;#34;]
    end
    subgraph 扩容后[&amp;#34;扩容后：新节点 D 加入&amp;lt;br/&amp;gt;只搬走 3 个虚拟分区，其余不动&amp;#34;]
        N1b[&amp;#34;节点 A&amp;lt;br/&amp;gt;v0 v1 v2&amp;#34;]
        N2b[&amp;#34;节点 B&amp;lt;br/&amp;gt;v4 v5 v6&amp;#34;]
        N3b[&amp;#34;节点 C&amp;lt;br/&amp;gt;v8 v9 v10&amp;#34;]
        N4[&amp;#34;节点 D&amp;lt;br/&amp;gt;v3 v7 v11&amp;lt;br/&amp;gt;（从 A/B/C 各搬一个）&amp;#34;]
    end
    扩容前 --&amp;gt;|&amp;#34;固定数量虚拟分区策略&amp;lt;br/&amp;gt;只迁移少量 vNode&amp;#34;| 扩容后&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;主流策略就是图里这个：&lt;strong&gt;固定数量的虚拟分区&lt;/strong&gt;（vNode）——分区数远多于节点数，加节点时从每个老节点&amp;quot;匀&amp;quot;几个虚拟分区过来。Riak、Cassandra 都这么干。反例是取模哈希（hash mod N）：节点数一变，几乎全部 key 都要搬家，这就是一致性哈希要解决的原始问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 2&lt;/strong&gt;：分区键一旦选定，几乎不可更改。用 user_id 做分区键的系统，后来想做&amp;quot;按时间范围全局扫描&amp;quot;，会发现每个查询都要打满全部分区。选分区键时想清楚：&lt;strong&gt;你的主要查询模式是什么，热点在哪&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;7-派生数据一个数据系统的输出是另一个系统的输入&#34;&gt;7. 派生数据：一个数据系统的输出，是另一个系统的输入&lt;/h3&gt;
&lt;p&gt;Kleppmann 有个非常锋利的视角：&lt;strong&gt;缓存、搜索索引、数据仓库，本质上都是派生数据&lt;/strong&gt;——它们都是从某个&amp;quot;真相源&amp;quot;（source of truth）加工出来的冗余副本。&lt;/p&gt;
&lt;p&gt;一旦接受这个设定，很多架构问题豁然开朗：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存和数据库不一致？因为派生数据没有和源数据同步更新。&lt;/li&gt;
&lt;li&gt;ES 里的搜索结果和 MySQL 对不上？同理。&lt;/li&gt;
&lt;li&gt;数仓报表延迟一天？因为派生管道是 T+1 的批处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是问题从&amp;quot;怎么让缓存不失效&amp;quot;变成&amp;quot;&lt;strong&gt;派生管道怎么设计&lt;/strong&gt;&amp;quot;，这正是流处理要回答的，下文第 10 节展开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第三部分-回路系统怎么自我纠错&#34;&gt;第三部分 回路：系统怎么自我纠错&lt;/h2&gt;
&lt;p&gt;存量和流量解决了&amp;quot;存得下、流得动&amp;quot;，剩下的问题是：&lt;strong&gt;当多个客户端并发操作、机器会宕机、网络会分区时，系统凭什么保证数据不出错？&lt;/strong&gt; 这就是回路——一致性保障与反馈机制。&lt;/p&gt;
&lt;h3 id=&#34;8-事务与隔离级别用弱隔离换性能代价是异常&#34;&gt;8. 事务与隔离级别：用&amp;quot;弱隔离&amp;quot;换性能，代价是异常&lt;/h3&gt;
&lt;p&gt;事务（ACID）是数据库给程序员的最大善意：&lt;strong&gt;把一堆操作打包成原子单元，要么全成要么全不成，并发的麻烦我来扛&lt;/strong&gt;。但&amp;quot;我来扛&amp;quot;是有价格的——严格串行执行性能太差，于是数据库发明了隔离级别这个&amp;quot;滑杆&amp;quot;，让你自己选扛多少：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;隔离级别&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;防住的异常&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;防不住的异常&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;谁在用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;读未提交&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;几乎啥都不防&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;脏读、一切&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;基本没人用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;读提交（RC）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;脏读、脏写&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;不可重复读、写倾斜&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;Oracle、PostgreSQL 的默认&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;快照隔离（SI）&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;上面所有 + 不可重复读&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;写倾斜&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;PostgreSQL 的 RR、MySQL InnoDB 的 RR&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;串行化&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;一切&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;无&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;性能最好时的梦想&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;快照隔离（Snapshot Isolation）&lt;/strong&gt; 值得单独说，因为它是现代数据库的主力。原理是 MVCC（多版本并发控制）：每次写入不覆盖旧值，而是生成新版本，每个事务看到&amp;quot;它开始那一刻&amp;quot;的数据快照。读不阻塞写、写不阻塞读，性能极佳。InnoDB 的 undo log、PostgreSQL 的多版本堆表，都是 MVCC 实现。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;写倾斜（write skew）是快照隔离最大的盲区&lt;/strong&gt;，也是面试和实战最容易踩的坑。小例子：医院排班系统要求&amp;quot;至少有一名医生值班&amp;quot;，当前有 Alice 和 Bob 两人值班。两人同时点&amp;quot;请假&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alice 的事务读到&amp;quot;2 人值班&amp;quot;，判断&amp;quot;我请假后还剩 1 人，OK&amp;quot;，提交；&lt;/li&gt;
&lt;li&gt;Bob 的事务同样读到&amp;quot;2 人值班&amp;quot;（快照！他看不到 Alice 未提交的修改），同样判断 OK，提交；&lt;/li&gt;
&lt;li&gt;结果：两人都不值班了，约束被破坏。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意这不是脏读也不是不可重复读——每个事务内部看到的数据完全一致，但&lt;strong&gt;两个事务基于同一份过期前提做出了互相冲突的决定&lt;/strong&gt;。解法只有：升级到串行化，或者用 &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; 物化冲突（把隐含的读前提变成显式行锁）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 3&lt;/strong&gt;：凡是有&amp;quot;读取一个约束条件，然后据此决定写入&amp;quot;模式的代码（库存扣减、名额抢占、余额校验），在 RR/快照隔离下都可能写倾斜。别指望 ORM 救你，老老实实 &lt;code&gt;FOR UPDATE&lt;/code&gt; 或上串行化。&lt;/p&gt;
&lt;/blockquote&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;：单线程跑事务（Redis 单线程模型、VoltDB），简单暴力，内存够快时性能惊人；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两阶段锁（2PL）&lt;/strong&gt;：读写都加锁，性能差但正确，MySQL 的串行化级别就是这么实现的，也是死锁的主要来源；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可串行化快照隔离（SSI）&lt;/strong&gt;：快照隔离之上检测危险读写依赖，有冲突就中止重试。PostgreSQL 9.1+ 的串行化级别用的就是它，是近几十年事务领域最重要的学术成果落地。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;9-共识与线性一致性分布式系统的终极回路&#34;&gt;9. 共识与线性一致性：分布式系统的&amp;quot;终极回路&amp;quot;&lt;/h3&gt;
&lt;p&gt;事务解决的是&amp;quot;单机/单系统内的并发正确性&amp;quot;，共识（consensus）解决的是更难的问题：&lt;strong&gt;一群会宕机、消息会延迟的机器，怎么对&amp;quot;某件事&amp;quot;达成一致&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;先纠正一个常见混淆：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线性一致性（Linearizability）&lt;/strong&gt;：让系统表现得&amp;quot;像只有一份数据&amp;quot;——任何人读到 x=1 之后，不可能再读到 x=0。它是对&lt;strong&gt;单个对象&lt;/strong&gt;的读写一致性承诺。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共识（Consensus）&lt;/strong&gt;：多个节点对&lt;strong&gt;某个值/某条决策&lt;/strong&gt;达成不可逆的一致，典型算法 Paxos、Raft、Zab。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者的关系：要实现线性一致的存储，背后几乎必然要靠共识算法选主或定序。ZooKeeper（Zab）、etcd（Raft）就是共识系统的工业代表，整个 Kubernetes 的正确性底座就是 etcd 里的那套 Raft。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;经典案例：Google Spanner 与 TrueTime&lt;/strong&gt;。Spanner 要在全球范围提供&lt;strong&gt;外部一致性&lt;/strong&gt;（比线性一致更强，跨事务的全局顺序），最大的拦路虎是：分布式系统里各机器时钟不可靠，你没法靠时间戳判断两个事务谁先谁后。Google 的解法粗暴而硬核——&lt;strong&gt;给数据中心装 GPS 接收机和原子钟&lt;/strong&gt;，把时钟误差压缩到一个已知的小区间（TrueTime API 返回的不是一个时间点，而是&amp;quot;真实时间必然落在的这个区间&amp;quot;）。提交事务时，如果发现误差区间还没过去，就&lt;strong&gt;故意等待&lt;/strong&gt;（commit wait）直到确定。用硬件换确定性，用等待换正确性——这是&amp;quot;回路强度可以花钱买&amp;quot;的最著名示范。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;FLP 不可能定理&lt;/strong&gt;泼一盆冷水：在纯异步网络里，哪怕只有一台机器可能崩溃，也不存在&lt;strong&gt;确定性&lt;/strong&gt;的、保证能终止的共识算法。工程上的出路是引入&amp;quot;部分同步&amp;quot;假设（网络总会恢复）和超时机制——所以 Raft/Paxos 的正确性不依赖时钟，但&lt;strong&gt;活性&lt;/strong&gt;（liveness，能不能往前走）依赖。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;极端案例：比特币与拜占庭容错&lt;/strong&gt;。上面说的共识算法都假设节点&amp;quot;可能宕机但不会撒谎&amp;quot;（崩溃容错 CFT）。比特币面对的是更恶劣的环境：&lt;strong&gt;节点可能作恶&lt;/strong&gt;（拜占庭将军问题）。中本聪的解法是放弃确定性共识，改用工作量证明（PoW）+ 最长链规则做&lt;strong&gt;概率性共识&lt;/strong&gt;——一笔交易等 6 个区块确认，被推翻的概率指数级下降但永不为零。性能极差（比特币全网约 7 TPS，对比 Visa 峰值数万 TPS），换来的是&amp;quot;无许可环境下没人能篡改历史&amp;quot;。这是把回路强度推到极限、把流量吞吐牺牲殆尽的极端案例，也完美印证了我们的框架：&lt;strong&gt;一切都是权衡&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;共识落地前，还有个更现实的问题：故障检测&lt;/strong&gt;。分布式系统判断&amp;quot;某节点死了&amp;quot;的唯一办法是超时——但超时无法区分&amp;quot;节点真死了&amp;quot;和&amp;quot;节点只是 GC 停顿了 30 秒&amp;quot;（Java 应用的 Full GC、虚拟机的 steal time、网络拥塞都会造成这种&amp;quot;假死&amp;quot;）。于是就有了&lt;strong&gt;脑裂（split-brain）&lt;/strong&gt;：老 Leader 以为自己还是 Leader，新 Leader 已经被选出来，两个&amp;quot;主&amp;quot;同时接受写入，数据互相覆盖。&lt;/p&gt;
&lt;p&gt;工程解法是 &lt;strong&gt;fencing token（防护令牌）&lt;/strong&gt;：每次选主，共识系统发一个单调递增的编号，存储层只接受&amp;quot;编号比上次大&amp;quot;的写入。老 Leader 拿着旧编号来写，直接被拒。这个小机制在 ZooKeeper（zxid）、etcd（revision）里都有对应物。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;业界掌故&lt;/strong&gt;：Kleppmann 本人就是分布式锁话题的&amp;quot;圈内名人&amp;quot;——2016 年他发文《How to do distributed locking》批判 Redis 的 Redlock 算法在时钟跳变和进程暂停下不安全，引来 Redis 作者 antirez 的公开反驳，两人来回论战了好几轮，成为分布式领域最著名的公开辩论之一。争论的核心正是上面这些：时钟不可信、暂停不可测，所以任何分布式锁要么上 fencing token，要么干脆用共识系统。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;10-批处理-vs-流处理同一枚硬币的两面&#34;&gt;10. 批处理 vs 流处理：同一枚硬币的两面&lt;/h3&gt;
&lt;p&gt;最后回到流量，看数据处理的两种世界观：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;批处理&lt;/strong&gt;：数据是&lt;strong&gt;有界的&lt;/strong&gt;——一堆文件摆在那，MapReduce/Spark 跑一遍，输出结果。优点是吞吐高、可重跑、容错简单（任务挂了重跑）；缺点是延迟以小时计。Hadoop/MapReduce 的黄金时代（Google 2004 年论文、Yahoo 孵化 Hadoop）就是这套世界观。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流处理&lt;/strong&gt;：数据是&lt;strong&gt;无界的&lt;/strong&gt;——事件源源不断地来，Kafka + Flink/Storm 逐条处理，延迟毫秒级。难点：乱序、恰好一次语义（exactly-once）、容错状态恢复。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Lambda 架构&lt;/strong&gt;：流处理快但不准（可能丢、可能重复），批处理准但慢，那就两条都跑——流处理给实时近似结果，批处理定期用精确结果覆盖。维护两套系统、两份代码，运维成本翻倍，是出名的&amp;quot;说起来美、做起来痛&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kappa 架构&lt;/strong&gt;：LinkedIn 的 Jay Kreps（Kafka 作者）提出的简化——&lt;strong&gt;只要流处理一条腿&lt;/strong&gt;。历史数据重算？把 Kafka 的日志保留期拉长，让流处理程序从头重放一遍就行。这个架构能成立的前提，是 Kafka 把&amp;quot;消息队列&amp;quot;做成了&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;flowchart TD
    subgraph 数据源
        DB[(&amp;#34;业务数据库&amp;lt;br/&amp;gt;MySQL&amp;#34;)]
        APP[&amp;#34;应用事件&amp;#34;]
    end
    DB --&amp;gt;|&amp;#34;CDC 变更数据捕获&amp;lt;br/&amp;gt;（Debezium 监听 binlog）&amp;#34;| K[&amp;#34;Kafka&amp;lt;br/&amp;gt;（可重放的日志存储）&amp;#34;]
    APP --&amp;gt; K
    K --&amp;gt; S[&amp;#34;流处理引擎&amp;lt;br/&amp;gt;Flink / Kafka Streams&amp;#34;]
    S --&amp;gt; V1[&amp;#34;派生视图：缓存 Redis&amp;#34;]
    S --&amp;gt; V2[&amp;#34;派生视图：搜索索引 ES&amp;#34;]
    S --&amp;gt; V3[&amp;#34;派生视图：数仓 ClickHouse&amp;#34;]
    K --&amp;gt;|&amp;#34;日志长期保留&amp;lt;br/&amp;gt;需要重算时从头重放&amp;#34;| S
    subgraph KP[&amp;#34;Kappa / 批流一体&amp;#34;]
        S
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这张图里藏着 DDIA 全书最重要的思想之一：&lt;strong&gt;数据库和消息队列正在融合&lt;/strong&gt;。Kafka 是可以当数据库用的日志（有保留期、可重放、有流处理）；反过来，数据库的 binlog/WAL 通过 CDC（Change Data Capture）技术流出来，就变成了消息流。Debezium 监听 MySQL binlog 把每一次行变更变成 Kafka 消息，是当下&amp;quot;派生数据管道&amp;quot;的标准做法。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;经典案例：Kafka 从 LinkedIn 内部管道到行业标准&lt;/strong&gt;。Kafka 诞生时（2010 年前后）只是 LinkedIn 内部的一个活动流（activity stream）和日志管道，目标是扛住&amp;quot;每个用户的每个页面浏览都要记录&amp;quot;的巨大流量。它的关键设计——把消息组织成&lt;strong&gt;分区有序日志&lt;/strong&gt;、消费者自己维护 offset、依赖磁盘顺序写+page cache 而非内存——让它意外地发现：这个东西不只是消息队列，而是&amp;quot;以日志为中心的存储系统&amp;quot;。Confluent 成立后，Kafka 成了流处理事实标准，Kreps 那篇《The Log: What every software engineer should know about real-time data&amp;rsquo;s unifying abstraction》（2013）把&amp;quot;日志是数据系统统一抽象&amp;quot;的思想讲透了——和 DDIA 第 3 章&amp;quot;日志结构存储&amp;quot;一脉相承。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;11-全书串讲用三个词复盘四个案例&#34;&gt;11. 全书串讲：用三个词复盘四个案例&lt;/h2&gt;
&lt;p&gt;现在用&amp;quot;存量、流量、回路&amp;quot;的框架，把本文案例快速过一遍，你会发现它们只是同一个框架的不同参数组合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kafka&lt;/strong&gt;：存量用顺序日志（LSM 的近亲），流量靠分区+副本，回路选择&amp;quot;至少一次/恰好一次可配&amp;quot;的务实路线。→ 流量优先，回路可调。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamo&lt;/strong&gt;：无主复制+永远可写，回路是最终一致+应用层冲突解决。→ 流量（可用性）压倒一切，回路最弱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spanner&lt;/strong&gt;：全球分区+同步复制，回路用 TrueTime 堆到最强（外部一致性）。→ 回路优先，花钱买。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;比特币&lt;/strong&gt;：回路要求拜占庭容错（最强敌对环境），代价是吞吐 7 TPS。→ 回路极限，流量牺牲殆尽。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;没有银弹，只有取舍。&lt;/strong&gt; 这就是 Kleppmann 反复想说、而无数&amp;quot;中间件选型指南&amp;quot;不会告诉你的事。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;12-带走的清单&#34;&gt;12. 带走的清单&lt;/h2&gt;
&lt;p&gt;读完 DDIA，做系统设计时问自己这几句话：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;存量三问&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;写入多还是读取多？写多读少看 LSM（RocksDB/Cassandra），读多写少看 B-Tree（MySQL），分析查询上列存（ClickHouse/Parquet）。&lt;/li&gt;
&lt;li&gt;崩溃恢复靠什么？找到那个 WAL——它一定存在，可能只是叫别的名字（redo log / AOF / binlog / journal）。&lt;/li&gt;
&lt;li&gt;schema 演进支持双向兼容吗？向前向后都要，滚动升级时缺一不可。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;流量三问&lt;/strong&gt;&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;复制延迟用户能感知吗？能，就按需要补读己之写、单调读、一致前缀读，别等用户来报 bug。&lt;/li&gt;
&lt;li&gt;分区键选对了吗？对照主要查询模式和热点，选错分区键≈架构重来。&lt;/li&gt;
&lt;li&gt;缓存/索引/数仓是不是派生数据？是，就把&amp;quot;同步管道&amp;quot;当成一等公民来设计（CDC + 流处理），而不是事后打补丁。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;回路三问&lt;/strong&gt;&lt;/p&gt;
&lt;ol start=&#34;7&#34;&gt;
&lt;li&gt;业务能容忍什么级别的一致性？能用最终一致就别上线性一致，省下的都是真金白银。&lt;/li&gt;
&lt;li&gt;有没有&amp;quot;读约束再写入&amp;quot;的代码？有，检查写倾斜——&lt;code&gt;FOR UPDATE&lt;/code&gt; 或串行化，二选一。&lt;/li&gt;
&lt;li&gt;选主/分布式锁靠什么？别自己造轮子，ZooKeeper/etcd 这类共识系统才是正解（Redis 分布式锁的争议——Martin Kleppmann 本人就是那篇著名批判文章《How to do distributed locking》的作者，还设计了&amp;quot;马丁小花&amp;quot;fencing token 方案）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;最后的避坑清单&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;别把默认隔离级别当成&amp;quot;安全级别&amp;quot;——PostgreSQL/MySQL 的默认都不是串行化，写倾斜在看着你。&lt;/li&gt;
&lt;li&gt;别迷信&amp;quot;分布式 = 高可用&amp;quot;——复制和分区引入的故障模式（脑裂、时钟漂移、复制滞后）比单机复杂一个数量级，Jepsen 测试报告（Kyle Kingsbury 对各大数据库的&amp;quot;虐测&amp;quot;）里全是血泪。&lt;/li&gt;
&lt;li&gt;别在不了解权衡时引用 CAP——CAP 只说&amp;quot;网络分区时一致性和可用性二选一&amp;quot;，而网络不分区时你还有延迟与一致性的权衡（PACELC 才是完整版）。&lt;/li&gt;
&lt;li&gt;时间不可信：进程会 GC 暂停、线程会被调度、时钟会跳变。所有依赖&amp;quot;本地时钟先后关系&amp;quot;的分布式逻辑都值得怀疑——这正是 Spanner 要装原子钟的原因。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;13-结语野猪书为什么是神书&#34;&gt;13. 结语：野猪书为什么是神书&lt;/h2&gt;
&lt;p&gt;市面上讲分布式的书，要么讲理论（一堆证明，读完不会写代码），要么讲产品（一堆配置，换个组件就过时）。DDIA 恰好卡在中间：&lt;strong&gt;它讲的是&amp;quot;不会过时的权衡&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;LSM 和 B-Tree 的取舍，二十年前成立，二十年后还会成立；复制滞后引发的那些&amp;quot;诡异现象&amp;quot;，今天在 MySQL 从库上出现，明天就会在你的新数据库上换个马甲出现。工具会死，权衡永存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;顺带给个阅读路径建议&lt;/strong&gt;：这本书 12 章不必按顺序啃——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日常写业务代码的：先读第 3 章（存储引擎）、第 7 章（事务），性价比最高；&lt;/li&gt;
&lt;li&gt;做中间件/基础架构的：第 5、6 章（复制、分区）和第 9 章（共识）是主菜；&lt;/li&gt;
&lt;li&gt;做数据平台/实时数仓的：第 10、11 章（批处理、流处理）直接对口；&lt;/li&gt;
&lt;li&gt;第 8 章（分布式系统的麻烦）建议所有人都读，它是全书&amp;quot;劝退幻想&amp;quot;的一章，读完你会对时钟和网络产生健康的怀疑。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用一句话收尾，也是这本书给我最大的思维升级：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;不要问&amp;quot;这个系统支持什么特性&amp;quot;，要问&amp;quot;这个系统在存量、流量、回路上各做了什么取舍，这些取舍和我的业务匹配吗&amp;quot;。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;能问出这个问题，这本书就没白读。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《软件架构：最难的部分》详解：分布式架构的权衡分析实战指南</title>
      <link>https://ftmao.com/blog/bookstore/software-architecture-hard-parts-book-notes/</link>
      <pubDate>Wed, 26 Jul 2023 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/software-architecture-hard-parts-book-notes/</guid>
      <description>&lt;h1 id=&#34;软件架构最难的部分详解分布式架构的权衡分析实战指南&#34;&gt;《软件架构：最难的部分》详解：分布式架构的权衡分析实战指南&lt;/h1&gt;
&lt;p&gt;《软件架构：最难的部分》（Software Architecture: The Hard Parts: Modern Trade-Off Analyses for Distributed Architectures）是 O&amp;rsquo;Reilly 在 2021 年出版的一本架构书，四位作者都是业内响当当的人物：Neal Ford（《软件架构 fundamentals》作者之一）、Mark Richards（资深架构师、《软件架构 fundamentals》合著者）、Pramod Sadalage（数据库演进专家）和 Zhamak Dehghani（&amp;ldquo;数据网格&amp;quot;概念的提出者）。&lt;/p&gt;
&lt;p&gt;这本书回答了一个非常现实的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;当我把一个单体系统拆成微服务时，每一步到底该怎么拆？怎么知道拆得对不对？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;市面上的架构书大多告诉你&amp;quot;什么是好架构&amp;rdquo;，却很少教你&amp;quot;做决策的过程&amp;quot;。这本书补上了这块空白。它的核心立场是：&lt;strong&gt;架构里不存在&amp;quot;最佳实践&amp;quot;，只存在&amp;quot;权衡分析&amp;quot;（Trade-off Analysis）&lt;/strong&gt;。每一个决策都是在拿一些东西换另一些东西，架构师的工作就是把交换的条件摆清楚。&lt;/p&gt;
&lt;p&gt;本文面向&lt;strong&gt;从未接触过软件架构概念&lt;/strong&gt;的读者，按全书十五章的顺序逐层展开，配合图解和实例，帮你建立完整的知识地图。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-1-章-当没有最佳实践时会发生什么&#34;&gt;第 1 章 当没有&amp;quot;最佳实践&amp;quot;时会发生什么&lt;/h2&gt;
&lt;h3 id=&#34;一切皆权衡&#34;&gt;一切皆权衡&lt;/h3&gt;
&lt;p&gt;作者开篇就泼了一盆冷水：如果有人问你&amp;quot;微服务和单体哪个好&amp;quot;，唯一正确的回答是——&lt;strong&gt;看情况&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;架构决策和买东西很像。假如有人不问预算、不问用途，直接告诉你&amp;quot;买这辆车是最好的&amp;quot;，你肯定会觉得他不靠谱。架构也一样：任何脱离上下文的&amp;quot;最佳实践&amp;quot;都是耍流氓。架构师真正的工作流程是：&lt;/p&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;p&gt;所以这本书的副标题才是&amp;quot;现代分布式架构的权衡分析&amp;quot;——&lt;strong&gt;最难的部分不是技术本身，而是权衡&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;贯穿全书的案例sysops-squad-工单系统&#34;&gt;贯穿全书的案例：Sysops Squad 工单系统&lt;/h3&gt;
&lt;p&gt;为了方便讲解，作者虚构了一个贯穿全书的案例。Sysops Squad 是一家 IT 运维支持公司，客户（通常是企业的 IT 部门）遇到设备故障时提交&lt;strong&gt;工单（Ticket）&lt;/strong&gt;，系统把工单分配给合适的运维专家（Sysop），专家上门或远程修复，完成后填写处理报告。&lt;/p&gt;
&lt;p&gt;他们现在用的是一个大单体应用，随着业务增长越来越痛苦：改一个小功能要全量发布、一个模块的 bug 拖垮整个系统、高峰期扩容只能整体扩容。于是团队决定把它拆成微服务——&lt;strong&gt;这本书讲的就是这一路上遇到的每一个&amp;quot;难啃的骨头&amp;quot;&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 拆分前[&amp;#34;拆分前：单体应用&amp;#34;]
        M[&amp;#34;Sysops Squad 单体系统&amp;lt;br/&amp;gt;工单 + 用户 + 派单 + 报告 + 计费&amp;lt;br/&amp;gt;共享一个大数据库&amp;#34;]
    end
    subgraph 拆分后[&amp;#34;拆分后：微服务&amp;#34;]
        A[&amp;#34;工单服务&amp;#34;]
        B[&amp;#34;用户服务&amp;#34;]
        C[&amp;#34;派单服务&amp;#34;]
        D[&amp;#34;报告服务&amp;#34;]
        E[&amp;#34;计费服务&amp;#34;]
        DB1[(&amp;#34;工单库&amp;#34;)]
        DB2[(&amp;#34;用户库&amp;#34;)]
        DB3[(&amp;#34;派单库&amp;#34;)]
        DB4[(&amp;#34;报告库&amp;#34;)]
        DB5[(&amp;#34;计费库&amp;#34;)]
        A --- DB1
        B --- DB2
        C --- DB3
        D --- DB4
        E --- DB5
    end
    拆分前 --&amp;gt;|本书要讲的全过程| 拆分后&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;看起来很美好对吧？但魔鬼全在细节里：组件怎么划？数据库怎么拆？服务粒度多细合适？公共代码怎么办？跨服务的事务和数据访问怎么办？——这些就是后面十四章要逐一解决的问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-2-章-识别软件架构中的耦合&#34;&gt;第 2 章 识别软件架构中的耦合&lt;/h2&gt;
&lt;h3 id=&#34;架构量子architecture-quantum&#34;&gt;架构量子（Architecture Quantum）&lt;/h3&gt;
&lt;p&gt;这是全书最重要的概念之一，也是后续所有分析的基本单位。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;架构量子&lt;/strong&gt;：系统中可以&lt;strong&gt;独立部署&lt;/strong&gt;的最小单元，包含它正常运行所必需的一切（代码、数据库、配置等）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&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;/ul&gt;
&lt;p&gt;一个单体应用就是一个架构量子；一套拆得干净的微服务，每个服务各自是一个量子；而&amp;quot;服务拆了但数据库还共享&amp;quot;的系统，量子边界其实还卡在数据库上——这是新手最容易掉进去的坑。&lt;/p&gt;
&lt;h3 id=&#34;静态耦合与动态耦合&#34;&gt;静态耦合与动态耦合&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;静态耦合&lt;/strong&gt;：编译期/部署期就能看到的依赖。比如 A 服务调用了共享库里的类，或者两个服务连同一个数据库 schema。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态耦合&lt;/strong&gt;：运行时才发生的调用关系。比如订单服务运行时调用支付服务的接口。&lt;/li&gt;
&lt;/ul&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;通信（Communication）&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;一致性（Consistency）&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;协调（Coordination）&lt;/strong&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;这三个维度像调色盘的三原色，后面讲工作流（第 11 章）和 Saga（第 12 章）时会反复用到。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-3-章-架构模块化&#34;&gt;第 3 章 架构模块化&lt;/h2&gt;
&lt;p&gt;为什么要模块化？作者给出了五个驱动力，每一个都对应一种&amp;quot;不模块化会疼&amp;quot;的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可维护性（Maintainability）&lt;/strong&gt;：大泥球代码改一处坏三处，模块化让改动范围可控。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可测试性（Testability）&lt;/strong&gt;：边界清晰的模块可以单独写测试，不用拉起整个系统。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可部署性（Deployability）&lt;/strong&gt;：小模块发布风险低、频率高；单体发布一次像发射火箭。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可扩展性（Scalability）&lt;/strong&gt;：旺季只有&amp;quot;下单&amp;quot;需要扩容，模块化后可以只扩这一个模块。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可用性与容错（Availability/Fault Tolerance）&lt;/strong&gt;：故障被限制在模块内部，不至于全站瘫痪。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举个生活化的例子：老式组合音响里一个喇叭坏了，整台机器都得拆开修；而现在的家庭影院，功放、音箱、播放器各自独立，坏哪个换哪个——这就是模块化的价值。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-4-章-架构分解&#34;&gt;第 4 章 架构分解&lt;/h2&gt;
&lt;p&gt;拆分之前，先要学会&amp;quot;测量&amp;quot;耦合，而不是凭感觉。&lt;/p&gt;
&lt;h3 id=&#34;传入耦合与传出耦合&#34;&gt;传入耦合与传出耦合&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传入耦合（Afferent Coupling）&lt;/strong&gt;：有多少别的组件依赖我？——衡量&amp;quot;我有多重要、改动我的风险有多大&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传出耦合（Efferent Coupling）&lt;/strong&gt;：我依赖多少别的组件？——衡量&amp;quot;我有多容易被别人拖累&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;主序列距离distance-from-the-main-sequence&#34;&gt;主序列距离（Distance from the Main Sequence）&lt;/h3&gt;
&lt;p&gt;这是 Robert C. Martin 提出的一组度量。想象一条坐标轴：横轴是&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;（比如工具类被全系统引用），它落在&amp;quot;痛苦区&amp;quot;：难改、一改全崩。&lt;/li&gt;
&lt;li&gt;一个组件如果&lt;strong&gt;很抽象但没人用&lt;/strong&gt;（比如过度设计的框架），它落在&amp;quot;无用区&amp;quot;。&lt;/li&gt;
&lt;li&gt;理想位置在两者之间的对角线（&amp;ldquo;主序列&amp;rdquo;）附近。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些度量不是让你拿计算器天天算，而是提供一个&lt;strong&gt;客观的拆分依据&lt;/strong&gt;：拆分候选的组件，先看它们的耦合度量，优先拆那些传入传出耦合低、边界清晰的——它们是&amp;quot;最容易摘下来的果子&amp;quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-5-章-基于组件的分解模式&#34;&gt;第 5 章 基于组件的分解模式&lt;/h2&gt;
&lt;p&gt;这是全书最&amp;quot;实操&amp;quot;的一章：怎么把一坨单体代码拆成组件？作者给出了一个六步流水线，每一步都有对应的&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;flowchart TD
    S1[&amp;#34;① 识别并确定组件大小&amp;lt;br/&amp;gt;盘点代码库里有哪些组件、各占多大比例&amp;#34;]
    S2[&amp;#34;② 收集公共组件&amp;lt;br/&amp;gt;把多个组件里重复出现的代码收拢到一处&amp;#34;]
    S3[&amp;#34;③ 扁平化组件&amp;lt;br/&amp;gt;把代码从根命名空间下沉到有明确职责的叶子节点&amp;#34;]
    S4[&amp;#34;④ 确定组件依赖&amp;lt;br/&amp;gt;画出组件依赖图，找出纠缠在一起的循环依赖&amp;#34;]
    S5[&amp;#34;⑤ 创建组件域&amp;lt;br/&amp;gt;把职责相近的组件归入同一个领域分组&amp;#34;]
    S6[&amp;#34;⑥ 创建领域服务&amp;lt;br/&amp;gt;每个领域组对外暴露成一个粗粒度服务&amp;#34;]
    S1 --&amp;gt; S2 --&amp;gt; S3 --&amp;gt; S4 --&amp;gt; S5 --&amp;gt; S6&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;像搬家前盘点家当：先知道自己有什么。适应度函数可以自动检查&amp;quot;任何组件不得超过代码库总量的 X%&amp;quot;、&amp;ldquo;组件大小不得偏离平均值 N 个标准差&amp;rdquo;，防止拆出巨型组件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第②步&lt;/strong&gt;解决重复：如果&amp;quot;日志工具&amp;quot;&amp;ldquo;日期处理&amp;quot;在十个组件里各抄了一份，先把它们收拢，否则拆开后会十处同步改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第③步&lt;/strong&gt;针对一种常见病：大量代码堆在根命名空间（比如所有代码都在 &lt;code&gt;com.sysops&lt;/code&gt; 下面不分层），说明系统从来没有真正的结构，必须先给每段代码找到归属。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第④步&lt;/strong&gt;是分水岭：依赖图里纠缠不清的组件（A 调 B、B 调 C、C 又调 A）说明领域边界划分有误，要回到业务层面重新划。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第⑤⑥步&lt;/strong&gt;完成从&amp;quot;代码组件&amp;quot;到&amp;quot;业务服务&amp;quot;的跃迁：同一域内的组件将来部署成一个服务，域与域之间的边界就是服务边界。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;**适应度函数（Fitness Function）**是本书反复强调的治理手段：把架构约束写成自动化测试，放进 CI 流水线。比如&amp;quot;报告域的组件不允许依赖计费域的组件&amp;quot;可以直接写成一条 ArchUnit 测试，谁违反了构建就失败——架构约束从&amp;quot;文档里的愿望&amp;quot;变成&amp;quot;会报警的护栏&amp;rdquo;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-6-章-拆分运营数据&#34;&gt;第 6 章 拆分运营数据&lt;/h2&gt;
&lt;p&gt;代码拆完，真正的硬骨头来了：&lt;strong&gt;数据库&lt;/strong&gt;。很多团队服务拆得很漂亮，底下却还用同一个数据库——等于楼分了户、水电还是一户一表，根本没有真正解耦。&lt;/p&gt;
&lt;h3 id=&#34;拆开数据库的推力与阻力&#34;&gt;拆开数据库的推力与阻力&lt;/h3&gt;
&lt;p&gt;作者在拆数据这件事上建立了一个&amp;quot;天平&amp;quot;模型：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;拆分驱动因素（Data Disintegrators，推着你拆）：&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;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;整合因素（Data Integrators，拽着你不让拆）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据关系：外键、JOIN 在跨库之后全部失效。&lt;/li&gt;
&lt;li&gt;数据库事务：跨库没有 ACID 事务，必须面对分布式事务的复杂性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;五步数据库分解法&#34;&gt;五步数据库分解法&lt;/h3&gt;
&lt;p&gt;书里给出了一个务实的渐进式拆分流程，核心思想是&amp;quot;&lt;strong&gt;先逻辑拆，再物理拆&lt;/strong&gt;&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;flowchart LR
    P1[&amp;#34;① 分析数据库&amp;lt;br/&amp;gt;划分数据域&amp;#34;]
    P2[&amp;#34;② 把表分配到&amp;lt;br/&amp;gt;各数据域&amp;#34;]
    P3[&amp;#34;③ 数据库连接&amp;lt;br/&amp;gt;按域分离&amp;#34;]
    P4[&amp;#34;④ Schema 迁移到&amp;lt;br/&amp;gt;独立数据库服务器&amp;#34;]
    P5[&amp;#34;⑤ 切换到完全独立的&amp;lt;br/&amp;gt;数据库&amp;#34;]
    P1 --&amp;gt; P2 --&amp;gt; P3 --&amp;gt; P4 --&amp;gt; P5&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;关键是第③步：表还没物理分开，就先让各服务只连自己的 schema、只访问自己的表。这一步不动数据，只动代码和连接，风险小、可回滚。等代码层面确认拆分正确了，再做物理迁移（第④⑤步）。这种&amp;quot;小步快跑、每步可退&amp;quot;的思路贯穿全书。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-7-章-服务粒度&#34;&gt;第 7 章 服务粒度&lt;/h2&gt;
&lt;p&gt;拆服务时最纠结的问题：&lt;strong&gt;一个服务到底该多大？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;作者又祭出了&amp;quot;天平&amp;quot;分析法——这次叫粒度解耦因素（Granularity Disintegrators，推着你往细拆）与粒度整合因素（Granularity Integrators，拽着你往粗合）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;往细拆的理由（Disintegrators）：&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;：如果系统里&amp;quot;促销规则&amp;quot;每周改、&amp;ldquo;订单核心&amp;quot;半年不动，就该把促销拆出去，免得高频改动反复惊扰稳定部分。&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;可扩展性（Extensibility）&lt;/strong&gt;：新功能以新服务的形式生长，不改老代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;往粗合的理由（Integrators）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据库事务&lt;/strong&gt;：两个操作必须在同一个 ACID 事务里？那它们最好待在同一个服务里。&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;&lt;strong&gt;实例&lt;/strong&gt;：Sysops Squad 拆出&amp;quot;工单服务&amp;quot;后，争论要不要把&amp;quot;工单指派&amp;quot;再拆成独立服务。分析后发现：指派逻辑改动频繁（每周调规则）、需要单独的弹性伸缩（早高峰派单量是平时的 5 倍）——两个 disintegrator 都很强；而它与工单共享的表只有一张、没有跨库事务需求——integrator 很弱。于是拆。反过来，&amp;ldquo;工单备注&amp;quot;功能没有任何独立的扩展和发布需求，就留在工单服务里。&lt;/p&gt;
&lt;p&gt;这就是权衡分析的样子：&lt;strong&gt;不是数哪边的理由多，而是掂量哪边的理由重&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-8-章-复用模式&#34;&gt;第 8 章 复用模式&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;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;改 bug 要改 N 处&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;共享库&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;打成 jar/npm 包共用&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;Sidecar / 服务网格&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;p&gt;选择思路很直接：&lt;strong&gt;变化越频繁、越偏向业务的代码，越不要共享&lt;/strong&gt;（宁肯复制）；&lt;strong&gt;越稳定、越偏向技术基础设施的能力&lt;/strong&gt;（日志、监控、鉴权），越适合共享或 Sidecar。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实例&lt;/strong&gt;：Sysops Squad 的&amp;quot;专家评分算法&amp;quot;每个服务都在用，但它每月都在调优——选了代码复制，各服务按需演进。而&amp;quot;日志采集&amp;quot;用了 Sidecar，统一由运维团队维护。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-9-章-数据所有权与分布式事务&#34;&gt;第 9 章 数据所有权与分布式事务&lt;/h2&gt;
&lt;p&gt;拆库之后立刻撞上两个难题：一张表被两个服务都要写，归谁？一个业务操作要跨两个服务写数据，怎么保证一致性？&lt;/p&gt;
&lt;h3 id=&#34;数据所有权&#34;&gt;数据所有权&lt;/h3&gt;
&lt;p&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;/ul&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
    subgraph 表拆分技术[&amp;#34;表拆分：按列拆开&amp;#34;]
        T1[&amp;#34;customer 表&amp;lt;br/&amp;gt;姓名/联系方式 → 档案服务&amp;lt;br/&amp;gt;信用额度/账单地址 → 计费服务&amp;#34;]
    end
    subgraph 数据域技术[&amp;#34;数据域：按行拆开&amp;#34;]
        T2[&amp;#34;ticket 表&amp;lt;br/&amp;gt;进行中的工单 → 工单服务&amp;lt;br/&amp;gt;已归档的历史工单 → 报告服务&amp;#34;]
    end
    subgraph 委派技术[&amp;#34;委派：一主一从&amp;#34;]
        T3[&amp;#34;invoice 表归计费服务所有&amp;lt;br/&amp;gt;工单服务需要时&amp;lt;br/&amp;gt;通过接口委托计费服务写&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&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;：写本地库后立刻发事件，其他服务异步消费。性能和可用性最好，但要想清楚&amp;quot;事件丢了怎么办&amp;rdquo;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;实例&lt;/strong&gt;：工单完成后要同时更新&amp;quot;工单状态&amp;quot;和&amp;quot;专家积分&amp;rdquo;。如果用编排请求，用户要等两个服务都响应；用事件模式，工单服务先发 &lt;code&gt;TicketCompleted&lt;/code&gt; 事件立即返回，积分服务异步加分——用户快了几百毫秒，代价是积分查询页面可能晚几秒才更新。Sysops Squad 选了后者：&lt;strong&gt;最终一致性换了响应速度&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-10-章-分布式数据访问&#34;&gt;第 10 章 分布式数据访问&lt;/h2&gt;
&lt;p&gt;读操作比写操作更常见：服务 A 需要服务 B 拥有的数据，怎么拿？四种模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务间通信&lt;/strong&gt;：直接调 B 的接口。最简单，但每次查询都是一次网络往返，B 挂了 A 也查不了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;列模式复制&lt;/strong&gt;：把需要的那几列数据复制一份到 A 的数据库（通过消息保持同步）。查询快、无运行时依赖，但数据有延迟、要处理重复治理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复制缓存&lt;/strong&gt;：B 把数据推进共享缓存（如 Redis），A 从缓存读。适合读多写少的参考数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据域&lt;/strong&gt;：把双方都要的数据干脆划给一个专门的&amp;quot;数据域服务&amp;quot;管理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;选择的关键问题是：&amp;quot;&lt;strong&gt;这份数据 A 需要多新鲜？B 的可用性 A 能承受吗？&lt;/strong&gt;&amp;ldquo;答案不同，模式就不同。比如专家的联系信息（几乎不变）适合复制缓存；工单的实时状态（必须最新）只能走服务间通信。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-11-章-管理分布式工作流&#34;&gt;第 11 章 管理分布式工作流&lt;/h2&gt;
&lt;p&gt;一个业务流程跨多个服务时，谁来当&amp;quot;导演&amp;rdquo;？两种风格：&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;编排 Orchestration：有个总指挥&amp;#34;]
        O[&amp;#34;编排者&amp;lt;br/&amp;gt;（工单流程服务）&amp;#34;]
        O --&amp;gt; A[&amp;#34;工单服务&amp;#34;]
        O --&amp;gt; B[&amp;#34;派单服务&amp;#34;]
        O --&amp;gt; C[&amp;#34;计费服务&amp;#34;]
        O --&amp;gt; D[&amp;#34;通知服务&amp;#34;]
    end&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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 编制[&amp;#34;编制 Choreography：舞者跟着音乐各自起舞&amp;#34;]
        A2[&amp;#34;工单服务&amp;#34;] --&amp;gt;|发事件| B2[&amp;#34;派单服务&amp;#34;]
        B2 --&amp;gt;|发事件| C2[&amp;#34;计费服务&amp;#34;]
        C2 --&amp;gt;|发事件| D2[&amp;#34;通知服务&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编排（Orchestration）&lt;/strong&gt;：设一个中心编排者，像乐队指挥，告诉每个服务&amp;quot;现在该你了&amp;quot;。流程一目了然、好监控、好报错；但编排者容易变成新的&amp;quot;小单体&amp;quot;，且是所有服务的耦合点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编制（Choreography）&lt;/strong&gt;：没有中心，每个服务监听事件、做完自己的事再发下一个事件，像舞者跟着音乐即兴配合。极度解耦、无单点；但流程分散在各处，&amp;ldquo;现在进行到哪一步了&amp;quot;这种简单问题都很难回答。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有一个关键细节：**工作流状态放哪？**编排模式下状态天然在编排者手里；编制模式下要么设一个专门的&amp;quot;状态所有者&amp;quot;服务，要么忍受状态分散的痛苦。&lt;/p&gt;
&lt;p&gt;经验法则：&lt;strong&gt;流程复杂、分支多、需要人工介入补偿的，用编排；流程线性、追求吞吐量的，用编制&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-12-章-事务性-saga&#34;&gt;第 12 章 事务性 Saga&lt;/h2&gt;
&lt;p&gt;Saga 是分布式世界里替代 ACID 事务的方案：把一个跨服务的大事务拆成一串本地小事务，任何一步失败就按相反顺序执行&lt;strong&gt;补偿操作&lt;/strong&gt;（退款、释放库存……）回滚已完成的步骤。&lt;/p&gt;
&lt;p&gt;书里把第 2 章的三个维度组合起来，给 Saga 模式起了八个别致的名字（括号里的三个字母依次代表：一致性 a=原子/e=最终、通信 s=同步/a=异步、协调 o=编排/c=编制）：&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;史诗 Saga&lt;/strong&gt;（a,s,o）&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;电话接龙 Saga&lt;/strong&gt;（a,s,c）&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;童话 Saga&lt;/strong&gt;（e,s,o）&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;时间旅行 Saga&lt;/strong&gt;（e,s,c）&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;奇幻小说 Saga&lt;/strong&gt;（a,a,o）&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;恐怖故事 Saga&lt;/strong&gt;（a,a,c）&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;平行 Saga&lt;/strong&gt;（e,a,o）&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;文集 Saga&lt;/strong&gt;（e,a,c）&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;起这些&amp;quot;俗气&amp;quot;的名字是故意的：&lt;strong&gt;好记&lt;/strong&gt;。团队讨论时说&amp;quot;这个流程用文集模式&amp;rdquo;，比说&amp;quot;最终一致+异步通信+编制协调的 Saga&amp;quot;高效得多。&lt;/p&gt;
&lt;p&gt;核心结论：&lt;strong&gt;越往左（原子一致），越安全越慢越脆；越往右（最终一致+异步+编制），越快越抗造，但你要为&amp;quot;中间状态&amp;quot;和&amp;quot;补偿逻辑&amp;quot;付出工程成本&lt;/strong&gt;。没有免费的午餐，只有明摆着的账单。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-13-章-契约&#34;&gt;第 13 章 契约&lt;/h2&gt;
&lt;p&gt;服务之间的接口约定（Contract）该多严格？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;严格契约&lt;/strong&gt;：字段类型、格式全部写死并强校验（如带 schema 的 RPC）。好处是错误在编译/调用瞬间就暴露；代价是任何字段变更都要双方同时升级，耦合很紧。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;宽松契约&lt;/strong&gt;：只约定必需字段，其余自由扩展（如 JSON + 约定）。灵活、可独立演进；代价是错误可能潜伏到运行时才爆发。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有两个进阶概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;消费者驱动的契约（Consumer-Driven Contracts）&lt;/strong&gt;：由服务的&lt;strong&gt;调用方&lt;/strong&gt;写下&amp;quot;我需要这些字段、这种格式&amp;quot;，作为测试放进提供方的流水线。提供方一改接口，立刻知道会弄坏谁——契约从&amp;quot;提供方说了算&amp;quot;变成&amp;quot;对消费者负责&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;戳记耦合（Stamp Coupling）&lt;/strong&gt;：调用时把一大坨数据结构整个传过去（比如把 50 个字段的 &lt;code&gt;Customer&lt;/code&gt; 对象传给只需要 &lt;code&gt;email&lt;/code&gt; 的服务）。对方被迫依赖整个结构的形状，改任何一个字段都可能波及无辜。&lt;strong&gt;只传对方真正需要的字段&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-14-章-管理分析型数据&#34;&gt;第 14 章 管理分析型数据&lt;/h2&gt;
&lt;p&gt;运营数据（支撑业务运转）拆给了微服务，那&lt;strong&gt;分析数据&lt;/strong&gt;（做报表、跑 BI、训练模型）怎么办？作者梳理了三代方案：&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
    DW[&amp;#34;数据仓库&amp;lt;br/&amp;gt;ETL 抽取 → 清洗 → 集中存储&amp;lt;br/&amp;gt;结构化、为报表优化&amp;#34;] --&amp;gt; DL[&amp;#34;数据湖&amp;lt;br/&amp;gt;原始数据先倒进去&amp;lt;br/&amp;gt;用的时候再处理&amp;lt;br/&amp;gt;灵活但容易变成数据沼泽&amp;#34;] --&amp;gt; DM[&amp;#34;数据网格&amp;lt;br/&amp;gt;按领域分布式的数据产品&amp;lt;br/&amp;gt;数据像微服务一样被治理&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;**数据网格（Data Mesh）**是本章的高潮，也是作者之一 Zhamak Dehghani 原创的概念。它的核心思想是把微服务的哲学搬到数据世界：&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;：数据要有明确的接口、质量承诺、文档，像 API 一样被消费。&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;作者也诚实地给出了&amp;quot;什么时候&lt;strong&gt;不要&lt;/strong&gt;用数据网格&amp;quot;：团队小、数据量不大、分析需求简单时，一个数据仓库就够了——&lt;strong&gt;架构量子越少越好，够用就行&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-15-章-构建你自己的权衡分析&#34;&gt;第 15 章 构建你自己的权衡分析&lt;/h2&gt;
&lt;p&gt;最后一章是方法论总结：这套&amp;quot;天平分析&amp;quot;手艺怎么用到自己的项目里？&lt;/p&gt;
&lt;p&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;用 MECE 原则列清单&lt;/strong&gt;：相互独立、完全穷尽，避免漏掉某个维度的代价。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;警惕&amp;quot;脱离上下文&amp;quot;的陷阱&lt;/strong&gt;：网上说&amp;quot;Netflix 用了 X 架构所以 X 好&amp;quot;——Netflix 的上下文（规模、团队、预算）和你完全不同，结论不能直接搬。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定性分析优先于定量&lt;/strong&gt;：很多时候没法拿到精确数字，用&amp;quot;高/中/低&amp;quot;的比较级就够做决策了，别为了伪精确而瘫痪。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只给结论，别淹没听众&lt;/strong&gt;：给业务方汇报时，先说&amp;quot;推荐方案 B，因为……&amp;quot;，细节放附录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;远离蛇油和布道&lt;/strong&gt;：任何声称&amp;quot;无条件最优&amp;quot;的方案（银弹、框架布道者），默认先怀疑。&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;mindmap
  root((软件架构&amp;lt;br/&amp;gt;最难的部分))
    核心理念
      没有最佳实践
      一切皆权衡
      架构量子是分析单位
    怎么拆
      组件分解六步法
      数据库五步分解
      粒度天平
        Disintegrators 往细拆
        Integrators 往粗合
    拆完怎么办
      复用四模式
      数据所有权
      分布式数据访问
    跨服务协作
      编排 vs 编制
      八种 Saga
      严格 vs 宽松契约
    数据与分析
      数据仓库/湖
      数据网格
    方法论
      适应度函数
      MECE 分析
      警惕脱离上下文&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这本书最有价值的不是任何一条具体结论，而是它示范的&lt;strong&gt;思考方式&lt;/strong&gt;：面对任何架构决策，先问&amp;quot;这个选择让我得到什么、失去什么&amp;quot;，把天平两端的砝码摆出来，再结合自己的上下文做判断。&lt;/p&gt;
&lt;p&gt;如果你是架构新手，建议阅读顺序是：先读第 1、2 章建立&amp;quot;权衡&amp;quot;和&amp;quot;架构量子&amp;quot;的心智模型，再读第 5、6、7 章学会&amp;quot;拆&amp;quot;，最后按需查阅第 8–14 章解决&amp;quot;拆完之后&amp;quot;的具体问题。第 15 章适合放在手边，每次做重大技术选型前翻一遍——它会让你少交很多学费。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>消息队列总结：RabbitMQ、Kafka、RocketMQ 与 Redis MQ 选型对比与实践避坑</title>
      <link>https://ftmao.com/blog/posts/mq-comparison-rabbitmq-kafka-rocketmq/</link>
      <pubDate>Tue, 30 Jun 2020 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/mq-comparison-rabbitmq-kafka-rocketmq/</guid>
      <description>&lt;h1 id=&#34;消息队列总结rabbitmqkafkarocketmq-与-redis-mq-选型对比与实践避坑&#34;&gt;消息队列总结：RabbitMQ、Kafka、RocketMQ 与 Redis MQ 选型对比与实践避坑&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;消息队列是分布式系统里最常见的「异步管道」。这篇笔记把 RabbitMQ、Kafka、RocketMQ 和 Redis MQ 放在一起比较，重点回答：什么时候该用哪个？推和拉到底怎么选？长连接有什么影响？以及实际使用中的避坑和异常处理经验。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一为什么需要消息队列&#34;&gt;一、为什么需要消息队列？&lt;/h2&gt;
&lt;p&gt;在没有 MQ 的系统中，A 服务调用 B 服务通常是同步的：&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;A ──同步调用──▶ B ──同步调用──▶ C
&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;链路越长，响应越慢，容错越差。MQ 的核心价值是把「同步依赖」变成「异步管道」：&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;A ──发送消息──▶ MQ ──接收消息──▶ 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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;11-mq-解决的三大问题&#34;&gt;1.1 MQ 解决的三大问题&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;：把非核心链路（发短信、记日志、发券）从主流程剥离，降低接口 RT。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;削峰填谷&lt;/strong&gt;：突发流量先写入队列，下游按自身能力消费，避免被压垮。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;12-什么时候必须引入-mq&#34;&gt;1.2 什么时候必须引入 MQ？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;上下游处理速度差异大，同步调用会导致阻塞或超时。&lt;/li&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;二四种-mq-横向对比&#34;&gt;二、四种 MQ 横向对比&lt;/h2&gt;
&lt;p&gt;RabbitMQ、Kafka、RocketMQ 是主流企业级 MQ，Redis 也能当轻量 MQ 用，但定位不同。&lt;/p&gt;
&lt;h3 id=&#34;21-核心定位&#34;&gt;2.1 核心定位&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&lt;/th&gt;
					&lt;th&gt;Redis MQ&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;Erlang 实现，面向通用企业消息&lt;/td&gt;
					&lt;td&gt;LinkedIn 开源，面向日志采集与流处理&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;Topic + Queue，支持 Tag 过滤&lt;/td&gt;
					&lt;td&gt;Exchange + Queue，路由灵活&lt;/td&gt;
					&lt;td&gt;Topic + Partition，分区有序&lt;/td&gt;
					&lt;td&gt;List / Stream / PubSub&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;默认消费方式&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Push（长轮询封装）+ Pull&lt;/td&gt;
					&lt;td&gt;Push（AMQP 推送）&lt;/td&gt;
					&lt;td&gt;Pull（消费者主动拉取）&lt;/td&gt;
					&lt;td&gt;Push（PubSub）/ Pull（BLPOP）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;顺序消息&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;分片内有序，Sharding Key 控制&lt;/td&gt;
					&lt;td&gt;单 Queue 有序&lt;/td&gt;
					&lt;td&gt;Partition 内有序&lt;/td&gt;
					&lt;td&gt;单 Stream/列表有序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;延迟消息&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;原生支持 18 个级别&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;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;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;td&gt;无&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;22-性能对比&#34;&gt;2.2 性能对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&lt;/th&gt;
					&lt;th&gt;Redis MQ&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;10 万级 TPS&lt;/td&gt;
					&lt;td&gt;万级 TPS&lt;/td&gt;
					&lt;td&gt;百万级 TPS（批量场景）&lt;/td&gt;
					&lt;td&gt;十万级 TPS（内存操作）&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;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;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;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;Kafka 吞吐数字通常来自批量、压缩、顺序写的日志场景；单条小消息低延迟不如 RocketMQ。&lt;/li&gt;
&lt;li&gt;RabbitMQ 延迟低，但高吞吐和海量堆积不是强项。&lt;/li&gt;
&lt;li&gt;RocketMQ 在「中等吞吐 + 低延迟 + 强堆积」之间取得了较好平衡。&lt;/li&gt;
&lt;li&gt;Redis MQ 性能高，但消息堆积、持久化、事务、延迟消息都不完善，适合轻量、临时场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;23-成本与生态&#34;&gt;2.3 成本与生态&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&lt;/th&gt;
					&lt;th&gt;Redis MQ&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;中等（NameServer + Broker）&lt;/td&gt;
					&lt;td&gt;低（单节点即可运行）&lt;/td&gt;
					&lt;td&gt;高（ZooKeeper/KRaft + Broker）&lt;/td&gt;
					&lt;td&gt;低（Redis 单实例/集群）&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;低，但 Erlang 问题排查门槛高&lt;/td&gt;
					&lt;td&gt;高，调优项多&lt;/td&gt;
					&lt;td&gt;低，Redis 团队普遍熟悉&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;云服务成熟度&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;阿里云 RocketMQ 成熟&lt;/td&gt;
					&lt;td&gt;各云厂商都有托管版&lt;/td&gt;
					&lt;td&gt;各云厂商 Kafka 托管版成熟&lt;/td&gt;
					&lt;td&gt;云 Redis 托管版成熟&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;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;一般，可与 Flink/Spark 对接&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;Java 最完善，其他语言有社区版&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;24-适用场景推荐&#34;&gt;2.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;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;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;延迟低、事务消息、顺序消息、监控完善&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;异步任务、分布式 Worker、视频渲染&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;PushConsumer 自动负载均衡，失败重试完善&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;传统企业应用、RPC 解耦、路由复杂&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;简单易用，Exchange 路由灵活，快速上线&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;日志采集、埋点、实时数仓、流处理&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&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;strong&gt;RabbitMQ / Kafka&lt;/strong&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;strong&gt;RocketMQ&lt;/strong&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;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;部署简单、延迟低，但不要把核心交易交给它&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;已有 Redis 基础设施，不想引入新组件&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;作为过渡方案或辅助队列可用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;25-一句话选型&#34;&gt;2.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-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;可靠交易、事务消息、顺序消息、在线业务  →  RocketMQ
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;快速搭建、路由灵活、团队规模小          →  RabbitMQ
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;日志/流处理/大数据管道、吞吐优先          →  Kafka
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;轻量通知、临时队列、已有 Redis 生态       →  Redis MQ&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;很多大厂内部是「Kafka 做日志流 + RocketMQ 做业务消息 + RabbitMQ 做轻量任务 + Redis 做缓存/通知」组合使用，不是单选题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三底层架构与核心差异&#34;&gt;三、底层架构与核心差异&lt;/h2&gt;
&lt;h3 id=&#34;31-四种-mq-架构对比&#34;&gt;3.1 四种 MQ 架构对比&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 RocketMQ
        P1[Producer] --&amp;gt; NS[NameServer]
        NS --&amp;gt; B1[Broker Master]
        B1 --&amp;gt; B2[Broker Slave]
        B1 --&amp;gt; C1[Consumer]
    end

    subgraph Kafka
        P2[Producer] --&amp;gt; KZ[ZooKeeper / KRaft]
        KZ --&amp;gt; KB1[Broker 1]
        KZ --&amp;gt; KB2[Broker 2]
        KB1 --&amp;gt; C2[Consumer]
    end

    subgraph RabbitMQ
        P3[Producer] --&amp;gt; EX[Exchange]
        EX --&amp;gt; Q1[Queue A]
        EX --&amp;gt; Q2[Queue B]
        Q1 --&amp;gt; C3[Consumer A]
        Q2 --&amp;gt; C4[Consumer B]
    end

    subgraph RedisMQ
        P4[Producer] --&amp;gt; R[(Redis)]
        R --&amp;gt; C5[Consumer BLPOP]
        R --&amp;gt; C6[Subscriber]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;32-存储模型&#34;&gt;3.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;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;CommitLog + ConsumeQueue&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;所有 Topic 的消息顺序写入同一个 CommitLog，再用 ConsumeQueue 存每个 Queue 的偏移索引。顺序写磁盘，I/O 效率高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Partition Segment + Index&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每个 Partition 是独立日志目录，顺序写入 Segment，配合稀疏索引。Partition 间物理隔离。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Queue 独立存储&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每个 Queue 维护独立索引和存储，Queue 越多元数据开销越大。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;内存 List / Stream&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;基于内存操作，速度快，但容量受内存限制，持久化可靠性不如专业 MQ。&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;flowchart TB
    subgraph RocketMQ_Storage[&amp;#34;RocketMQ 存储模型&amp;#34;]
        CL[CommitLog&amp;lt;br/&amp;gt;所有消息顺序写]
        CQ1[ConsumeQueue A&amp;lt;br/&amp;gt;偏移索引]
        CQ2[ConsumeQueue B&amp;lt;br/&amp;gt;偏移索引]
        CL --&amp;gt; CQ1
        CL --&amp;gt; CQ2
    end

    subgraph Kafka_Storage[&amp;#34;Kafka 存储模型&amp;#34;]
        KP1[TopicA-P0 Log&amp;lt;br/&amp;gt;独立 Partition]
        KP2[TopicA-P1 Log&amp;lt;br/&amp;gt;独立 Partition]
    end

    subgraph RabbitMQ_Storage[&amp;#34;RabbitMQ 存储模型&amp;#34;]
        MQ1[Queue A&amp;lt;br/&amp;gt;独立索引]
        MQ2[Queue B&amp;lt;br/&amp;gt;独立索引]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&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;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;NameServer&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无状态集群，Broker 主动上报路由，Producer/Consumer 定时拉取。最终一致，轻量。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;ZooKeeper / KRaft&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;老版本依赖 ZooKeeper 存元数据、选 Controller；新版本用 KRaft 自洽协议。强一致要求更高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;内置 Mnesia 数据库&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;集群节点共享元数据，Erlang 进程模型天然分布式，但集群扩展性相对有限。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;无&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;依赖 Redis 自身的 Cluster/哨兵做高可用，没有独立路由层。&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;34-复制与高可用&#34;&gt;3.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;th&gt;特点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Master-Slave 同步/异步复制&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同步复制保证不丢消息，异步复制性能更高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Partition 多副本 + ISR 机制&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;自动 Failover，成熟度高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&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;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;主从 + 哨兵 / Cluster&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Redis 自身的高可用机制，不是为消息队列专门设计。&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;h3 id=&#34;41-push-vs-pull-对比&#34;&gt;4.1 Push vs Pull 对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;Push（推）&lt;/th&gt;
					&lt;th&gt;Pull（拉）&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;在 Broker&lt;/td&gt;
					&lt;td&gt;在 Consumer&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;实时性&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;高，Broker 有消息就发&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;较弱，Consumer 处理慢会堆积&lt;/td&gt;
					&lt;td&gt;强，Consumer 自己控制速率&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;高，需自管 offset、拉取循环&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;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;42-四种-mq-的推拉实现&#34;&gt;4.2 四种 MQ 的推拉实现&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;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;长轮询 Pull 封装成 Push&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;Consumer 与 Broker 保持长连接，Broker hold 请求 5 秒左右，有新消息立即返回。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;消费者主动 Pull&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;否（可保持连接但非必需）&lt;/td&gt;
					&lt;td&gt;Consumer 自己决定何时拉、拉多少，灵活性最高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Broker 真 Push（AMQP）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;Channel 长连接，Broker 把消息塞给 Consumer，处理完 ACK。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;PubSub 推 / BLPOP 拉&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;PubSub 是长连接推送；BLPOP 是阻塞拉取，也算长连接。&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&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;flowchart LR
    subgraph 长连接优势
        A1[低延迟]
        A2[实时推送]
        A3[减少建连开销]
    end

    subgraph 长连接代价
        B1[连接数消耗大]
        B2[需要心跳保活]
        B3[网络断开感知延迟]
        B4[Consumer 慢会拖垮 Broker]
    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;消息到达可以立刻推送给 Consumer，延迟低。&lt;/li&gt;
&lt;li&gt;避免频繁建立/断开 TCP 连接的开销。&lt;/li&gt;
&lt;li&gt;适合在线业务、实时通知类场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;长连接的代价&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Broker 需要维护大量连接，连接数成为重要资源。&lt;/li&gt;
&lt;li&gt;必须有心跳机制，否则无法及时发现 Consumer 宕机。&lt;/li&gt;
&lt;li&gt;Consumer 处理慢时，消息会堆积在 Broker 内存或磁盘，容易把 Broker 压垮。&lt;/li&gt;
&lt;li&gt;网络闪断后需要重连、重平衡，可能带来消费抖动。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;44-选择原则&#34;&gt;4.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;flowchart TD
    A[选择消费模式] --&amp;gt; B{实时性要求高?}
    B --&amp;gt;|是| C{消费逻辑轻量?}
    B --&amp;gt;|否| D[Pull 模式]
    C --&amp;gt;|是| E[Push 模式]
    C --&amp;gt;|否| F[Pull 模式 或 限流 Push]
    D --&amp;gt; G[批量处理/ETL/数据同步]
    E --&amp;gt; H[在线业务/实时通知]
    F --&amp;gt; I[重任务/视频渲染/大数据]&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;51-rocketmq-避坑&#34;&gt;5.1 RocketMQ 避坑&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;同一个 ConsumerGroup 订阅不同 Topic/Tag，重平衡混乱&lt;/td&gt;
					&lt;td&gt;一个 ConsumerGroup 只订阅一个 Topic + 一套 Tag&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Tag 用得太细&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;管理混乱，消费者过滤复杂&lt;/td&gt;
					&lt;td&gt;Topic 按业务域划分，Tag 做粗粒度过滤&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;消息体过大&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;超过 4MB 默认限制，发送失败&lt;/td&gt;
					&lt;td&gt;消息体只传 ID 和必要字段，完整数据从 DB/OSS 拉取&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;只在真正需要局部顺序的场景用 Sharding Key&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;NameServer 路由延迟&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Broker 扩缩容后发送到已下线节点&lt;/td&gt;
					&lt;td&gt;理解最终一致性，扩缩容留足等待时间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;自动创建 Topic&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;生产环境订阅失败或路由错误&lt;/td&gt;
					&lt;td&gt;提前创建 Topic 和 ConsumerGroup&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;52-kafka-避坑&#34;&gt;5.2 Kafka 避坑&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;Partition 数设置不当&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;太少并行度不够，太多元数据压力大&lt;/td&gt;
					&lt;td&gt;根据吞吐和 Consumer 数合理规划&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;消费者 Rebalance 频繁&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;消费停滞，组员加入/退出频繁&lt;/td&gt;
					&lt;td&gt;避免频繁扩缩容，调整 session.timeout.ms&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Offset 提交策略不当&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;重复消费或消息丢失&lt;/td&gt;
					&lt;td&gt;重要消息手动提交，处理完再 commit&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Producer  acks=0/1&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;丢消息&lt;/td&gt;
					&lt;td&gt;关键业务用 acks=all&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;单 Partition 热点&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;某个 Partition 消费滞后&lt;/td&gt;
					&lt;td&gt;合理设计 Key，避免 Key 倾斜&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;53-rabbitmq-避坑&#34;&gt;5.3 RabbitMQ 避坑&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;开镜像后吞吐明显降低&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;Broker 内存/磁盘告警，性能下降&lt;/td&gt;
					&lt;td&gt;控制生产速度，及时扩容 Consumer&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ACK 模式用错&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;消息丢失或重复&lt;/td&gt;
					&lt;td&gt;手动 ACK，处理完再确认&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Exchange 路由复杂&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;Connection/Channel 滥用&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;连接数耗尽&lt;/td&gt;
					&lt;td&gt;Channel 复用，避免每个消息新建连接&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;54-redis-mq-避坑&#34;&gt;5.4 Redis MQ 避坑&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;PubSub 消息丢失&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Consumer 断线期间的消息收不到&lt;/td&gt;
					&lt;td&gt;重要消息用 Stream 或专业 MQ&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;List 做队列无 ACK&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Consumer 处理失败消息丢失&lt;/td&gt;
					&lt;td&gt;用 Stream 的 ACK 机制&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;内存不足&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Redis OOM&lt;/td&gt;
					&lt;td&gt;设置合理过期时间，控制队列长度&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;把 Redis 当核心 MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;数据丢失、无事务、无延迟消息&lt;/td&gt;
					&lt;td&gt;核心交易链路用 RocketMQ/Kafka&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;55-通用最佳实践&#34;&gt;5.5 通用最佳实践&lt;/h3&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;：所有 Consumer 都要做幂等，MQ 只保证至少一次投递。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控到位&lt;/strong&gt;：堆积量、消费 TPS、延迟、死信队列、Broker 资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;版本一致&lt;/strong&gt;：服务端和客户端版本不要差距太大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提前创建资源&lt;/strong&gt;：生产环境不要依赖自动创建 Topic/Queue。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;区分可重试与不可重试异常&lt;/strong&gt;：网络抖动可以重试，参数错误直接进死信。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六异常场景处理分-mq-说明&#34;&gt;六、异常场景处理（分 MQ 说明）&lt;/h2&gt;
&lt;p&gt;下面列出的异常场景在 RocketMQ、RabbitMQ、Kafka、Redis MQ 中都会遇到，但每种 MQ 提供的机制和最佳实践不同。这一节把四种 MQ 放在一起对比说明。&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
    P[Producer 发送层] --&amp;gt;|发送失败/超时| B[Broker 存储层]
    B --&amp;gt;|刷盘/复制失败| C[Consumer 消费层]
    C --&amp;gt;|重复/失败/卡住| Biz[业务异常兜底]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;61-消息丢失各-mq-如何防范&#34;&gt;6.1 消息丢失：各 MQ 如何防范&lt;/h3&gt;
&lt;p&gt;消息丢失通常发生在三个环节：&lt;strong&gt;Producer 发送失败&lt;/strong&gt;、&lt;strong&gt;Broker 存储失败&lt;/strong&gt;、&lt;strong&gt;Consumer 消费失败未 ACK&lt;/strong&gt;。四种 MQ 的防范方式如下：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;环节&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&lt;/th&gt;
					&lt;th&gt;Redis MQ&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Producer 发送&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同步发送 &lt;code&gt;SYNC&lt;/code&gt; + 失败重试；避免 &lt;code&gt;ONEWAY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;使用 &lt;code&gt;confirm&lt;/code&gt; 模式等待 Broker ACK；开启 Publisher Confirms&lt;/td&gt;
					&lt;td&gt;设置 &lt;code&gt;acks=all&lt;/code&gt; + 重试；避免 &lt;code&gt;acks=0/1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;无发送确认机制，调用成功不代表持久化成功；重要消息不要用 Redis PubSub&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Broker 存储&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同步刷盘 &lt;code&gt;SYNC_FLUSH&lt;/code&gt; + 同步复制 &lt;code&gt;SYNC_MASTER&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;队列持久化 + 镜像队列复制；消息标记 &lt;code&gt;persistent&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;replication.factor &amp;gt;= 3&lt;/code&gt; + &lt;code&gt;min.insync.replicas&lt;/code&gt; 控制&lt;/td&gt;
					&lt;td&gt;AOF/ RDB 持久化，但默认异步；主从切换可能丢数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Consumer 消费&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;先处理业务再返回 &lt;code&gt;CONSUME_SUCCESS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;手动 ACK，处理完再 &lt;code&gt;basic.ack&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;处理完再 commit offset&lt;/td&gt;
					&lt;td&gt;Stream 用 &lt;code&gt;XACK&lt;/code&gt; 确认；List/ PubSub 无 ACK&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;td&gt;不建议做核心兜底，Redis 本身不是可靠 MQ&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;&lt;strong&gt;RocketMQ&lt;/strong&gt;：&lt;code&gt;SYNC_MASTER&lt;/code&gt; + &lt;code&gt;SYNC_FLUSH&lt;/code&gt; 配置最安全，但性能最低；在线交易通常用 &lt;code&gt;SYNC_MASTER&lt;/code&gt; + &lt;code&gt;ASYNC_FLUSH&lt;/code&gt; 折中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;：默认自动 ACK 最容易丢消息，生产环境一定要改手动 ACK。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka&lt;/strong&gt;：&lt;code&gt;acks=all&lt;/code&gt; 配合 &lt;code&gt;min.insync.replicas&lt;/code&gt; 才能保证写入 ISR 多数副本；只配置 &lt;code&gt;acks=all&lt;/code&gt; 不够。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;：Redis 本身不是为消息可靠性设计的，&lt;strong&gt;不要把核心交易消息的防丢失交给 Redis&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-消息重复各-mq-的投递语义与幂等&#34;&gt;6.2 消息重复：各 MQ 的投递语义与幂等&lt;/h3&gt;
&lt;p&gt;四种 MQ 大多只保证「至少一次投递」（at-least-once），重复消费要靠业务层幂等。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;MQ&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;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;At-least-once&lt;/td&gt;
					&lt;td&gt;网络超时重发、Consumer 重启、Broker 重试&lt;/td&gt;
					&lt;td&gt;消息 &lt;code&gt;keys&lt;/code&gt; 做唯一业务 Key + 数据库唯一索引&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;At-least-once&lt;/td&gt;
					&lt;td&gt;连接断开、ACK 未到达 Broker、Consumer 重启&lt;/td&gt;
					&lt;td&gt;消息 &lt;code&gt;message_id&lt;/code&gt; 或业务 Key + 幂等表&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;At-least-once&lt;/td&gt;
					&lt;td&gt;Consumer Rebalance、Offset 提交失败、Producer 重试&lt;/td&gt;
					&lt;td&gt;业务 Key 去重；Kafka 幂等 Producer 只能去重单会话内重复&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;不保证&lt;/td&gt;
					&lt;td&gt;PubSub 不持久化，断线即丢；Stream 相对可靠但无原生重试&lt;/td&gt;
					&lt;td&gt;业务侧用 Redis &lt;code&gt;SETNX&lt;/code&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;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-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 通用幂等伪代码，适用于四种 MQ&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;consume&lt;/span&gt;(msg):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    biz_key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; extract_biz_key(msg)  &lt;span style=&#34;color:#75715e&#34;&gt;# RocketMQ keys / RabbitMQ message_id / Kafka 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:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idempotent_record&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;insert(biz_key&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;biz_key, status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;processing&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;except&lt;/span&gt; UniqueViolation:
&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; SUCCESS  &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:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        do_business(msg)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idempotent_record&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;update(biz_key, status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;done&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;return&lt;/span&gt; SUCCESS
&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;except&lt;/span&gt; RetryableException:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idempotent_record&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;update(biz_key, status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed&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;return&lt;/span&gt; RETRY
&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;except&lt;/span&gt; NonRetryableException:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idempotent_record&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;update(biz_key, status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed&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;return&lt;/span&gt; DEAD_LETTER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;63-消费失败重试与死信机制&#34;&gt;6.3 消费失败：重试与死信机制&lt;/h3&gt;
&lt;p&gt;消费失败后，不同 MQ 的重试和死信（DLQ）机制差异很大：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;MQ&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;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;默认最多 16 次，间隔阶梯递增（10s、30s、1m&amp;hellip;）&lt;/td&gt;
					&lt;td&gt;自动进入 &lt;code&gt;%DLQ%consumer_group&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可区分 &lt;code&gt;RECONSUME_LATER&lt;/code&gt; 重试和直接成功；重试次数可在 Broker 配置&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无内置重试，需业务侧或插件实现；常用 TTL + 死信交换器（DLX）做延迟重试&lt;/td&gt;
					&lt;td&gt;通过 &lt;code&gt;x-dead-letter-exchange&lt;/code&gt; 配置 DLX/DLQ&lt;/td&gt;
					&lt;td&gt;手动 ACK 失败或 NACK + requeue=false 进入死信&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无内置重试次数，失败时 Consumer 自己决定是否重试或跳过&lt;/td&gt;
					&lt;td&gt;无原生 DLQ，需业务侧或 Kafka Connect 实现&lt;/td&gt;
					&lt;td&gt;常见做法：失败消息写入专用 Error Topic，由补偿服务处理&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无内置重试&lt;/td&gt;
					&lt;td&gt;无原生 DLQ&lt;/td&gt;
					&lt;td&gt;Stream 可配合 Consumer Group 的 Pending List 人工处理；List/ PubSub 无重试能力&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;flowchart TD
    A[Consumer 处理失败] --&amp;gt; B{MQ 类型}
    B --&amp;gt;|RocketMQ| C[返回 RECONSUME_LATER]
    B --&amp;gt;|RabbitMQ| D[NACK + requeue=false 进 DLX]
    B --&amp;gt;|Kafka| E[写入 Error Topic 或暂停消费]
    B --&amp;gt;|Redis Stream| F[XCLAIM 或人工处理 Pending]
    C --&amp;gt; G{重试超限?}
    G --&amp;gt;|是| H[进入 DLQ]
    G --&amp;gt;|否| C
    D --&amp;gt; H
    E --&amp;gt; I[补偿服务消费 Error Topic]
    F --&amp;gt; J[业务侧补偿]&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;：参数非法、依赖返回 4xx、业务规则冲突、数据不存在。&lt;/p&gt;
&lt;h3 id=&#34;64-业务异常处理&#34;&gt;6.4 业务异常处理&lt;/h3&gt;
&lt;p&gt;业务异常（如参数错误、状态冲突、规则校验失败）和系统异常（网络、DB、依赖故障）要分开处理。&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[Consumer 捕获异常] --&amp;gt; B{异常类型}
    B --&amp;gt;|系统异常&amp;lt;br/&amp;gt;网络/DB/依赖| C[MQ 重试]
    B --&amp;gt;|业务异常&amp;lt;br/&amp;gt;参数/规则/状态冲突| D[记录失败明细]
    D --&amp;gt; E{能否修复?}
    E --&amp;gt;|能| F[人工补偿/修复后重投]
    E --&amp;gt;|不能| G[进死信队列或丢弃]
    C --&amp;gt; H{重试成功?}
    H --&amp;gt;|否| I[进入死信/Error Topic]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;MQ&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;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;捕获业务异常后，直接返回 &lt;code&gt;CONSUME_SUCCESS&lt;/code&gt; 并记录失败原因，避免无意义重试；或主动放入自定义补偿 Topic。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;业务异常时 &lt;code&gt;basic.nack(requeue=false)&lt;/code&gt; 直接进死信，不要在 RabbitMQ 里反复重试业务错误。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;业务异常写入 Error Topic，由补偿服务按业务规则处理；Consumer 继续消费后续消息，避免阻塞 Partition。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Redis 不适合复杂异常处理；业务异常建议直接记录到数据库或日志，人工介入。&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;：消息内容、异常原因、时间、Consumer 实例、业务 Key。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人工补偿平台&lt;/strong&gt;：按业务 Key 重投、跳过、标记已处理。&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;：参数错误重试 16 次没有意义。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;65-消息卡住消费停滞&#34;&gt;6.5 消息卡住/消费停滞&lt;/h3&gt;
&lt;p&gt;消费停滞是四种 MQ 都会遇到的异常，但原因和处理方式各有侧重：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;MQ&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;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Consumer 实例少、单条处理慢、Consumer 宕机、下游依赖慢&lt;/td&gt;
					&lt;td&gt;扩容 Consumer（不超过 Queue 数）、优化消费逻辑、限流、降级&lt;/td&gt;
					&lt;td&gt;ConsumeProgress 堆积量、消费 TPS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;队列堆积导致内存/磁盘告警、Consumer 连接断开、Channel 阻塞&lt;/td&gt;
					&lt;td&gt;扩容 Consumer、控制生产速度、启用队列长度限制&lt;/td&gt;
					&lt;td&gt;Queue 深度、内存使用率、磁盘告警&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Partition 消费滞后、Consumer Rebalance 频繁、单 Partition 热点&lt;/td&gt;
					&lt;td&gt;增加 Partition、优化 Key 设计、调整 &lt;code&gt;session.timeout.ms&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Consumer Lag、Rebalance 频率&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;内存不足、Consumer 断线（PubSub 丢消息）、处理慢&lt;/td&gt;
					&lt;td&gt;控制队列长度、设置过期时间、用 Stream 替代 PubSub&lt;/td&gt;
					&lt;td&gt;Redis 内存、Pending List 长度&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;flowchart LR
    A[消息堆积] --&amp;gt; B{原因}
    B --&amp;gt;|Consumer 少| C[扩容 Consumer]
    B --&amp;gt;|单条处理慢| D[优化消费逻辑/异步化]
    B --&amp;gt;|Consumer 宕机| E[心跳超时触发重平衡]
    B --&amp;gt;|下游依赖慢| F[限流/降级/异步处理]
    B --&amp;gt;|Partition/Queue 热点| G[调整 Key 或增加分区]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;66-顺序消息异常&#34;&gt;6.6 顺序消息异常&lt;/h3&gt;
&lt;p&gt;顺序消息一旦某条处理卡住，后续消息都会被阻塞，四种 MQ 的处理方式不同：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;MQ&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;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Sharding Key 路由到同一 MessageQueue，单线程消费&lt;/td&gt;
					&lt;td&gt;一条失败会阻塞整个 Queue 的后续消息&lt;/td&gt;
					&lt;td&gt;设置消费超时；失败次数过多时考虑跳过或转移；业务上保证单条处理轻量&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;单 Queue 天然有序，单 Consumer 单线程消费&lt;/td&gt;
					&lt;td&gt;一个 Consumer 卡住会阻塞整个 Queue&lt;/td&gt;
					&lt;td&gt;控制单条处理时间；必要时拆分多个 Queue 做局部顺序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同一 Partition 内有序，单 Consumer 消费 Partition&lt;/td&gt;
					&lt;td&gt;一条消息处理慢会阻塞整个 Partition&lt;/td&gt;
					&lt;td&gt;合理设计 Key，避免单个 Partition 成为热点；业务异常时写入 Error Topic 避免阻塞&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Redis MQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Stream/ List 单节点内有序&lt;/td&gt;
					&lt;td&gt;单 Consumer 卡住会阻塞后续消费&lt;/td&gt;
					&lt;td&gt;不建议用 Redis 做强顺序消息；处理异常时直接记录并人工处理&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;flowchart LR
    A[Queue/Partition 内顺序消费] --&amp;gt; B[消息 1 处理失败或超慢]
    B --&amp;gt; C[消息 2 等待]
    C --&amp;gt; D[消息 3 等待]
    B --&amp;gt; E{如何处理?}
    E --&amp;gt;|跳过| F[破坏顺序性]
    E --&amp;gt;|重试| G[阻塞后续消息]
    E --&amp;gt;|转移| H[进死信/Error Topic]&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;/li&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;ol&gt;
&lt;li&gt;&lt;strong&gt;选型不是单选题&lt;/strong&gt;：RocketMQ 适合在线业务，Kafka 适合日志/流处理，RabbitMQ 适合轻量快速落地，Redis MQ 适合轻量辅助场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推/拉不是对立关系&lt;/strong&gt;：RocketMQ 的 Push 底层是长轮询 Pull；RabbitMQ 是真 Push；Kafka 是真 Pull。选择看「谁来控制消费节奏」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长连接是双刃剑&lt;/strong&gt;：实时性好，但连接维护、背压、心跳都要考虑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常处理要分层&lt;/strong&gt;：发送层确认、Broker 层持久化、消费层幂等 + 重试 + 死信。&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;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ftmao.com/blog/blog/posts/rocketmq-fundamentals/&#34;&gt;RocketMQ 基础科普：推/拉/通知模式、异常处理与高并发实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;《RocketMQ 实战与核心原理》&lt;/li&gt;
&lt;li&gt;《RocketMQ 技术内幕》&lt;/li&gt;
&lt;li&gt;《Kafka 权威指南》&lt;/li&gt;
&lt;li&gt;《RabbitMQ 实战指南》&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;如果对你有帮助，欢迎在评论区交流你在 MQ 选型或异常处理上的经验。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>RocketMQ 基础科普：推/拉/通知模式、异常处理与高并发实践</title>
      <link>https://ftmao.com/blog/posts/rocketmq-fundamentals/</link>
      <pubDate>Sun, 28 Jun 2020 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/rocketmq-fundamentals/</guid>
      <description>&lt;h1 id=&#34;rocketmq-基础科普推拉通知模式异常处理与高并发实践&#34;&gt;RocketMQ 基础科普：推/拉/通知模式、异常处理与高并发实践&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;这篇文章把 RocketMQ 当成一个「分布式异步任务管道」来理解，重点回答六个问题：RocketMQ 和 RabbitMQ、Kafka 怎么选？它们的底层原理差异是什么？消费者到底该推、该拉还是该通知？消息异常了怎么处理？怎么让它扛住高并发？以及那些年我们踩过的坑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一rocketmq-是什么一句话定位&#34;&gt;一、RocketMQ 是什么？一句话定位&lt;/h2&gt;
&lt;p&gt;RocketMQ 是阿里巴巴开源的&lt;strong&gt;分布式消息中间件&lt;/strong&gt;，核心定位是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;高吞吐、低延迟、可堆积、可扩展的异步消息传输与解耦基础设施。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&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;/ol&gt;
&lt;p&gt;RocketMQ 的核心角色很简单：&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
    Producer[&amp;#34;Producer（消息生产者）&amp;#34;]
    NameServer[&amp;#34;NameServer（路由发现）&amp;#34;]
    Broker[&amp;#34;Broker（存储与转发消息）&amp;#34;]
    Consumer[&amp;#34;Consumer（拉取/接收消息）&amp;#34;]

    Producer -.路由发现.-&amp;gt; NameServer
    Producer --&amp;gt; Broker
    Consumer -.路由发现.-&amp;gt; NameServer
    Broker --&amp;gt; Consumer&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Producer&lt;/strong&gt;：消息生产者，负责把业务消息发到 Broker。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broker&lt;/strong&gt;：消息存储和转发节点，分 Master/Slave，负责持久化、投递、重试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NameServer&lt;/strong&gt;：轻量级路由注册中心，Producer 和 Consumer 通过它发现 Broker。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consumer&lt;/strong&gt;：消息消费者，从 Broker 读取消息并处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二横向对比rocketmq--rabbitmq--kafka&#34;&gt;二、横向对比：RocketMQ / RabbitMQ / Kafka&lt;/h2&gt;
&lt;p&gt;RocketMQ、RabbitMQ、Kafka 是国内最常见的三种消息中间件。它们都能做异步解耦，但设计哲学差异很大，选型时要结合业务场景、团队能力、运维成本一起看。&lt;/p&gt;
&lt;h3 id=&#34;21-核心定位差异&#34;&gt;2.1 核心定位差异&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&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;Erlang 实现，面向通用企业消息&lt;/td&gt;
					&lt;td&gt;LinkedIn 开源，面向日志采集与流处理&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;数据模型&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Topic + Queue，支持 Tag 过滤&lt;/td&gt;
					&lt;td&gt;Exchange + Queue，路由灵活&lt;/td&gt;
					&lt;td&gt;Topic + Partition，分区有序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;默认消费方式&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Push（长轮询封装）+ Pull&lt;/td&gt;
					&lt;td&gt;Push（AMQP 推送）&lt;/td&gt;
					&lt;td&gt;Pull（消费者主动拉取）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;顺序消息&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;分片内有序，Sharding Key 控制&lt;/td&gt;
					&lt;td&gt;单 Queue 有序&lt;/td&gt;
					&lt;td&gt;Partition 内有序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;延迟消息&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;原生支持 18 个级别&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;22-性能对比&#34;&gt;2.2 性能对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&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;10 万级 TPS&lt;/td&gt;
					&lt;td&gt;万级 TPS&lt;/td&gt;
					&lt;td&gt;百万级 TPS（批量场景）&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;p&gt;&lt;strong&gt;说明&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kafka 的吞吐数字通常来自批量、压缩、顺序写的日志场景；单条小消息低延迟不如 RocketMQ。&lt;/li&gt;
&lt;li&gt;RabbitMQ 延迟低，但高吞吐和海量堆积不是强项。&lt;/li&gt;
&lt;li&gt;RocketMQ 在「中等吞吐 + 低延迟 + 强堆积」之间取得了较好平衡。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;23-成本对比&#34;&gt;2.3 成本对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&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;中等（NameServer + Broker 集群）&lt;/td&gt;
					&lt;td&gt;低（单节点即可运行）&lt;/td&gt;
					&lt;td&gt;高（ZooKeeper/KRaft + Broker）&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;低，但 Erlang 问题排查门槛高&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;阿里云 RocketMQ 成熟&lt;/td&gt;
					&lt;td&gt;各云厂商都有托管版&lt;/td&gt;
					&lt;td&gt;各云厂商 Kafka 托管版成熟&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;24-生态对比&#34;&gt;2.4 生态对比&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;RocketMQ&lt;/th&gt;
					&lt;th&gt;RabbitMQ&lt;/th&gt;
					&lt;th&gt;Kafka&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;Java 最完善，其他语言有社区版&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;一般，可与 Flink/Spark 对接&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;自带 Management UI + Prometheus&lt;/td&gt;
					&lt;td&gt;Kafka UI + Prometheus + 自建&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;社区活跃度&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;国内活跃，Apache 项目&lt;/td&gt;
					&lt;td&gt;成熟稳定，Pivotal/VMware 背景&lt;/td&gt;
					&lt;td&gt;极其活跃，Confluent 生态&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;25-适用场景推荐&#34;&gt;2.5 适用场景推荐&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;/td&gt;
					&lt;td&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;延迟低、事务消息、顺序消息、监控完善&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;异步任务、分布式 Worker、视频渲染&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;PushConsumer 自动负载均衡，失败重试完善&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;传统企业应用、RPC 解耦、路由复杂&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;简单易用，Exchange 路由灵活，快速上线&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;日志采集、埋点、实时数仓、流处理&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&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;strong&gt;RabbitMQ / Kafka&lt;/strong&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;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;原生事务消息是差异化能力&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;26-一句话选型建议&#34;&gt;2.6 一句话选型建议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;要可靠交易、事务消息、顺序消息、在线业务&lt;/strong&gt; → RocketMQ&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要快速搭建、路由灵活、团队规模小&lt;/strong&gt; → RabbitMQ&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要日志/流处理/大数据管道、吞吐优先&lt;/strong&gt; → Kafka&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;很多大厂内部其实是「Kafka 做日志流 + RocketMQ 做业务消息 + RabbitMQ 做轻量任务」组合使用，而不是单选题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;27-底层原理与核心差异&#34;&gt;2.7 底层原理与核心差异&lt;/h3&gt;
&lt;p&gt;表面的功能差异（延迟消息、事务消息）背后，是三种产品在&lt;strong&gt;存储模型、路由发现、复制机制、消费模型&lt;/strong&gt;上的根本不同。理解这些底层差异，才能真正解释「为什么 Kafka 吞吐高」「为什么 RocketMQ 延迟低」「为什么 RabbitMQ 路由灵活」。&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;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;CommitLog + ConsumeQueue&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;所有 Topic 的消息顺序写入同一个 CommitLog，再利用 ConsumeQueue 存储每个 Queue 的偏移索引。读消息时先查 ConsumeQueue 拿到物理偏移，再读 CommitLog。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Partition Segment + Index&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每个 Partition 是一个独立的日志目录，顺序写入 Segment 文件，配合稀疏索引加速消费。Partition 之间物理隔离。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Queue 独立存储&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每个 Queue 维护独立的消息索引和存储，默认写入 Mnesia/ETS，持久化消息写磁盘。Queue 越多，元数据开销越大。&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;&lt;strong&gt;RocketMQ 的 CommitLog 是全局顺序写&lt;/strong&gt;，不管有多少 Topic/Queue，磁盘写入都是顺序的，因此单机 I/O 效率很高；读取时需要两次 I/O（ConsumeQueue → CommitLog），但 ConsumeQueue 很小，通常能缓存命中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka 的 Partition 是物理隔离的&lt;/strong&gt;，每个 Partition 内部顺序写，但 Partition 多了会形成随机写，所以 Kafka 更适合少量 Topic + 大量 Partition 的日志场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ 的 Queue 是逻辑和物理都独立的&lt;/strong&gt;，每条消息进入 Queue 都要维护索引，高吞吐下容易成为瓶颈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RocketMQ 存储模型：&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
    CL[&amp;#34;CommitLog（顺序写）&amp;lt;br/&amp;gt;[msg1][msg2][msg3][msg4][msg5]...&amp;#34;]
    CQ1[&amp;#34;ConsumeQueue&amp;lt;br/&amp;gt;TopicA-Q0&amp;#34;]
    CQ2[&amp;#34;ConsumeQueue&amp;lt;br/&amp;gt;TopicB-Q0&amp;#34;]

    CL -- &amp;#34;写入后分发偏移索引&amp;#34; --&amp;gt; CQ1
    CL -- &amp;#34;写入后分发偏移索引&amp;#34; --&amp;gt; CQ2&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;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;flowchart LR
    P0[&amp;#34;TopicA-P0 Log&amp;lt;br/&amp;gt;[msg1][msg2]...&amp;#34;]
    P1[&amp;#34;TopicA-P1 Log&amp;lt;br/&amp;gt;[msg1][msg2]...&amp;#34;]&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;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;NameServer&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无状态集群，Broker 主动上报路由信息，Producer/Consumer 定时拉取。最终一致，轻量。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;ZooKeeper / KRaft&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;老版本依赖 ZooKeeper 存元数据、选 Controller；新版本用 KRaft 自洽协议。强一致要求更高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;内置 Mnesia 数据库&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;集群节点共享元数据，Erlang 进程模型天然分布式，但集群扩展性相对有限。&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;&lt;strong&gt;NameServer 是「轻量级最终一致」&lt;/strong&gt;：Broker 启动时向所有 NameServer 注册，Producer 从 NameServer 拿到路由后缓存使用。Broker 宕机后，NameServer 不会主动通知 Producer，而是靠 Producer 心跳/发送失败时刷新路由。这种设计牺牲了一定的实时性，换来了极简的部署和无状态扩展能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka 的 Controller 负责元数据一致性&lt;/strong&gt;：分区副本分配、Leader 选举、ISR 维护都由 Controller 处理，更复杂但也更精确。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ 的元数据在节点间同步&lt;/strong&gt;：小集群简单，大集群（几百个 Queue）容易出现脑裂和同步压力。&lt;/li&gt;
&lt;/ul&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;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Master-Slave 同步/异步复制&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;一个 Master 对应一个或多个 Slave。同步复制保证不丢消息，异步复制性能更高。Slave 可读可切换（需要人工或脚本介入）。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Partition 多副本 + ISR 机制&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每个 Partition 有 N 个副本，Leader 写，Follower 拉取；只有 ISR 中的副本才能竞选 Leader。自动 Failover，成熟度高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&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;p&gt;&lt;strong&gt;关键差异&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RocketMQ 的复制是 Broker 级别&lt;/strong&gt;，Master 上所有 Queue 一起复制到 Slave；Kafka 是 Partition 级别，复制粒度更细。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka 的 ISR 是动态维护的&lt;/strong&gt;，写入延迟受最慢 Follower 影响；RocketMQ 的同步复制是 Master 等待 Slave ACK，延迟相对可控。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ 默认不复制&lt;/strong&gt;，需要显式配置镜像队列，否则节点宕机就丢队列。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;消费模型与-offset-管理&#34;&gt;消费模型与 Offset 管理&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;Offset 管理&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;长轮询 Pull 封装成 Push&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Consumer 本地维护消费进度，定时上报 Broker。支持集群消费和广播消费。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;消费者主动 Pull&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Offset 存在 Kafka 内部 Topic（&lt;code&gt;__consumer_offsets&lt;/code&gt;），Consumer 提交进度。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Broker Push（AMQP）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无 Offset 概念，消息 ACK 后从 Queue 移除。不支持消息回溯。&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;&lt;strong&gt;RocketMQ 的 Push 是「假 Push」&lt;/strong&gt;：Consumer 向 Broker 发起长轮询请求（默认 5 秒），Broker hold 住连接，有新消息时立即返回，没消息时超时返回再轮询。这样既实现了低延迟，又保留了 Pull 的背压能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka 的 Pull 是「真 Pull」&lt;/strong&gt;：Consumer 自己决定什么时候拉、拉多少，框架不负责推送，灵活性最高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ 的 Push 是「真 Push」&lt;/strong&gt;：Broker 把消息塞给 Channel，Consumer 处理完 ACK。如果 Consumer 处理慢，Broker 会堆积在内存或磁盘，容易把 Broker 压垮。&lt;/li&gt;
&lt;/ul&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;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同一 Topic 下，相同 Sharding Key 的消息发送到同一个 Message Queue；Consumer 单线程消费该 Queue&lt;/td&gt;
					&lt;td&gt;全局顺序需要所有消息进一个 Queue，吞吐极低&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同一 Partition 内有序；Producer 按 Key 路由到固定 Partition&lt;/td&gt;
					&lt;td&gt;同 RocketMQ，Partition 内有序，全局有序不可行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;单 Queue 天然有序；多个 Consumer 竞争消费时会乱序&lt;/td&gt;
					&lt;td&gt;要顺序消费只能单 Consumer 单线程&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;三种 MQ 的所谓「顺序消息」本质上都是&lt;strong&gt;把需要顺序的消息收敛到同一个最小调度单元&lt;/strong&gt;（Queue / Partition），然后单线程消费。不存在真正的全局顺序高吞吐方案。&lt;/p&gt;
&lt;/blockquote&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;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;内置 18 个延迟级别，消息先进入 SCHEDULE_TOPIC_XXXX，到期后转回真实 Topic&lt;/td&gt;
					&lt;td&gt;时间粒度固定，不支持任意时间；实现成熟可靠&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无原生支持&lt;/td&gt;
					&lt;td&gt;常用方案：时间轮、RocketMQ 中转、Pulsar 等替代&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;通过 TTL + 死信队列（DLX）实现&lt;/td&gt;
					&lt;td&gt;每个延迟级别需要一个 Queue，灵活但配置繁琐&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;&lt;strong&gt;RocketMQ&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;半消息 + 事务回查&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Producer 先发「半消息」（对消费者不可见），本地事务成功后 Commit，失败则 Rollback；Broker 会回查超时事务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;无原生事务消息&lt;/td&gt;
					&lt;td&gt;Kafka 有幂等 Producer 和事务 API，但那是「跨分区事务」，不是「本地事务 + 消息发送」的事务消息&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RabbitMQ&lt;/strong&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;RocketMQ 事务消息的核心流程：&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;1. Producer 发送 Half Message 到 Broker&amp;#34;] --&amp;gt; B[&amp;#34;2. Producer 执行本地事务（如扣减库存）&amp;#34;]
    B --&amp;gt; C{&amp;#34;本地事务结果&amp;#34;}
    C --&amp;gt;|&amp;#34;成功&amp;#34;| D[&amp;#34;Commit&amp;#34;]
    C --&amp;gt;|&amp;#34;失败&amp;#34;| E[&amp;#34;Rollback&amp;#34;]
    D --&amp;gt; F[&amp;#34;3. Broker 投递消息给消费者&amp;#34;]
    E --&amp;gt; G[&amp;#34;3. Broker 删除半消息&amp;#34;]
    C -. &amp;#34;Producer 无响应&amp;#34; .-&amp;gt; H[&amp;#34;Broker 主动回查事务状态&amp;#34;]
    H --&amp;gt; C&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 id=&#34;为什么-rocketmq-更适合在线业务&#34;&gt;为什么 RocketMQ 更适合在线业务？&lt;/h4&gt;
&lt;p&gt;把底层差异串起来看，答案就清晰了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CommitLog + ConsumeQueue 让它在消息量不大时延迟很低&lt;/strong&gt;（两次索引查询 + 顺序读），适合在线交易这种「消息体小、QPS 高、延迟敏感」的场景。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NameServer 轻量无状态&lt;/strong&gt;，部署和扩容简单，不像 Kafka 强依赖 ZooKeeper/KRaft。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原生事务消息和延迟消息&lt;/strong&gt;，是电商、金融场景的刚需，其他两个 MQ 都需要业务侧自己造轮子。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长轮询 Push 模型&lt;/strong&gt;对业务开发者友好，自动重平衡、失败重试、死信队列开箱即用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而 Kafka 更适合&lt;strong&gt;海量日志、流计算&lt;/strong&gt;这种「吞吐优先、延迟可接受、需要分区有序」的场景；RabbitMQ 更适合&lt;strong&gt;轻量解耦、路由复杂、快速上线&lt;/strong&gt;的场景。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三推拉通知三种消费模式怎么选&#34;&gt;三、推、拉、通知三种消费模式：怎么选？&lt;/h2&gt;
&lt;p&gt;RocketMQ 的消费端主要提供 &lt;strong&gt;Push&lt;/strong&gt; 和 &lt;strong&gt;Pull&lt;/strong&gt; 两种消费方式，实际工程里还经常配合 &lt;strong&gt;Notify/回调&lt;/strong&gt; 一起用。三者不是非此即彼，而是适用场景不同。&lt;/p&gt;
&lt;h3 id=&#34;31-pull-模式消费者主动拉取&#34;&gt;3.1 Pull 模式：消费者主动拉取&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 C as Consumer
    participant B as Broker
    C-&amp;gt;&amp;gt;B: 有消息吗？
    B-&amp;gt;&amp;gt;C: 有，拿去吧&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;/li&gt;
&lt;li&gt;可以灵活控制拉取频率、批量大小、并发量。&lt;/li&gt;
&lt;li&gt;代码稍复杂，需要自己维护 offset、拉取循环、负载均衡。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&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;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;需要自管 offset 和重平衡&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;32-push-模式broker-主动推送给消费者&#34;&gt;3.2 Push 模式：Broker 主动推送给消费者&lt;/h3&gt;
&lt;p&gt;RocketMQ 的 PushConsumer 本质上也是 &lt;strong&gt;Pull 的长轮询封装&lt;/strong&gt;，但对业务代码来说更像「Broker 推过来」。&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 C as Consumer
    participant B as Broker
    C-&amp;gt;&amp;gt;B: 注册长轮询
    B-&amp;gt;&amp;gt;C: 来消息了，处理一下&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;/li&gt;
&lt;li&gt;RocketMQ 自动处理 offset、重平衡、失败重试。&lt;/li&gt;
&lt;li&gt;实时性好，能立刻感知新消息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&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;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;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;33-notify--回调模式处理完主动通知上游&#34;&gt;3.3 Notify / 回调模式：处理完主动通知上游&lt;/h3&gt;
&lt;p&gt;消息队列本身不负责回调，但业务常把 MQ 和回调配合起来用：&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;#34;] --&amp;gt; MQ[&amp;#34;MQ&amp;#34;] --&amp;gt; C[&amp;#34;消费者处理&amp;#34;] --&amp;gt; N[&amp;#34;回调 / 事件总线 / SSE&amp;#34;] --&amp;gt; U[&amp;#34;上游或其他系统&amp;#34;]&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;不是 RocketMQ 原生消费模式，而是业务架构模式。&lt;/li&gt;
&lt;li&gt;适合「异步任务完成后需要通知调用方」的场景，比如视频渲染、订单状态变更。&lt;/li&gt;
&lt;li&gt;通常需要配合幂等、状态机、重试策略一起设计。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适合场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务完成后需要反向通知 UI、APP、第三方系统。&lt;/li&gt;
&lt;li&gt;长耗时异步任务 + 进度推送。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;34-选择原则总结&#34;&gt;3.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;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;strong&gt;PushConsumer&lt;/strong&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;strong&gt;PullConsumer&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;控制力强，避免被消息压垮&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;数据同步、ETL、定时批量&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Pull / 批量拉取&lt;/strong&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;strong&gt;Push + 业务回调/Notify&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;MQ 解耦 + 回调闭环&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;不确定怎么选&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;先 Push，遇到瓶颈再切 Pull&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Push 是默认最佳实践&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个常见误区是认为 Push 一定比 Pull 先进。实际上 RocketMQ 的 Push 底层也是拉，只是框架帮你做了。真正决定选型的不是「推或拉」，而是 &lt;strong&gt;「谁来控制消费节奏」和「实时性与吞吐的优先级」&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四异常处理消息丢了重复了卡住了怎么办&#34;&gt;四、异常处理：消息丢了、重复了、卡住了怎么办？&lt;/h2&gt;
&lt;p&gt;消息队列的异常处理是面试和工程中最核心的部分。RocketMQ 提供了机制，但业务层必须配合。&lt;/p&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;flowchart LR
    P[&amp;#34;Producer&amp;#34;] -- &amp;#34;发送失败&amp;#34; --&amp;gt; B[&amp;#34;Broker&amp;#34;] -- &amp;#34;刷盘/同步失败&amp;#34; --&amp;gt; C[&amp;#34;Consumer&amp;#34;] -- &amp;#34;消费失败未重试&amp;#34; --&amp;gt; Biz[&amp;#34;业务&amp;#34;]&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;Producer 发送&lt;/td&gt;
					&lt;td&gt;同步发送 + 失败重试&lt;/td&gt;
					&lt;td&gt;重要消息用 &lt;code&gt;send_sync&lt;/code&gt;，不要用 oneway&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Broker 存储&lt;/td&gt;
					&lt;td&gt;同步刷盘 + 主从同步&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;SYNC_MASTER&lt;/code&gt; + &lt;code&gt;SYNC_FLUSH&lt;/code&gt; 保证落盘&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Consumer 消费&lt;/td&gt;
					&lt;td&gt;先处理业务再返回成功&lt;/td&gt;
					&lt;td&gt;不要先 ACK 再执行业务&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;RocketMQ 的发送确认策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SYNC&lt;/strong&gt;：同步等待 Broker ACK，最可靠但最慢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ASYNC&lt;/strong&gt;：异步发送，失败时回调，吞吐高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ONEWAY&lt;/strong&gt;：只发不管，最快但不可靠，适合日志等可丢场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-消息重复不可避免靠幂等解决&#34;&gt;4.2 消息重复：不可避免，靠幂等解决&lt;/h3&gt;
&lt;p&gt;RocketMQ 保证「至少一次投递」，不保证「恰好一次」。网络抖动、Broker 重试、Consumer 重启都会导致重复消费。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;幂等设计要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;唯一业务 Key&lt;/strong&gt;：发送时给消息设置 &lt;code&gt;keys&lt;/code&gt;，消费时用业务 Key 去重。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据库唯一索引&lt;/strong&gt;：把业务 Key 作为幂等表的唯一键，重复插入会失败。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态机防重&lt;/strong&gt;：如订单状态只能 &lt;code&gt;pending → paid → shipped&lt;/code&gt;，重复消息不会触发非法流转。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis 去重&lt;/strong&gt;：短期用 &lt;code&gt;SETNX msg_key 1&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;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-python&#34; data-lang=&#34;python&#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;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;consume&lt;/span&gt;(msg):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    biz_key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; msg&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;keys
&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;        idempotent_record&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;insert(biz_key&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;biz_key, status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;processing&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;except&lt;/span&gt; UniqueViolation:
&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; ConsumeStatus&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CONSUME_SUCCESS  &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:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        do_business(msg)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idempotent_record&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;update(biz_key, status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;done&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;return&lt;/span&gt; ConsumeStatus&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CONSUME_SUCCESS
&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;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Exception&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        idempotent_record&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;update(biz_key, status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed&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;return&lt;/span&gt; ConsumeStatus&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;RECONSUME_LATER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;43-消费失败重试与死信队列&#34;&gt;4.3 消费失败：重试与死信队列&lt;/h3&gt;
&lt;p&gt;RocketMQ 对消费失败的消息会自动重试，默认最多 16 次，间隔逐渐拉长。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;重试间隔（默认值，Broker 端可配置）&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;1&lt;/td&gt;
					&lt;td&gt;10 秒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;30 秒&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;1 分钟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&amp;hellip;&lt;/td&gt;
					&lt;td&gt;递增&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;16&lt;/td&gt;
					&lt;td&gt;约 2 小时&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;重试仍然失败后，消息进入 &lt;strong&gt;死信队列（DLQ）&lt;/strong&gt;，Topic 名为 &lt;code&gt;%DLQ%consumer_group&lt;/code&gt;。&lt;/p&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;：DLQ 不是垃圾桶，需要监控和补偿机制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;区分可重试异常和不可重试异常&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;网络超时 → 可重试&lt;/li&gt;
&lt;li&gt;参数非法 / 依赖返回 4xx / 业务规则冲突 → 不可重试，直接失败或进死信&lt;/li&gt;
&lt;/ul&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;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-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;consume&lt;/span&gt;(msg):
&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;        process(msg)
&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; ConsumeStatus&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CONSUME_SUCCESS
&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;except&lt;/span&gt; RetryableException &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; e:
&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;        log&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;warn(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;retryable: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;e&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&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;return&lt;/span&gt; ConsumeStatus&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;RECONSUME_LATER
&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;except&lt;/span&gt; NonRetryableException &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; e:
&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;        log&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;error(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;non-retryable: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;e&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&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:#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;return&lt;/span&gt; ConsumeStatus&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;CONSUME_SUCCESS  &lt;span style=&#34;color:#75715e&#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;44-消息卡住消费超时与心跳检测&#34;&gt;4.4 消息卡住：消费超时与心跳检测&lt;/h3&gt;
&lt;p&gt;Consumer 处理到一半进程挂了，消息状态可能卡在「处理中」。《RocketMQ 技术内幕》里强调：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;消息队列只保证投递，不保证业务逻辑一定完成。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&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;：Consumer 处理任务时定时更新心跳，超时时由兜底任务重置状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进度监控&lt;/strong&gt;：关注消费进度（ConsumeProgress），发现停滞及时告警。&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;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-python&#34; data-lang=&#34;python&#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;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process_task&lt;/span&gt;(task_id):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    claim_task(task_id, worker_id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;WORKER_ID, ttl&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&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;with&lt;/span&gt; heartbeat_renewal(task_id, interval&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            do_heavy_work(task_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mark_done(task_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;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Exception&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        release_task(task_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;raise&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;RocketMQ 本身的吞吐很高（单机万级 TPS 没问题），但业务侧能不能撑住高并发，取决于怎么用它。&lt;/p&gt;
&lt;h3 id=&#34;51-生产者侧优化&#34;&gt;5.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;/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;把小消息攒成一批发送，减少 RTT&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;send_async&lt;/code&gt;，失败回调补偿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;多 Producer 实例&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;生产端水平扩展，分摊发送压力&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;合理选择 Topic/Queue 数量&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Queue 数影响并行度，太多会增加元数据开销&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;关闭不必要的消息轨迹&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;非核心场景关闭 msg_trace 减少开销&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;52-broker-侧优化&#34;&gt;5.2 Broker 侧优化&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;多 Master 多 Slave，避免单点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;读写分离（Slave 读）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;消费压力大时，从 Slave 拉取减轻 Master 压力&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;ASYNC_FLUSH&lt;/code&gt; 性能更高，但可能丢少量消息&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;分片存储（CommitLog）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;利用顺序写磁盘，提升吞吐&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;消息队列是 IO 密集型，磁盘 SSD、网卡万兆是基础&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;53-消费者侧优化&#34;&gt;5.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;增加 Consumer 实例&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;消费组内实例数 ≤ Queue 数时，扩容能线性提升吞吐&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;提高单实例并发&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;PushConsumer 可配置 consumeThreadMin/Max&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;批量消费&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;一次拉取多条，合并数据库/Redis 操作&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;消费逻辑异步化&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;IO 密集型任务不要阻塞消费线程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;本地缓存热点数据&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;减少重复 RPC/DB 查询&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-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;总消费吞吐 = Consumer 实例数 × 每个实例的并发线程数 × 单线程处理 TPS&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;当 Consumer 实例数达到 Topic 的 Queue 数后，再增加实例也无法提升并行度，这时需要增加 Queue 数或优化单线程处理速度。&lt;/p&gt;
&lt;h3 id=&#34;54-削峰填谷的核心思想&#34;&gt;5.4 削峰填谷的核心思想&lt;/h3&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
    A[&amp;#34;突发流量&amp;#34;] --&amp;gt; B[&amp;#34;Broker 队列堆积&amp;lt;br/&amp;gt;（容忍峰值）&amp;#34;] --&amp;gt; C[&amp;#34;Consumer 按稳定速率消费&amp;lt;br/&amp;gt;（保护下游）&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;所以设计高并发系统时，Queue 的堆积能力本身就是「容量缓冲」。只要 Consumer 能最终追上来，系统就是健康的。&lt;/p&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;&lt;strong&gt;坑&lt;/strong&gt;：同一个 ConsumerGroup 下的不同实例订阅了不同的 Topic 或 Tag，导致重平衡混乱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：一个 ConsumerGroup 只订阅一个 Topic + 一套 Tag 表达式，不要随意变更。&lt;/p&gt;
&lt;h3 id=&#34;62-tag-用得太细&#34;&gt;6.2 Tag 用得太细&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;坑&lt;/strong&gt;：每个业务场景都建一个 Tag，结果消费者端过滤复杂，Topic 管理混乱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：Topic 按业务域划分，Tag 做粗粒度过滤。不要用一个 Topic 承载完全无关的业务。&lt;/p&gt;
&lt;h3 id=&#34;63-消息体过大&#34;&gt;6.3 消息体过大&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;坑&lt;/strong&gt;：把完整对象、图片二进制、大 JSON 塞进消息体，超过 4MB 默认限制，发送失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：消息体只传 ID 和必要字段，完整数据由 Consumer 从存储（DB/Redis/OSS）拉取。&lt;/p&gt;
&lt;h3 id=&#34;64-顺序消息误用&#34;&gt;6.4 顺序消息误用&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;坑&lt;/strong&gt;：为了「简单」把所有消息都当顺序消息发，结果全局顺序严重拖垮吞吐。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：RocketMQ 的顺序消息是「分片内有序」，不是全局有序。只在真正需要局部顺序的场景用（比如同一订单的操作按顺序处理），并且用业务 Key 做 Sharding。&lt;/p&gt;
&lt;h3 id=&#34;65-忽视-nameserver-路由更新延迟&#34;&gt;6.5 忽视 NameServer 路由更新延迟&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;坑&lt;/strong&gt;：Broker 扩缩容后，Producer/Consumer 还没刷新路由，导致发送到已下线的 Broker。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：理解 NameServer 是最终一致的路由发现，扩缩容时留足等待时间或主动触发重连。&lt;/p&gt;
&lt;h3 id=&#34;66-producerconsumer-启动顺序问题&#34;&gt;6.6 Producer/Consumer 启动顺序问题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;坑&lt;/strong&gt;：Consumer 先启动但 Topic 还没创建，订阅失败；或 Producer 先启动但 NameServer 还没 ready。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：生产环境用自动化方式提前创建 Topic 和 ConsumerGroup，不要依赖自动创建。&lt;/p&gt;
&lt;h3 id=&#34;67-忽视监控与告警&#34;&gt;6.7 忽视监控与告警&lt;/h3&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;消息堆积量（Consumer Lag）&lt;/td&gt;
					&lt;td&gt;是否需要扩容 Consumer&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消费 TPS&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;Broker CPU/内存/磁盘 IO&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;68-版本与客户端兼容性&#34;&gt;6.8 版本与客户端兼容性&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;hr&gt;
&lt;h2 id=&#34;七总结&#34;&gt;七、总结&lt;/h2&gt;
&lt;p&gt;RocketMQ 不是银弹，但在大多数分布式异步场景里是非常稳妥的选择。用好它，关键是理解几个核心原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;推/拉/通知不是对立关系&lt;/strong&gt;：Push 适合大多数业务，Pull 适合重任务和批量，Notify 是业务层闭环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常处理要分三层&lt;/strong&gt;：发送层用同步/异步确认，Broker 层用刷盘和主从，消费层用幂等 + 重试 + 死信。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高并发 = 水平扩展 + 削峰填谷&lt;/strong&gt;：Queue 是缓冲，Consumer 是处理单元，单线程效率决定上限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避坑的核心是规范&lt;/strong&gt;：订阅一致、消息体小、顺序消息慎用、监控到位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选型不是单选题&lt;/strong&gt;：RocketMQ 适合在线业务，Kafka 适合日志流处理，RabbitMQ 适合轻量快速落地，大厂往往是组合使用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;底层差异决定选型&lt;/strong&gt;：CommitLog + ConsumeQueue、NameServer、长轮询 Push、原生事务/延迟消息，共同决定了 RocketMQ 在在线业务场景的优势。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;《RocketMQ 实战与核心原理》&lt;/li&gt;
&lt;li&gt;《RocketMQ 技术内幕》&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloud.tencent.com/developer/article/2615437&#34;&gt;RocketMQ 消息消费核心原理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/1978935567824736405&#34;&gt;RocketMQ 高并发与异常处理实践&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;如果你也在用 RocketMQ 做异步化改造，欢迎在评论区交流推拉选型、重试策略或高并发调优经验。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《Elasticsearch 权威指南》详解：零基础读懂 ES 的核心知识体系</title>
      <link>https://ftmao.com/blog/bookstore/elasticsearch-definitive-guide-notes/</link>
      <pubDate>Sat, 20 Jun 2020 09:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/elasticsearch-definitive-guide-notes/</guid>
      <description>&lt;h1 id=&#34;elasticsearch-权威指南详解零基础读懂-es-的核心知识体系&#34;&gt;《Elasticsearch 权威指南》详解：零基础读懂 ES 的核心知识体系&lt;/h1&gt;
&lt;p&gt;《Elasticsearch 权威指南》（Elasticsearch: The Definitive Guide，Clinton Gormley &amp;amp; Zachary Tong 著）是学习 Elasticsearch 公认的第一本书。中文版由 looly 等人组织社区翻译。它不是 API 手册，而是沿着&amp;quot;&lt;strong&gt;为什么这样设计&lt;/strong&gt;&amp;ldquo;的路线，把 ES 从安装、文档 CRUD 一路讲到分布式内核、分词、相关性算分和集群运维。&lt;/p&gt;
&lt;p&gt;这本书基于 ES 1.x/2.x 时代写成，个别细节（如 type 概念、TF/IDF 默认评分）在新版本有变化，但&lt;strong&gt;核心思想至今不变&lt;/strong&gt;。本文面向从未读过这本书的人，按&amp;quot;基础概念 → 分布式原理 → 搜索 → 语言处理 → 聚合 → 建模 → 运维&amp;quot;的顺序完整梳理，配图解和可直接运行的实例。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一elasticsearch-是什么先建立直觉&#34;&gt;一、Elasticsearch 是什么：先建立直觉&lt;/h2&gt;
&lt;p&gt;书的开篇用一句话定义 ES：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Elasticsearch 是一个&lt;strong&gt;分布式的、实时的文档存储与搜索引擎&lt;/strong&gt;，基于 Apache Lucene 构建，通过 RESTful API 暴露 Lucene 的全部威力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lucene 本身是一个 Java 搜索库，强大但难用——ES 的价值在于把 Lucene 包装成一个可以水平扩展、开箱即用的分布式系统。书中列举的典型用户包括 Wikipedia（全文搜索 + 高亮 + 输入即搜 + 拼写纠错）、GitHub（检索 1300 亿行代码）、StackOverflow（&amp;ldquo;你可能想问&amp;quot;的 more-like-this 推荐）。&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;Elasticsearch&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Database（库）&lt;/td&gt;
					&lt;td&gt;Index（索引）&lt;/td&gt;
					&lt;td&gt;逻辑命名空间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Table（表）&lt;/td&gt;
					&lt;td&gt;Type（类型）&lt;/td&gt;
					&lt;td&gt;⚠️ 新版已废弃，理解旧书时需要&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Row（行）&lt;/td&gt;
					&lt;td&gt;Document（文档）&lt;/td&gt;
					&lt;td&gt;一条 JSON 记录&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Column（列）&lt;/td&gt;
					&lt;td&gt;Field（字段）&lt;/td&gt;
					&lt;td&gt;文档的属性&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Schema（表结构）&lt;/td&gt;
					&lt;td&gt;Mapping（映射）&lt;/td&gt;
					&lt;td&gt;字段类型定义&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SQL&lt;/td&gt;
					&lt;td&gt;Query DSL&lt;/td&gt;
					&lt;td&gt;查询语言&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;index（索引，动词）&lt;/td&gt;
					&lt;td&gt;index（索引，动词）&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;：数据库假设&amp;quot;每行结构相同&amp;rdquo;，ES 的文档是无模式的 JSON（虽然实际上有 mapping 约束）；数据库为&amp;quot;精确存取&amp;quot;优化，ES 为&amp;rdquo;&lt;strong&gt;相关性检索&lt;/strong&gt;&amp;ldquo;优化——它回答的问题不是&amp;quot;有没有&amp;rdquo;，而是&amp;quot;&lt;strong&gt;有多相关&lt;/strong&gt;&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;11-十分钟跑起来书中的第一个例子&#34;&gt;1.1 十分钟跑起来：书中的第一个例子&lt;/h3&gt;
&lt;p&gt;全书第一章用一家虚构公司 megacorp 的员工数据贯穿：&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;&lt;span style=&#34;color:#75715e&#34;&gt;# 写入一个员工文档（PUT = 指定 ID 创建/覆盖）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PUT /megacorp/employee/1
&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;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;first_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;John&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;&amp;#34;last_name&amp;#34;&lt;/span&gt;:  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Smith&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;&amp;#34;age&amp;#34;&lt;/span&gt;:        25,
&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;#34;about&amp;#34;&lt;/span&gt;:      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I love to go rock climbing&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;&amp;#34;interests&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sports&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;music&amp;#34;&lt;/span&gt;&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;&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;
&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;# 取回（GET）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/1
&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;GET /megacorp/employee/_search?q&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;last_name:Smith
&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;# 正式搜索：Query DSL（JSON 请求体）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/_search
&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;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;last_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Smith&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &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;&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;p&gt;注意这里&lt;strong&gt;没有建库、没有建表、没有定义字段类型&lt;/strong&gt;——这就是&amp;quot;无模式&amp;quot;体验。但书里马上提醒：无模式是假象，ES 在背后做了&lt;strong&gt;动态映射（dynamic mapping）&lt;/strong&gt;，第一次见 &lt;code&gt;&amp;quot;age&amp;quot;: 25&lt;/code&gt; 就推断 age 是 long。生产环境应该显式定义 mapping（第四章详解）。&lt;/p&gt;
&lt;h3 id=&#34;12-一个展示相关性的例子&#34;&gt;1.2 一个展示&amp;quot;相关性&amp;quot;的例子&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/_search
&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;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;: &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;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bool&amp;#34;&lt;/span&gt;: &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;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;must&amp;#34;&lt;/span&gt;:     &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;about&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rock climbing&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &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;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;filter&amp;#34;&lt;/span&gt;:   &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;range&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;gt&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &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;    &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;  &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;&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;p&gt;返回结果中每个文档带一个 &lt;code&gt;_score&lt;/code&gt;（相关性得分），按得分降序排列。&lt;code&gt;must&lt;/code&gt; 里的 match 参与算分，&lt;code&gt;filter&lt;/code&gt; 里的 range 只过滤不算分——&lt;strong&gt;这是全书反复出现的 query/filter 二分法&lt;/strong&gt;，第六章详解。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二分布式内核es-为什么能水平扩展&#34;&gt;二、分布式内核：ES 为什么能水平扩展&lt;/h2&gt;
&lt;p&gt;这是全书的硬核部分，也是 ES 区别于&amp;quot;单机 Lucene&amp;quot;的根本。&lt;/p&gt;
&lt;h3 id=&#34;21-集群节点与分片&#34;&gt;2.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 TB
    subgraph CLUSTER[&amp;#34;集群 cluster（一个 cluster.name）&amp;#34;]
        N1[&amp;#34;节点 Node 1&amp;lt;br/&amp;gt;（一个 ES 进程）&amp;#34;]
        N2[&amp;#34;节点 Node 2&amp;#34;]
        N3[&amp;#34;节点 Node 3&amp;#34;]
    end
    subgraph IDX[&amp;#34;索引 blogs（3 主分片 × 1 副本）&amp;#34;]
        P0[&amp;#34;主分片 P0&amp;#34;] --- R0[&amp;#34;副本分片 R0&amp;#34;]
        P1[&amp;#34;主分片 P1&amp;#34;] --- R1[&amp;#34;副本分片 R1&amp;#34;]
        P2[&amp;#34;主分片 P2&amp;#34;] --- R2[&amp;#34;副本分片 R2&amp;#34;]
    end
    N1 -.承载.- P0
    N1 -.承载.- R1
    N2 -.承载.- P1
    N2 -.承载.- R2
    N3 -.承载.- P2
    N3 -.承载.- R0
    style CLUSTER fill:#e8f0fe&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分片（shard）是 ES 分布式能力的最小单元&lt;/strong&gt;：一个分片就是一个完整的 Lucene 实例。索引只是&amp;quot;一组分片的逻辑命名空间&amp;quot;。&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;code&gt;green&lt;/code&gt;（所有主副本都就位）/ &lt;code&gt;yellow&lt;/code&gt;（主分片齐了，副本缺）/ &lt;code&gt;red&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /_cluster/health   &lt;span style=&#34;color:#75715e&#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;22-扩容为什么是免费的&#34;&gt;2.2 扩容为什么是&amp;quot;免费&amp;quot;的&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 三节点[&amp;#34;3 节点&amp;#34;]
        A1[&amp;#34;P0 R1&amp;#34;] 
        B1[&amp;#34;P1 R2&amp;#34;]
        C1[&amp;#34;P2 R0&amp;#34;]
    end
    subgraph 加一节点[&amp;#34;加入第 4 节点后：自动再平衡&amp;#34;]
        A2[&amp;#34;P0&amp;#34;]
        B2[&amp;#34;P1 R0&amp;#34;]
        C2[&amp;#34;P2&amp;#34;]
        D2[&amp;#34;R1 R2&amp;#34;]
    end
    三节点 --&amp;gt;|&amp;#34;分片自动迁移&amp;lt;br/&amp;gt;无需停机&amp;#34;| 加一节点&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;分片是 Lucene 实例，搬分片 = 搬一个独立的小搜索引擎。ES 自动把分片挪到新节点，流量自动均摊。&lt;strong&gt;垂直扩容（换大机器）之外，ES 天生支持水平扩容（加机器）&lt;/strong&gt;——这是书里&amp;quot;scale up vs scale out&amp;quot;一节的核心论点。&lt;/p&gt;
&lt;h3 id=&#34;23-文档是怎么找到家的路由&#34;&gt;2.3 文档是怎么找到家的：路由&lt;/h3&gt;
&lt;p&gt;写入/读取一个文档时，ES 用它定位分片：&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&gt;shard = hash(routing) % number_of_primary_shards&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;routing&lt;/code&gt; 默认是文档的 &lt;code&gt;_id&lt;/code&gt;，也可以自定义（比如按用户 ID 路由，让同一用户的数据落在同一分片）。&lt;strong&gt;这解释了主分片数为什么不能改&lt;/strong&gt;：改了模数，所有已有文档的哈希结果全部失效。&lt;/p&gt;
&lt;h3 id=&#34;24-写入与读取的分布式流程&#34;&gt;2.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;sequenceDiagram
    participant C as 客户端
    participant N as 协调节点&amp;lt;br/&amp;gt;（收到请求的任意节点）
    participant P as 主分片所在节点
    participant R1 as 副本节点1
    participant R2 as 副本节点2

    Note over C,N: 写入流程
    C-&amp;gt;&amp;gt;N: PUT /blogs/post/123
    N-&amp;gt;&amp;gt;N: 计算路由 hash(_id)%3 → P1
    N-&amp;gt;&amp;gt;P: 转发写入
    P-&amp;gt;&amp;gt;P: 本地写 Lucene + translog
    par 并行同步副本
        P-&amp;gt;&amp;gt;R1: 复制写入
        P-&amp;gt;&amp;gt;R2: 复制写入
    end
    R1--&amp;gt;&amp;gt;P: ack
    R2--&amp;gt;&amp;gt;P: ack
    P--&amp;gt;&amp;gt;N: 成功
    N--&amp;gt;&amp;gt;C: 201 + _version

    Note over C,N: 读取流程
    C-&amp;gt;&amp;gt;N: GET /blogs/post/123
    N-&amp;gt;&amp;gt;N: 路由计算 → 分片 P1
    N-&amp;gt;&amp;gt;R2: 主/副本轮询负载均衡
    R2--&amp;gt;&amp;gt;N: 文档
    N--&amp;gt;&amp;gt;C: 200 + _source&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;/li&gt;
&lt;li&gt;&lt;strong&gt;写走主分片，读可以走主或副本&lt;/strong&gt;——副本不仅容灾，还分摊读流量。加副本 = 加读吞吐。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三近实时的秘密refreshtranslog-与段合并&#34;&gt;三、近实时的秘密：refresh、translog 与段合并&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;文档写入后为什么默认 1 秒才能搜到？&amp;ldquo;回答这个问题需要理解 Lucene 的存储模型。&lt;/p&gt;
&lt;h3 id=&#34;31-倒排索引与不可变段&#34;&gt;3.1 倒排索引与不可变段&lt;/h3&gt;
&lt;p&gt;ES 快的根源是&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
    subgraph 词典[&amp;#34;Term Dictionary&amp;#34;]
        T1[&amp;#34;climbing&amp;#34;]
        T2[&amp;#34;music&amp;#34;]
        T3[&amp;#34;rock&amp;#34;]
    end
    subgraph 倒排表[&amp;#34;Posting List&amp;#34;]
        L1[&amp;#34;doc 1, doc 5&amp;#34;]
        L2[&amp;#34;doc 1, doc 3&amp;#34;]
        L3[&amp;#34;doc 1&amp;#34;]
    end
    T1 --&amp;gt; L1
    T2 --&amp;gt; L2
    T3 --&amp;gt; L3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;Lucene 把倒排索引持久化为&lt;strong&gt;段（segment）&lt;/strong&gt;，段一旦写出就&lt;strong&gt;不可变（immutable）&lt;/strong&gt;。不可变性带来一串好处：不需要锁、可以进文件系统缓存、删除只需打标记。代价是：&lt;strong&gt;更新 = 删旧 + 建新&lt;/strong&gt;，新写的文档必须先攒成新段才能被搜索。&lt;/p&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;flowchart TD
    W[&amp;#34;文档写入请求&amp;#34;] --&amp;gt; MEM[&amp;#34;内存缓冲区&amp;#34;]
    W --&amp;gt; TL[&amp;#34;translog&amp;lt;br/&amp;gt;（先写 WAL，防宕机丢数据）&amp;#34;]
    MEM --&amp;gt;|&amp;#34;refresh&amp;lt;br/&amp;gt;默认每 1 秒&amp;#34;| SEG[&amp;#34;新 segment&amp;lt;br/&amp;gt;写入文件系统缓存&amp;lt;br/&amp;gt;→ 立即可被搜索&amp;#34;]
    SEG --&amp;gt;|&amp;#34;flush&amp;lt;br/&amp;gt;translog 满或定时&amp;#34;| DISK[&amp;#34;fsync 落盘&amp;lt;br/&amp;gt;清空 translog&amp;#34;]
    SEG -.-&amp;gt;|&amp;#34;后台 merge&amp;#34;| BIG[&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;refresh&lt;/strong&gt;：内存 → 新的可搜索 segment（在文件系统缓存里，不落盘）。这就是&amp;quot;近实时（NRT）&amp;ldquo;的来源——默认 1 秒延迟。批量导入时可以调大 &lt;code&gt;refresh_interval&lt;/code&gt; 换吞吐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;translog&lt;/strong&gt;：预写日志。每个写入先记 translog，节点宕机后用它重放还没 flush 的数据。这是&amp;quot;ES 不丢数据&amp;quot;的底气。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;flush&lt;/strong&gt;：真正 fsync 落盘 + 清空 translog。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;merge&lt;/strong&gt;：小段太多会拖慢搜索（每个查询都要遍历所有段），后台线程持续把小段合并成大段，并顺带物理删除被标记删除的文档。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;给初学者的记忆法&lt;/strong&gt;：refresh 管&amp;quot;搜得到&amp;rdquo;，flush 管&amp;quot;丢不了&amp;rdquo;，merge 管&amp;quot;搜得快&amp;rdquo;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四映射与分析数据进-es-之前发生了什么&#34;&gt;四、映射与分析：数据进 ES 之前发生了什么&lt;/h2&gt;
&lt;h3 id=&#34;41-mapping字段的类型决定它能怎么被查&#34;&gt;4.1 Mapping：字段的类型决定它能怎么被查&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-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;PUT&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/blogs&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;&amp;#34;mappings&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;&amp;#34;properties&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;&amp;#34;title&amp;#34;&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ik_max_word&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;&amp;#34;status&amp;#34;&lt;/span&gt;:   { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;keyword&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;&amp;#34;views&amp;#34;&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;integer&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;&amp;#34;created&amp;#34;&lt;/span&gt;:  { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;date&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&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;strong&gt;&lt;code&gt;text&lt;/code&gt;&lt;/strong&gt;：全文字段，会被&lt;strong&gt;分词&lt;/strong&gt;后存入倒排索引，用于 &lt;code&gt;match&lt;/code&gt; 全文检索；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;keyword&lt;/code&gt;&lt;/strong&gt;：精确值字段，&lt;strong&gt;原样&lt;/strong&gt;入索引，用于过滤、排序、聚合。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书中金句：&amp;quot;&lt;strong&gt;exact value 和 full text 的区别，是 SQL 世界和搜索世界的分界&lt;/strong&gt;&amp;quot;。用 keyword 字段做 match 全文搜索（或反过来）是新手最常见的错误。&lt;/p&gt;
&lt;p&gt;⚠️ 版本提示：原书写作时还是 &lt;code&gt;string&lt;/code&gt; + &lt;code&gt;analyzed/not_analyzed&lt;/code&gt; 的写法，ES 5.x 起拆分为 &lt;code&gt;text&lt;/code&gt;/&lt;code&gt;keyword&lt;/code&gt;，思想完全一致。&lt;/p&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;graph LR
    A[&amp;#34;原始文本&amp;lt;br/&amp;gt;&amp;#39;The Quick Brown Fox!&amp;#39;&amp;#34;] --&amp;gt; CF[&amp;#34;Character Filter&amp;lt;br/&amp;gt;字符过滤&amp;lt;br/&amp;gt;（去 HTML、字符映射）&amp;#34;]
    CF --&amp;gt; TK[&amp;#34;Tokenizer&amp;lt;br/&amp;gt;分词器&amp;lt;br/&amp;gt;&amp;#39;The&amp;#39;,&amp;#39;Quick&amp;#39;,&amp;#39;Brown&amp;#39;,&amp;#39;Fox&amp;#39;&amp;#34;]
    TK --&amp;gt; TF[&amp;#34;Token Filter&amp;lt;br/&amp;gt;词项过滤器&amp;lt;br/&amp;gt;lowercase → 停用词 → 词干提取&amp;#34;]
    TF --&amp;gt; R[&amp;#34;最终词项&amp;lt;br/&amp;gt;&amp;#39;quick&amp;#39;,&amp;#39;brown&amp;#39;,&amp;#39;fox&amp;#39;&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Character Filter&lt;/strong&gt;：分词前做字符级预处理；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tokenizer&lt;/strong&gt;：把文本切成词项（一个分析器&lt;strong&gt;只能有一个&lt;/strong&gt; tokenizer）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token Filter&lt;/strong&gt;：对词项再加工，可串联多个（lowercase、停用词、同义词、词干提取）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内置的 &lt;code&gt;standard&lt;/code&gt; 分析器 = standard tokenizer + lowercase + stop。可以用 &lt;code&gt;_analyze&lt;/code&gt; API 眼见为实：&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;POST /_analyze
&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 style=&#34;color:#e6db74&#34;&gt;&amp;#34;analyzer&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;The Quick Brown Fox!&amp;#34;&lt;/span&gt; &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;&lt;span style=&#34;color:#75715e&#34;&gt;# → the, quick, brown, fox（&amp;#39;The&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;strong&gt;中文场景&lt;/strong&gt;：标准分词器会把中文逐字切开，效果很差。社区方案是 IK 分词器（&lt;code&gt;ik_max_word&lt;/code&gt; 细粒度建索引、&lt;code&gt;ik_smart&lt;/code&gt; 粗粒度搜索），这是中文 ES 项目的标配。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;索引时与搜索时的分析必须配套&lt;/strong&gt;：文档里的 &lt;code&gt;&amp;quot;Fox&amp;quot;&lt;/code&gt; 入库时被分析成 &lt;code&gt;fox&lt;/code&gt;；搜索 &lt;code&gt;&amp;quot;FOX&amp;quot;&lt;/code&gt; 时查询词也会被同一个分析器处理成 &lt;code&gt;fox&lt;/code&gt; 去匹配——&lt;strong&gt;这就是为什么 ES 搜索&amp;quot;不区分大小写&amp;quot;&lt;/strong&gt;：不是查询忽略了大小写，而是两边都被 lowercased 了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五搜索query-dsl-的全景&#34;&gt;五、搜索：Query DSL 的全景&lt;/h2&gt;
&lt;h3 id=&#34;51-query-与-filter全书最重要的性能概念&#34;&gt;5.1 Query 与 Filter：全书最重要的性能概念&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
    Q[&amp;#34;Query 上下文&amp;lt;br/&amp;gt;&amp;#39;这个文档和查询有多相关？&amp;#39;&amp;#34;]
    F[&amp;#34;Filter 上下文&amp;lt;br/&amp;gt;&amp;#39;这个文档是否匹配？&amp;#39;&amp;#34;]
    Q --&amp;gt; A1[&amp;#34;计算 _score&amp;lt;br/&amp;gt;结果按相关性排序&amp;#34;]
    F --&amp;gt; A2[&amp;#34;只答 yes/no&amp;lt;br/&amp;gt;不算分、结果可被缓存&amp;#34;]
    style Q fill:#fef3e8
    style F fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;经验法则：能用 filter 就不用 query&lt;/strong&gt;。过滤条件（状态、时间范围、ID 列表）放 filter，既省算分开销又能命中 bitset 缓存。&lt;/p&gt;
&lt;h3 id=&#34;52-常用查询速查&#34;&gt;5.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;code&gt;match&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全文搜索（会分词）&lt;/td&gt;
					&lt;td&gt;标题含&amp;quot;登山 装备&amp;quot;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;term&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;精确匹配（不分词）&lt;/td&gt;
					&lt;td&gt;status 等于 &amp;ldquo;published&amp;rdquo;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;terms&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多值精确匹配&lt;/td&gt;
					&lt;td&gt;标签 in [&amp;ldquo;科幻&amp;rdquo;,&amp;ldquo;动作&amp;rdquo;]&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;range&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;范围&lt;/td&gt;
					&lt;td&gt;age ≥ 30&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;exists&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;字段非空&lt;/td&gt;
					&lt;td&gt;必须有 poster 字段&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;multi_match&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;match_phrase&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;短语匹配（词序相邻）&lt;/td&gt;
					&lt;td&gt;搜 &amp;ldquo;rock climbing&amp;rdquo; 不拆词&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;bool&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;53-bool-查询乐高积木&#34;&gt;5.3 bool 查询：乐高积木&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-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GET&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/megacorp/employee/_search&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;&amp;#34;query&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;&amp;#34;bool&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;&amp;#34;must&amp;#34;&lt;/span&gt;:     [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;about&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rock climbing&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;&amp;#34;should&amp;#34;&lt;/span&gt;:   [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;interests&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;music&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;&amp;#34;must_not&amp;#34;&lt;/span&gt;: [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;term&amp;#34;&lt;/span&gt;:  { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;last_name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;smith&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;&amp;#34;filter&amp;#34;&lt;/span&gt;:   [ { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;range&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;gte&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30&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;ul&gt;
&lt;li&gt;&lt;code&gt;must&lt;/code&gt;：必须匹配，参与算分；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;should&lt;/code&gt;：最好匹配，匹配上加分（没有 must/filter 时至少命中一个）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;must_not&lt;/code&gt;：必须不匹配，不参与算分（在 filter 上下文执行）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt;：必须匹配，不参与算分。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;所有复杂查询都是这四块积木的嵌套&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;54-相关性算分_score-是怎么来的&#34;&gt;5.4 相关性算分：_score 是怎么来的&lt;/h3&gt;
&lt;p&gt;书里讲了经典的 &lt;strong&gt;TF/IDF&lt;/strong&gt; 模型（新版本默认换成 BM25，思想相同，多了文档长度归一化）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;词频 TF&lt;/strong&gt;：词在本文档出现越多，越相关；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逆文档频率 IDF&lt;/strong&gt;：词在所有文档中越罕见，区分度越高（&amp;ldquo;的&amp;quot;不值钱，&amp;ldquo;攀岩&amp;quot;值钱）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字段长度&lt;/strong&gt;：字段越短，匹配越&amp;quot;精悍&amp;rdquo;，得分越高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对结果有疑问时用 &lt;code&gt;_explain&lt;/code&gt; API 看算分过程——这是调搜索体验的第一件工具。&lt;/p&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /_search?from&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;10000&amp;amp;size&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#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;scatter-gather&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;sequenceDiagram
    participant C as 客户端
    participant CO as 协调节点
    participant S0 as 分片0
    participant S1 as 分片1
    participant S2 as 分片2
    C-&amp;gt;&amp;gt;CO: from=10000, size=10
    par scatter
        CO-&amp;gt;&amp;gt;S0: 各分片先取 Top 10010
        CO-&amp;gt;&amp;gt;S1: 各分片先取 Top 10010
        CO-&amp;gt;&amp;gt;S2: 各分片先取 Top 10010
    end
    S0--&amp;gt;&amp;gt;CO: 10010 条
    S1--&amp;gt;&amp;gt;CO: 10010 条
    S2--&amp;gt;&amp;gt;CO: 10010 条
    CO-&amp;gt;&amp;gt;CO: 归并 30030 条，取 10001~10010
    CO--&amp;gt;&amp;gt;C: 10 条&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;取第 1001 页，要在每个分片各取 10010 条再归并。&lt;strong&gt;深度翻页成本线性爆炸&lt;/strong&gt;，书里的忠告是&amp;quot;不要在搜索场景做深翻页&amp;rdquo;；确实需要遍历全量时，用 &lt;code&gt;scroll&lt;/code&gt;（游标快照）或新版本的 &lt;code&gt;search_after&lt;/code&gt;。&lt;/p&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;ul&gt;
&lt;li&gt;&lt;strong&gt;语言分析器&lt;/strong&gt;：英语词干提取（&lt;code&gt;foxes&lt;/code&gt;→&lt;code&gt;fox&lt;/code&gt;）、德语的复合词拆分、中文/日文的无空格分词。每种语言的正确处理方式完全不同——&lt;strong&gt;选对语言分析器比调任何参数都重要&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拼写纠错（did-you-mean）&lt;/strong&gt;：&lt;code&gt;term suggester&lt;/code&gt; / &lt;code&gt;phrase suggester&lt;/code&gt;，&amp;ldquo;你搜的是不是 elasticsearch？&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入即搜（search-as-you-type）&lt;/strong&gt;：用 &lt;code&gt;edge ngram&lt;/code&gt;（前缀 ngram）在索引时把 &lt;code&gt;&amp;quot;elasticsearch&amp;quot;&lt;/code&gt; 切成 &lt;code&gt;e, el, ela, ...&lt;/code&gt;，用户每敲一个字母就能命中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;近似匹配&lt;/strong&gt;：&lt;code&gt;match&lt;/code&gt; 的 &lt;code&gt;fuzziness&lt;/code&gt; 参数基于编辑距离，容忍拼写错误（&lt;code&gt;searching&lt;/code&gt; 打成 &lt;code&gt;serching&lt;/code&gt; 也能命中）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;近似短语&lt;/strong&gt;：&lt;code&gt;match_phrase&lt;/code&gt; 的 &lt;code&gt;slop&lt;/code&gt; 参数允许词与词之间隔着几个词仍算匹配。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一部分的共同心法：&lt;strong&gt;召回率与准确率的权衡&lt;/strong&gt;。ngram 开得越大、fuzziness 越高，召回越好但噪音越多；工程上常用&amp;quot;多层子字段&amp;quot;（精确字段 + 分词字段 + ngram 字段）分层兜底。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七聚合从搜索到分析&#34;&gt;七、聚合：从搜索到分析&lt;/h2&gt;
&lt;p&gt;搜索回答&amp;quot;哪些文档匹配&amp;quot;，&lt;strong&gt;聚合（aggregations）&lt;/strong&gt; 回答&amp;quot;数据整体长什么样&amp;quot;。聚合分两大类，可以无限嵌套：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;桶（Bucket）聚合&lt;/strong&gt;：把文档分组——&lt;code&gt;terms&lt;/code&gt;（按字段值）、&lt;code&gt;date_histogram&lt;/code&gt;（按时间）、&lt;code&gt;range&lt;/code&gt;（按区间）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;度量（Metric）聚合&lt;/strong&gt;：对组内文档算统计——&lt;code&gt;avg/sum/min/max/stats/cardinality&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;71-实例员工数据统计&#34;&gt;7.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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET /megacorp/employee/_search
&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;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;size&amp;#34;&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:#e6db74&#34;&gt;&amp;#34;aggs&amp;#34;&lt;/span&gt;: &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;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;by_interest&amp;#34;&lt;/span&gt;: &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 style=&#34;color:#e6db74&#34;&gt;&amp;#34;terms&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;field&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;interests&amp;#34;&lt;/span&gt; &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;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;aggs&amp;#34;&lt;/span&gt;: &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;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;avg_age&amp;#34;&lt;/span&gt;: &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 style=&#34;color:#e6db74&#34;&gt;&amp;#34;avg&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;field&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt; &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;        &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;      &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;    &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;  &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;&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;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;|&amp;#34;terms 桶 by_interest&amp;#34;| B1[&amp;#34;sports 组&amp;#34;]
    A --&amp;gt; B2[&amp;#34;music 组&amp;#34;]
    A --&amp;gt; B3[&amp;#34;forestry 组&amp;#34;]
    B1 --&amp;gt; C1[&amp;#34;avg_age = 28.5&amp;#34;]
    B2 --&amp;gt; C2[&amp;#34;avg_age = 31.2&amp;#34;]
    B3 --&amp;gt; C3[&amp;#34;avg_age = 35.0&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;72-要点&#34;&gt;7.2 要点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;size: 0&lt;/code&gt;&lt;/strong&gt;：只要聚合不要文档，省掉无用传输；&lt;/li&gt;
&lt;li&gt;聚合只能跑在 &lt;code&gt;keyword&lt;/code&gt;/数值/日期等&lt;strong&gt;精确值字段&lt;/strong&gt;上——在 &lt;code&gt;text&lt;/code&gt; 字段上聚合会失败或慢到崩溃（需要 fielddata，默认关闭，这是个性能地雷）；&lt;/li&gt;
&lt;li&gt;高基数 &lt;code&gt;terms&lt;/code&gt; 聚合（如按 userId 分桶）吃内存，新版有 circuit breaker 兜底；&lt;/li&gt;
&lt;li&gt;近似聚合是 ES 的特色：&lt;code&gt;cardinality&lt;/code&gt;（去重计数）和 &lt;code&gt;percentiles&lt;/code&gt;（百分位）都是&lt;strong&gt;近似算法&lt;/strong&gt;，用微小误差换海量数据下的可用性。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八关系建模es-不是数据库&#34;&gt;八、关系建模：ES 不是数据库&lt;/h2&gt;
&lt;p&gt;书里最&amp;quot;劝退&amp;quot;也最重要的一章：ES 没有 join。建模关系的四种方式按推荐顺序：&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;strong&gt;反规范化（Denormalize）&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;应用层 join&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;查两次，在代码里拼接&lt;/td&gt;
					&lt;td&gt;多一次网络往返&lt;/td&gt;
					&lt;td&gt;一对多且&amp;quot;多&amp;quot;侧很大&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;nested 对象&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;parent-child（join 字段）&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;p&gt;&lt;strong&gt;默认答案永远是先反规范化&lt;/strong&gt;。搜索系统的哲学是&amp;quot;用写入时的冗余，换查询时的简单&amp;quot;——磁盘便宜，join 昂贵。&lt;/p&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;：master 节点管集群元数据（建议 3 个专职 master 防脑裂）、data 节点存数据、协调节点转发请求。大集群应该角色分离。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;脑裂与 minimum_master_nodes&lt;/strong&gt;：网络分区时两个子集群各自选主 = 数据写烂。经典配置是 &lt;code&gt;master 节点数 / 2 + 1&lt;/code&gt;（新版本的集群选举机制已自动化）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分片规划&lt;/strong&gt;：分片不是越多越好——每个分片都有固定开销（文件句柄、内存、段管理）。单分片控制在几十 GB 量级、节点堆内存 32GB 以内（压指针上限）、堆内存不超过物理内存一半（另一半留给文件系统缓存）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控集群健康&lt;/strong&gt;：&lt;code&gt;green/yellow/red&lt;/code&gt; + &lt;code&gt;hot_threads&lt;/code&gt; + &lt;code&gt;cat&lt;/code&gt; API 家族。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;升级与备份&lt;/strong&gt;：滚动升级逐个节点来；快照（snapshot）到 S3/HDFS 做备份。&lt;/li&gt;
&lt;/ol&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
    CORE[&amp;#34;倒排索引&amp;lt;br/&amp;gt;（一切的根基）&amp;#34;]
    CORE --&amp;gt; WRITE[&amp;#34;写入链路&amp;lt;br/&amp;gt;refresh/translog/merge&amp;lt;br/&amp;gt;→ 近实时 + 不丢数据&amp;#34;]
    CORE --&amp;gt; SEARCH[&amp;#34;搜索&amp;lt;br/&amp;gt;Query/Filter + bool 组合&amp;lt;br/&amp;gt;TF-IDF 相关性算分&amp;#34;]
    CORE --&amp;gt; AGG[&amp;#34;聚合&amp;lt;br/&amp;gt;桶 × 度量嵌套&amp;#34;]
    WRITE --&amp;gt; DIST[&amp;#34;分布式&amp;lt;br/&amp;gt;分片路由 scatter-gather&amp;lt;br/&amp;gt;主副本容灾&amp;#34;]
    SEARCH --&amp;gt; LANG[&amp;#34;语言处理&amp;lt;br/&amp;gt;分析器/纠错/输入即搜&amp;#34;]
    SEARCH --&amp;gt; MODEL[&amp;#34;建模&amp;lt;br/&amp;gt;反规范化优先&amp;#34;]
    DIST --&amp;gt; OPS[&amp;#34;运维&amp;lt;br/&amp;gt;健康监控/分片规划/防脑裂&amp;#34;]
    style CORE fill:#e1f5e1&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;：不可变段决定了 NRT，分词决定了查不查得到，keyword/text 之分决定了能不能聚合；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;query 算分、filter 缓存&lt;/strong&gt;——写任何查询前先想这一条；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分片是分布式的原子&lt;/strong&gt;：路由公式决定了主分片数不能改，副本决定了读吞吐和容灾。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考资料：Clinton Gormley, Zachary Tong. 《Elasticsearch: The Definitive Guide》. O&amp;rsquo;Reilly, 2015. 中文版：&lt;a href=&#34;https://github.com/looly/elasticsearch-definitive-guide-cn&#34;&gt;elasticsearch-definitive-guide-cn&lt;/a&gt;（looly 等译）。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
