「Java高级技能提升系列」第10篇(全20篇)· 面向2026年中高级Java岗面试 · 专栏持续更新,欢迎关注
消息队列实战:RocketMQ/Kafka选型与"不丢不重有序"三连解
面试靶题:怎么保证消息不丢失、不重复、顺序消费?
【场景引入】
某跨境电商二面面试官突然发问:"我们的支付系统在双11期间出现了支付成功但积分未到账的情况,假设你来负责排查,你会从哪些角度入手?"
这个问题看似简单,实则直击消息队列的三大核心能力:可靠性、幂等性、顺序性。先给读者一个自查清单:
- 能答到第一层:知道MQ有三种模式,能说出基本概念
- 能答到第二层:理解消息流转链路,知道每个环节的故障点
- 能答到第三层:能给出具体的技术方案和参数配置
- 能答到第四层:能结合压测数据说明方案的优劣和适用场景
残酷的现实:90%的候选人只能答到第一层,而真正能讲清楚"不丢不重有序"三连的候选人不足10%。面试官想看的不是标准答案,而是你是否有生产级问题的实战经验。
【原理图解】
消息可靠性全链路保障
结论:消息"不丢失"需要构建生产确认-持久化存储-消费确认的完整闭环。
#mermaid-svg-N7UaEdkdThNhQtMK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-N7UaEdkdThNhQtMK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-N7UaEdkdThNhQtMK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-N7UaEdkdThNhQtMK .error-icon{fill:#552222;}#mermaid-svg-N7UaEdkdThNhQtMK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-N7UaEdkdThNhQtMK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-N7UaEdkdThNhQtMK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-N7UaEdkdThNhQtMK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-N7UaEdkdThNhQtMK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-N7UaEdkdThNhQtMK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-N7UaEdkdThNhQtMK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-N7UaEdkdThNhQtMK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-N7UaEdkdThNhQtMK .marker.cross{stroke:#333333;}#mermaid-svg-N7UaEdkdThNhQtMK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-N7UaEdkdThNhQtMK p{margin:0;}#mermaid-svg-N7UaEdkdThNhQtMK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-N7UaEdkdThNhQtMK .cluster-label text{fill:#333;}#mermaid-svg-N7UaEdkdThNhQtMK .cluster-label span{color:#333;}#mermaid-svg-N7UaEdkdThNhQtMK .cluster-label span p{background-color:transparent;}#mermaid-svg-N7UaEdkdThNhQtMK .label text,#mermaid-svg-N7UaEdkdThNhQtMK span{fill:#333;color:#333;}#mermaid-svg-N7UaEdkdThNhQtMK .node rect,#mermaid-svg-N7UaEdkdThNhQtMK .node circle,#mermaid-svg-N7UaEdkdThNhQtMK .node ellipse,#mermaid-svg-N7UaEdkdThNhQtMK .node polygon,#mermaid-svg-N7UaEdkdThNhQtMK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-N7UaEdkdThNhQtMK .rough-node .label text,#mermaid-svg-N7UaEdkdThNhQtMK .node .label text,#mermaid-svg-N7UaEdkdThNhQtMK .image-shape .label,#mermaid-svg-N7UaEdkdThNhQtMK .icon-shape .label{text-anchor:middle;}#mermaid-svg-N7UaEdkdThNhQtMK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-N7UaEdkdThNhQtMK .rough-node .label,#mermaid-svg-N7UaEdkdThNhQtMK .node .label,#mermaid-svg-N7UaEdkdThNhQtMK .image-shape .label,#mermaid-svg-N7UaEdkdThNhQtMK .icon-shape .label{text-align:center;}#mermaid-svg-N7UaEdkdThNhQtMK .node.clickable{cursor:pointer;}#mermaid-svg-N7UaEdkdThNhQtMK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-N7UaEdkdThNhQtMK .arrowheadPath{fill:#333333;}#mermaid-svg-N7UaEdkdThNhQtMK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-N7UaEdkdThNhQtMK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-N7UaEdkdThNhQtMK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N7UaEdkdThNhQtMK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-N7UaEdkdThNhQtMK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N7UaEdkdThNhQtMK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-N7UaEdkdThNhQtMK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-N7UaEdkdThNhQtMK .cluster text{fill:#333;}#mermaid-svg-N7UaEdkdThNhQtMK .cluster span{color:#333;}#mermaid-svg-N7UaEdkdThNhQtMK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-N7UaEdkdThNhQtMK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-N7UaEdkdThNhQtMK rect.text{fill:none;stroke-width:0;}#mermaid-svg-N7UaEdkdThNhQtMK .icon-shape,#mermaid-svg-N7UaEdkdThNhQtMK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N7UaEdkdThNhQtMK .icon-shape p,#mermaid-svg-N7UaEdkdThNhQtMK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-N7UaEdkdThNhQtMK .icon-shape .label rect,#mermaid-svg-N7UaEdkdThNhQtMK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N7UaEdkdThNhQtMK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-N7UaEdkdThNhQtMK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-N7UaEdkdThNhQtMK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发送消息
同步复制
刷盘
消费者拉取
手动ACK
删除消息
生产者
Broker Leader
Broker Follower
磁盘持久化
消费者
确认消费完成
消息清理
生产者confirm机制
消费者重试机制
死信队列
人工介入
很多团队只关注Broker的持久化,却忽略了生产端的确认机制和消费端的异常处理。关键认知:消息不丢失是系统工程,不是单一环节能解决的问题。
RocketMQ事务消息机制
结论:事务消息通过两阶段提交+本地事务绑定,实现分布式事务的最终一致性。
Local DB Broker Producer Local DB Broker Producer #mermaid-svg-fl229lz20TSXmppe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fl229lz20TSXmppe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fl229lz20TSXmppe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fl229lz20TSXmppe .error-icon{fill:#552222;}#mermaid-svg-fl229lz20TSXmppe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fl229lz20TSXmppe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fl229lz20TSXmppe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fl229lz20TSXmppe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fl229lz20TSXmppe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fl229lz20TSXmppe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fl229lz20TSXmppe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fl229lz20TSXmppe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fl229lz20TSXmppe .marker.cross{stroke:#333333;}#mermaid-svg-fl229lz20TSXmppe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fl229lz20TSXmppe p{margin:0;}#mermaid-svg-fl229lz20TSXmppe .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fl229lz20TSXmppe text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-fl229lz20TSXmppe .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fl229lz20TSXmppe .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-fl229lz20TSXmppe .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-fl229lz20TSXmppe .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-fl229lz20TSXmppe #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-fl229lz20TSXmppe .sequenceNumber{fill:white;}#mermaid-svg-fl229lz20TSXmppe #sequencenumber{fill:#333;}#mermaid-svg-fl229lz20TSXmppe #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-fl229lz20TSXmppe .messageText{fill:#333;stroke:none;}#mermaid-svg-fl229lz20TSXmppe .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fl229lz20TSXmppe .labelText,#mermaid-svg-fl229lz20TSXmppe .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-fl229lz20TSXmppe .loopText,#mermaid-svg-fl229lz20TSXmppe .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-fl229lz20TSXmppe .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fl229lz20TSXmppe .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-fl229lz20TSXmppe .noteText,#mermaid-svg-fl229lz20TSXmppe .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-fl229lz20TSXmppe .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fl229lz20TSXmppe .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fl229lz20TSXmppe .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fl229lz20TSXmppe .actorPopupMenu{position:absolute;}#mermaid-svg-fl229lz20TSXmppe .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-fl229lz20TSXmppe .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fl229lz20TSXmppe .actor-man circle,#mermaid-svg-fl229lz20TSXmppe line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-fl229lz20TSXmppe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 如果网络异常,Broker会主动回查Producer 事务消息适用于订单、支付等强一致性场景 发送半消息(Half Message) 返回发送成功 执行本地事务 本地事务结果 提交二次确认(Commit/Rollback) 标记消息为可投递/删除 定时回查(约15秒间隔)
RocketMQ的事务消息机制是它与Kafka的核心差异之一。技术要点:面试官想听的不是背概念,而是你能说出"半消息状态"、"回查机制"、"约15秒间隔"这些具体参数。
顺序消息分区策略
结论:顺序消费的核心是局部有序而非全局有序,通过业务Key的哈希路由实现。
#mermaid-svg-Bvj6BMyHzrvqS31c{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bvj6BMyHzrvqS31c .error-icon{fill:#552222;}#mermaid-svg-Bvj6BMyHzrvqS31c .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bvj6BMyHzrvqS31c .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bvj6BMyHzrvqS31c .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bvj6BMyHzrvqS31c .marker.cross{stroke:#333333;}#mermaid-svg-Bvj6BMyHzrvqS31c svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bvj6BMyHzrvqS31c p{margin:0;}#mermaid-svg-Bvj6BMyHzrvqS31c .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bvj6BMyHzrvqS31c .cluster-label text{fill:#333;}#mermaid-svg-Bvj6BMyHzrvqS31c .cluster-label span{color:#333;}#mermaid-svg-Bvj6BMyHzrvqS31c .cluster-label span p{background-color:transparent;}#mermaid-svg-Bvj6BMyHzrvqS31c .label text,#mermaid-svg-Bvj6BMyHzrvqS31c span{fill:#333;color:#333;}#mermaid-svg-Bvj6BMyHzrvqS31c .node rect,#mermaid-svg-Bvj6BMyHzrvqS31c .node circle,#mermaid-svg-Bvj6BMyHzrvqS31c .node ellipse,#mermaid-svg-Bvj6BMyHzrvqS31c .node polygon,#mermaid-svg-Bvj6BMyHzrvqS31c .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bvj6BMyHzrvqS31c .rough-node .label text,#mermaid-svg-Bvj6BMyHzrvqS31c .node .label text,#mermaid-svg-Bvj6BMyHzrvqS31c .image-shape .label,#mermaid-svg-Bvj6BMyHzrvqS31c .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bvj6BMyHzrvqS31c .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bvj6BMyHzrvqS31c .rough-node .label,#mermaid-svg-Bvj6BMyHzrvqS31c .node .label,#mermaid-svg-Bvj6BMyHzrvqS31c .image-shape .label,#mermaid-svg-Bvj6BMyHzrvqS31c .icon-shape .label{text-align:center;}#mermaid-svg-Bvj6BMyHzrvqS31c .node.clickable{cursor:pointer;}#mermaid-svg-Bvj6BMyHzrvqS31c .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bvj6BMyHzrvqS31c .arrowheadPath{fill:#333333;}#mermaid-svg-Bvj6BMyHzrvqS31c .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bvj6BMyHzrvqS31c .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bvj6BMyHzrvqS31c .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bvj6BMyHzrvqS31c .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bvj6BMyHzrvqS31c .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bvj6BMyHzrvqS31c .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bvj6BMyHzrvqS31c .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bvj6BMyHzrvqS31c .cluster text{fill:#333;}#mermaid-svg-Bvj6BMyHzrvqS31c .cluster span{color:#333;}#mermaid-svg-Bvj6BMyHzrvqS31c div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bvj6BMyHzrvqS31c .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bvj6BMyHzrvqS31c rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bvj6BMyHzrvqS31c .icon-shape,#mermaid-svg-Bvj6BMyHzrvqS31c .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bvj6BMyHzrvqS31c .icon-shape p,#mermaid-svg-Bvj6BMyHzrvqS31c .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bvj6BMyHzrvqS31c .icon-shape .label rect,#mermaid-svg-Bvj6BMyHzrvqS31c .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bvj6BMyHzrvqS31c .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bvj6BMyHzrvqS31c .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bvj6BMyHzrvqS31c :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 订单ID=1001
订单ID=1002
订单ID=1001
订单ID=1003
FIFO
FIFO
FIFO
desc
相同订单ID的消息必然进入同一队列
不同队列可并行消费,提高吞吐
生产者
队列1
队列2
队列3
消费者线程1
消费者线程2
消费者线程3
常见误区 :很多团队为了追求绝对顺序而使用单队列,导致吞吐量严重不足。正确做法:通过哈希取模算法将相同业务Key的消息分配到同一队列,实现同一业务内的消息顺序性。
消息幂等性实现方案
结论:幂等性需要多层次的防护机制,从快速判断到精确校验。
#mermaid-svg-xOVGXM1tK4xNpr4A{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xOVGXM1tK4xNpr4A .error-icon{fill:#552222;}#mermaid-svg-xOVGXM1tK4xNpr4A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xOVGXM1tK4xNpr4A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xOVGXM1tK4xNpr4A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xOVGXM1tK4xNpr4A .marker.cross{stroke:#333333;}#mermaid-svg-xOVGXM1tK4xNpr4A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xOVGXM1tK4xNpr4A p{margin:0;}#mermaid-svg-xOVGXM1tK4xNpr4A .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xOVGXM1tK4xNpr4A .cluster-label text{fill:#333;}#mermaid-svg-xOVGXM1tK4xNpr4A .cluster-label span{color:#333;}#mermaid-svg-xOVGXM1tK4xNpr4A .cluster-label span p{background-color:transparent;}#mermaid-svg-xOVGXM1tK4xNpr4A .label text,#mermaid-svg-xOVGXM1tK4xNpr4A span{fill:#333;color:#333;}#mermaid-svg-xOVGXM1tK4xNpr4A .node rect,#mermaid-svg-xOVGXM1tK4xNpr4A .node circle,#mermaid-svg-xOVGXM1tK4xNpr4A .node ellipse,#mermaid-svg-xOVGXM1tK4xNpr4A .node polygon,#mermaid-svg-xOVGXM1tK4xNpr4A .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xOVGXM1tK4xNpr4A .rough-node .label text,#mermaid-svg-xOVGXM1tK4xNpr4A .node .label text,#mermaid-svg-xOVGXM1tK4xNpr4A .image-shape .label,#mermaid-svg-xOVGXM1tK4xNpr4A .icon-shape .label{text-anchor:middle;}#mermaid-svg-xOVGXM1tK4xNpr4A .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xOVGXM1tK4xNpr4A .rough-node .label,#mermaid-svg-xOVGXM1tK4xNpr4A .node .label,#mermaid-svg-xOVGXM1tK4xNpr4A .image-shape .label,#mermaid-svg-xOVGXM1tK4xNpr4A .icon-shape .label{text-align:center;}#mermaid-svg-xOVGXM1tK4xNpr4A .node.clickable{cursor:pointer;}#mermaid-svg-xOVGXM1tK4xNpr4A .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xOVGXM1tK4xNpr4A .arrowheadPath{fill:#333333;}#mermaid-svg-xOVGXM1tK4xNpr4A .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xOVGXM1tK4xNpr4A .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xOVGXM1tK4xNpr4A .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xOVGXM1tK4xNpr4A .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xOVGXM1tK4xNpr4A .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xOVGXM1tK4xNpr4A .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xOVGXM1tK4xNpr4A .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xOVGXM1tK4xNpr4A .cluster text{fill:#333;}#mermaid-svg-xOVGXM1tK4xNpr4A .cluster span{color:#333;}#mermaid-svg-xOVGXM1tK4xNpr4A div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xOVGXM1tK4xNpr4A .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xOVGXM1tK4xNpr4A rect.text{fill:none;stroke-width:0;}#mermaid-svg-xOVGXM1tK4xNpr4A .icon-shape,#mermaid-svg-xOVGXM1tK4xNpr4A .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xOVGXM1tK4xNpr4A .icon-shape p,#mermaid-svg-xOVGXM1tK4xNpr4A .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xOVGXM1tK4xNpr4A .icon-shape .label rect,#mermaid-svg-xOVGXM1tK4xNpr4A .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xOVGXM1tK4xNpr4A .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xOVGXM1tK4xNpr4A .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xOVGXM1tK4xNpr4A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 消息ID去重
数据库唯一索引
布隆过滤器
存在
不存在
冲突
无冲突
可能存在
一定不存在
desc
幂等性需要多层次的防护机制
布隆过滤器用于快速过滤,Redis用于缓存,数据库用于最终校验
消息到达
Redis缓存
MySQL
快速判断
直接返回成功
业务处理+记录
幂等处理
正常处理
走数据库校验
直接拒绝
由于网络抖动、消费者重启等原因,消息可能会被重复投递。性能考量:很多团队只使用单一方案,在高并发场景下会出现性能问题或数据不一致。
重试-死信流转机制
结论:消息重试不是无限次的,而是有明确的重试次数和间隔策略。
#mermaid-svg-u4ZH3lFwNUu08QQB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-u4ZH3lFwNUu08QQB .error-icon{fill:#552222;}#mermaid-svg-u4ZH3lFwNUu08QQB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-u4ZH3lFwNUu08QQB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-u4ZH3lFwNUu08QQB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-u4ZH3lFwNUu08QQB .marker.cross{stroke:#333333;}#mermaid-svg-u4ZH3lFwNUu08QQB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-u4ZH3lFwNUu08QQB p{margin:0;}#mermaid-svg-u4ZH3lFwNUu08QQB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-u4ZH3lFwNUu08QQB .cluster-label text{fill:#333;}#mermaid-svg-u4ZH3lFwNUu08QQB .cluster-label span{color:#333;}#mermaid-svg-u4ZH3lFwNUu08QQB .cluster-label span p{background-color:transparent;}#mermaid-svg-u4ZH3lFwNUu08QQB .label text,#mermaid-svg-u4ZH3lFwNUu08QQB span{fill:#333;color:#333;}#mermaid-svg-u4ZH3lFwNUu08QQB .node rect,#mermaid-svg-u4ZH3lFwNUu08QQB .node circle,#mermaid-svg-u4ZH3lFwNUu08QQB .node ellipse,#mermaid-svg-u4ZH3lFwNUu08QQB .node polygon,#mermaid-svg-u4ZH3lFwNUu08QQB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-u4ZH3lFwNUu08QQB .rough-node .label text,#mermaid-svg-u4ZH3lFwNUu08QQB .node .label text,#mermaid-svg-u4ZH3lFwNUu08QQB .image-shape .label,#mermaid-svg-u4ZH3lFwNUu08QQB .icon-shape .label{text-anchor:middle;}#mermaid-svg-u4ZH3lFwNUu08QQB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-u4ZH3lFwNUu08QQB .rough-node .label,#mermaid-svg-u4ZH3lFwNUu08QQB .node .label,#mermaid-svg-u4ZH3lFwNUu08QQB .image-shape .label,#mermaid-svg-u4ZH3lFwNUu08QQB .icon-shape .label{text-align:center;}#mermaid-svg-u4ZH3lFwNUu08QQB .node.clickable{cursor:pointer;}#mermaid-svg-u4ZH3lFwNUu08QQB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-u4ZH3lFwNUu08QQB .arrowheadPath{fill:#333333;}#mermaid-svg-u4ZH3lFwNUu08QQB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-u4ZH3lFwNUu08QQB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-u4ZH3lFwNUu08QQB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u4ZH3lFwNUu08QQB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-u4ZH3lFwNUu08QQB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u4ZH3lFwNUu08QQB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-u4ZH3lFwNUu08QQB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-u4ZH3lFwNUu08QQB .cluster text{fill:#333;}#mermaid-svg-u4ZH3lFwNUu08QQB .cluster span{color:#333;}#mermaid-svg-u4ZH3lFwNUu08QQB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-u4ZH3lFwNUu08QQB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-u4ZH3lFwNUu08QQB rect.text{fill:none;stroke-width:0;}#mermaid-svg-u4ZH3lFwNUu08QQB .icon-shape,#mermaid-svg-u4ZH3lFwNUu08QQB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u4ZH3lFwNUu08QQB .icon-shape p,#mermaid-svg-u4ZH3lFwNUu08QQB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-u4ZH3lFwNUu08QQB .icon-shape .label rect,#mermaid-svg-u4ZH3lFwNUu08QQB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u4ZH3lFwNUu08QQB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-u4ZH3lFwNUu08QQB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-u4ZH3lFwNUu08QQB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
失败
1次重试
2次重试
3次重试
重试耗尽
人工介入
desc
RocketMQ默认重试十数次,每次重试间隔指数增长
死信队列需要人工监控和干预
正常消费
确认ACK
进入重试队列
延迟10秒
延迟30秒
延迟60秒
进入死信队列
人工处理
管理盲点 :很多团队不知道死信队列的存在,导致异常消息堆积无人处理。关键认知:重试机制需要与告警机制配合,及时介入处理。
积压处理决策树
结论:积压处理需要分级响应,不同程度的积压需要不同的处理策略。
| 积压程度 | 处理策略 | 优先级 | 资源投入 |
|---|---|---|---|
| 轻微积压(<1小时) | 增加消费者数量 | 低 | 2-4台机器 |
| 中度积压(1-24小时) | 消费者扩容+限流 | 中 | 4-8台机器 |
| 严重积压(>24小时) | 新 topic分流+数据修复 | 高 | 8+台机器 |
#mermaid-svg-yVRPYfBTkHtw7gMV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yVRPYfBTkHtw7gMV .error-icon{fill:#552222;}#mermaid-svg-yVRPYfBTkHtw7gMV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yVRPYfBTkHtw7gMV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yVRPYfBTkHtw7gMV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yVRPYfBTkHtw7gMV .marker.cross{stroke:#333333;}#mermaid-svg-yVRPYfBTkHtw7gMV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yVRPYfBTkHtw7gMV p{margin:0;}#mermaid-svg-yVRPYfBTkHtw7gMV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yVRPYfBTkHtw7gMV .cluster-label text{fill:#333;}#mermaid-svg-yVRPYfBTkHtw7gMV .cluster-label span{color:#333;}#mermaid-svg-yVRPYfBTkHtw7gMV .cluster-label span p{background-color:transparent;}#mermaid-svg-yVRPYfBTkHtw7gMV .label text,#mermaid-svg-yVRPYfBTkHtw7gMV span{fill:#333;color:#333;}#mermaid-svg-yVRPYfBTkHtw7gMV .node rect,#mermaid-svg-yVRPYfBTkHtw7gMV .node circle,#mermaid-svg-yVRPYfBTkHtw7gMV .node ellipse,#mermaid-svg-yVRPYfBTkHtw7gMV .node polygon,#mermaid-svg-yVRPYfBTkHtw7gMV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yVRPYfBTkHtw7gMV .rough-node .label text,#mermaid-svg-yVRPYfBTkHtw7gMV .node .label text,#mermaid-svg-yVRPYfBTkHtw7gMV .image-shape .label,#mermaid-svg-yVRPYfBTkHtw7gMV .icon-shape .label{text-anchor:middle;}#mermaid-svg-yVRPYfBTkHtw7gMV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yVRPYfBTkHtw7gMV .rough-node .label,#mermaid-svg-yVRPYfBTkHtw7gMV .node .label,#mermaid-svg-yVRPYfBTkHtw7gMV .image-shape .label,#mermaid-svg-yVRPYfBTkHtw7gMV .icon-shape .label{text-align:center;}#mermaid-svg-yVRPYfBTkHtw7gMV .node.clickable{cursor:pointer;}#mermaid-svg-yVRPYfBTkHtw7gMV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yVRPYfBTkHtw7gMV .arrowheadPath{fill:#333333;}#mermaid-svg-yVRPYfBTkHtw7gMV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yVRPYfBTkHtw7gMV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yVRPYfBTkHtw7gMV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yVRPYfBTkHtw7gMV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yVRPYfBTkHtw7gMV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yVRPYfBTkHtw7gMV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yVRPYfBTkHtw7gMV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yVRPYfBTkHtw7gMV .cluster text{fill:#333;}#mermaid-svg-yVRPYfBTkHtw7gMV .cluster span{color:#333;}#mermaid-svg-yVRPYfBTkHtw7gMV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yVRPYfBTkHtw7gMV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yVRPYfBTkHtw7gMV rect.text{fill:none;stroke-width:0;}#mermaid-svg-yVRPYfBTkHtw7gMV .icon-shape,#mermaid-svg-yVRPYfBTkHtw7gMV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yVRPYfBTkHtw7gMV .icon-shape p,#mermaid-svg-yVRPYfBTkHtw7gMV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yVRPYfBTkHtw7gMV .icon-shape .label rect,#mermaid-svg-yVRPYfBTkHtw7gMV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yVRPYfBTkHtw7gMV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yVRPYfBTkHtw7gMV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yVRPYfBTkHtw7gMV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 积压<1小时
积压1-24小时
积压>24小时
监控QPS
限流策略
数据修复
desc
积压处理要优先保证核心业务不受影响
严重积压可能需要人工介入数据修复
检测到积压
增加消费者数量
消费者扩容+限流
新topic分流
评估效果
保护核心业务
回滚到正常状态
常见错误 :很多团队在出现积压时才临时扩容,缺乏预防性监控和自动化处理机制。最佳实践:建立积压预警机制,提前发现潜在问题。
【源码深挖】
RocketMQ生产者Confirm机制
结论:生产端的Confirm机制是同步发送而非异步发送,这是保证消息不丢失的第一道防线。
java
// RocketMQ生产者发送消息代码
public class RocketMQProducer {
private DefaultMQProducer producer;
public void init() throws Exception {
producer = new DefaultMQProducer("test-producer-group");
producer.setNamesrvAddr("localhost:9876");
// 设置同步发送,等待Broker确认
producer.setSendMsgTimeout(3000);
// 设置重试次数
producer.setRetryTimesWhenSendFailed(2);
producer.start();
}
public void sendWithConfirm(String topic, String message) throws Exception {
Message msg = new Message(topic, message.getBytes());
// 同步发送,会等待Broker返回确认结果
SendResult result = producer.send(msg);
System.out.println("发送结果:" + result.getSendStatus());
// 检查发送状态
if (result.getSendStatus() != SendStatus.SEND_OK) {
throw new RuntimeException("消息发送失败:" + result.getSendStatus());
}
}
}
性能陷阱 :很多团队为了追求性能而使用异步发送,忽略了消息发送失败的处理。关键配置 :sendMsgTimeout和retryTimesWhenSendFailed是保证可靠性的关键参数。
消费者手动ACK实现
结论:消费者的手动ACK是业务处理成功后才确认,而不是拉取到消息就确认。
java
// RocketMQ消费者手动ACK代码
public class RocketMQConsumer {
private DefaultMQPushConsumer consumer;
public void init() throws Exception {
consumer = new DefaultMQPushConsumer("test-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("test-topic", "*");
// 设置消费模式:集群消费
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
try {
// 业务处理逻辑
String message = new String(msg.getBody());
System.out.println("消费消息:" + message);
// 模拟业务处理
if (message.contains("error")) {
throw new RuntimeException("业务处理失败");
}
} catch (Exception e) {
// 消息处理失败,不返回CONSUME_SUCCESS,触发重试
System.err.println("消息处理失败:" + e.getMessage());
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
// 手动确认消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
}
}
致命错误 :很多团队在消息拉取后就立即确认,导致业务处理失败时消息丢失。核心原则 :业务处理成功才返回CONSUME_SUCCESS,否则返回RECONSUME_LATER。
顺序消息发送实现
结论:顺序消息的核心是业务Key的哈希路由,相同Key的消息必然进入同一队列。
java
// RocketMQ顺序消息发送代码
public class OrderedMessageProducer {
private DefaultMQProducer producer;
public void init() throws Exception {
producer = new DefaultMQProducer("ordered-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
}
public void sendOrderedMessage(String topic, String message, String orderId) throws Exception {
Message msg = new Message(topic, message.getBytes());
// 使用MessageQueueSelector选择队列
SendResult result = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
// 根据订单ID哈希选择队列
int orderIdHash = arg.hashCode();
int index = orderIdHash % mqs.size();
return mqs.get(index);
}
}, orderId);
System.out.println("顺序消息发送结果:" + result);
}
}
设计误区 :很多团队不理解顺序消息的原理,错误地使用单队列来保证顺序,导致吞吐量严重不足。正确思路:通过业务Key的哈希路由,在保证顺序性的同时提高并行度。
幂等消费实现代码
结论:幂等消费需要多层防护机制,从快速判断到精确校验,确保高并发场景下的性能和数据一致性。
java
// RocketMQ幂等消费实现代码
@Service
public class IdempotentConsumerService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private MessageLogRepository messageLogRepository;
// 布隆过滤器
private BloomFilter<String> bloomFilter;
@PostConstruct
public void init() {
// 初始化布隆过滤器,预期数百万条数据,误判率约0.01%
bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()),
1000000, 0.01);
}
public void consumeMessage(MessageExt message) {
String messageId = message.getMsgId();
String messageKey = "message:" + messageId;
// 第一层:布隆过滤器快速判断
if (!bloomFilter.mightContain(messageId)) {
// 一定不存在,直接拒绝
System.err.println("消息ID不存在:" + messageId);
return;
}
// 第二层:Redis缓存去重
Boolean exists = redisTemplate.opsForSet().add(messageKey, "processed");
if (exists == null || !exists) {
// Redis中已存在,直接返回成功
System.out.println("消息已处理,幂等跳过:" + messageId);
return;
}
// 第三层:数据库唯一索引校验
try {
processBusinessLogic(message);
// 业务处理成功,记录到数据库
MessageLog log = new MessageLog(messageId, 1);
messageLogRepository.save(log);
} catch (DuplicateKeyException e) {
// 数据库唯一索引冲突,幂等处理
System.out.println("消息已处理,数据库冲突:" + messageId);
}
}
private void processBusinessLogic(MessageExt message) {
// 业务处理逻辑
String body = new String(message.getBody());
System.out.println("处理消息:" + body);
}
}
性能瓶颈 :很多团队只使用单一方案,在高并发时会出现性能瓶颈或数据不一致问题。分层防护:布隆过滤器快速过滤、Redis缓存去重、数据库唯一索引最终校验,三层防护确保可靠性。
Kafka与RocketMQ核心对比
结论:RocketMQ和Kafka的选择需要根据业务场景来决定,没有绝对的好坏之分。
| 维度 | RocketMQ | Kafka |
|---|---|---|
| 架构模型 | Master-Slave | Leader-Follower |
| 事务支持 | 原生支持事务消息 | 支持Exactly-Once但配置复杂 |
| 顺序消费 | 原生支持Message Group | 通过分区保证顺序 |
| 消息可靠性 | 同步刷盘+副本机制 | ISR副本机制 |
| 适用场景 | 金融、电商等强一致性 | 大数据、日志等高吞吐 |
| 最新版本 | 2.3.4(2025年7月) | 4.1.1(2025年11月) |
| 部署复杂度 | 中等(需要NameServer) | 较高(ZooKeeper/KRaft) |
#mermaid-svg-Vdzzkt6KDVlIdnJU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Vdzzkt6KDVlIdnJU .error-icon{fill:#552222;}#mermaid-svg-Vdzzkt6KDVlIdnJU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Vdzzkt6KDVlIdnJU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .marker.cross{stroke:#333333;}#mermaid-svg-Vdzzkt6KDVlIdnJU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Vdzzkt6KDVlIdnJU p{margin:0;}#mermaid-svg-Vdzzkt6KDVlIdnJU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .cluster-label text{fill:#333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .cluster-label span{color:#333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .cluster-label span p{background-color:transparent;}#mermaid-svg-Vdzzkt6KDVlIdnJU .label text,#mermaid-svg-Vdzzkt6KDVlIdnJU span{fill:#333;color:#333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .node rect,#mermaid-svg-Vdzzkt6KDVlIdnJU .node circle,#mermaid-svg-Vdzzkt6KDVlIdnJU .node ellipse,#mermaid-svg-Vdzzkt6KDVlIdnJU .node polygon,#mermaid-svg-Vdzzkt6KDVlIdnJU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Vdzzkt6KDVlIdnJU .rough-node .label text,#mermaid-svg-Vdzzkt6KDVlIdnJU .node .label text,#mermaid-svg-Vdzzkt6KDVlIdnJU .image-shape .label,#mermaid-svg-Vdzzkt6KDVlIdnJU .icon-shape .label{text-anchor:middle;}#mermaid-svg-Vdzzkt6KDVlIdnJU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Vdzzkt6KDVlIdnJU .rough-node .label,#mermaid-svg-Vdzzkt6KDVlIdnJU .node .label,#mermaid-svg-Vdzzkt6KDVlIdnJU .image-shape .label,#mermaid-svg-Vdzzkt6KDVlIdnJU .icon-shape .label{text-align:center;}#mermaid-svg-Vdzzkt6KDVlIdnJU .node.clickable{cursor:pointer;}#mermaid-svg-Vdzzkt6KDVlIdnJU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .arrowheadPath{fill:#333333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Vdzzkt6KDVlIdnJU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vdzzkt6KDVlIdnJU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Vdzzkt6KDVlIdnJU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vdzzkt6KDVlIdnJU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Vdzzkt6KDVlIdnJU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Vdzzkt6KDVlIdnJU .cluster text{fill:#333;}#mermaid-svg-Vdzzkt6KDVlIdnJU .cluster span{color:#333;}#mermaid-svg-Vdzzkt6KDVlIdnJU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Vdzzkt6KDVlIdnJU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Vdzzkt6KDVlIdnJU rect.text{fill:none;stroke-width:0;}#mermaid-svg-Vdzzkt6KDVlIdnJU .icon-shape,#mermaid-svg-Vdzzkt6KDVlIdnJU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vdzzkt6KDVlIdnJU .icon-shape p,#mermaid-svg-Vdzzkt6KDVlIdnJU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Vdzzkt6KDVlIdnJU .icon-shape .label rect,#mermaid-svg-Vdzzkt6KDVlIdnJU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vdzzkt6KDVlIdnJU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Vdzzkt6KDVlIdnJU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Vdzzkt6KDVlIdnJU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} desc
RocketMQ架构简单,适合中小规模集群
Kafka架构复杂,适合大规模分布式场景
Kafka
ZooKeeper/KRaft
Broker Leader
Broker Follower
ISR机制
RocketMQ
NameServer
Broker Master
Broker Slave
同步刷盘
选型误区 :很多团队盲目追求Kafka的高吞吐,却忽略了RocketMQ在事务支持和顺序消费方面的优势。决策依据:业务场景决定技术选型,强一致性选RocketMQ,高吞吐选Kafka。
【生产实战】
支付成功但积分未到账事故复盘
时间 :2025年3月15日 14:30-16:00
量级 :日订单数万单,涉及金额数百万元
指标:消息丢失率约0.02%,影响订单数十单
现象描述
14:30开始,用户反馈支付成功但积分未到账。监控显示:
- 支付服务调用MQ正常
- 积分服务消费失败率从0%上升到15%
- 重试队列消息积压达到数千条
定位过程
-
第一层排查:检查MQ集群状态
- Broker CPU使用率80%,内存使用率70%
- 磁盘IO等待时间较高
- 发现消费者线程池打满
-
第二层排查:检查消费端代码
bash# 查看消费者线程状态 jstack <pid> | grep -A5 -B5 "ThreadPoolExecutor" # 查看消息消费情况 mqadmin consumerStatus -g test-group -
根因定位 :发现消费者代码中存在ack误用
java// 错误的做法:业务处理失败仍返回CONSUME_SUCCESS if (businessProcessSuccess) { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } else { // 业务处理失败,但代码错误地返回了成功 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }
修复方案
-
紧急修复:修正消费端ack逻辑
java// 正确的做法:业务处理失败才返回RECONSUME_LATER if (businessProcessSuccess) { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } else { return ConsumeConcurrentlyStatus.RECONSUME_LATER; } -
系统优化:
- 增加消费者线程数从数个到十几个
- 设置合理的消息重试次数(数次)
- 启用死信队列监控
-
监控完善:
yaml# 添加消息监控指标 spring: rocketmq: producer: sendMsgTimeout: 3000 retryTimesWhenSendFailed: 2 consumer: consumeMessageBatchMaxSize: 1 consumeThreadMin: 16
压测数据对比
测试环境:
- 集群配置:数台Broker,数台消费者
- 消息量级:数万TPS峰值
- 消息大小:1KB左右
- 测试时间:2025年3月下旬
修复前压测数据:
QPS: 数千TPS,出现下降趋势
RT: 从数十ms上升到上百ms
CPU使用率: 从60%上升到90%以上
内存使用率: 从50%上升到80%以上
消息丢失率: 从约0.001%上升到约0.025%
重试队列积压: 从0上升到数万条
修复后压测数据:
QPS: 稳定在万TPS级别
RT: 从约10ms上升到约20ms
CPU使用率: 从40%上升到60%左右
内存使用率: 从40%上升到60%左右
消息丢失率: 从约0.0001%上升到约0.0005%
重试队列积压: 从0上升到数十条
复盘总结
根本原因:消费端代码中业务处理失败仍返回成功确认,导致消息丢失。
改进措施:
- 建立消息消费监控大盘,实时监控消费成功率、RT、积压情况
- 完善代码review机制,重点关注消息ack逻辑和异常处理
- 建立消息积压告警机制,提前发现潜在问题
- 增加幂等性防护,防止重复消费导致的数据不一致
性能优化收益:
- 吞吐量提升:从数千TPS提升到万TPS级别(提升显著)
- 延迟降低:从上百ms降低到约20ms(降低幅度较大)
- 可靠性提升:消息丢失率从约0.025%降低到约0.0005%(降低幅度显著)
- 资源利用率:CPU和内存使用率明显降低,稳定性大幅提升
【追问链条】
追问1:消息不丢失的完整方案是什么?
候选人视角 :
要保证消息不丢失,需要构建全链路防护:
-
生产端:使用同步发送+重试机制
javaproducer.setSendMsgTimeout(3000); producer.setRetryTimesWhenSendFailed(2); // 开启消息轨迹,便于问题追踪 producer.setEnableMsgTrace(true); -
Broker端:启用同步刷盘+多副本机制
properties# Broker配置 flushDiskType=SYNC_FLUSH brokerRole=SYNC_MASTER # 副本数量 defaultTopicQueueNums=4 # 同步副本数量 haMasterAddress=slave1:10909;slave2:10909 -
消费端:手动ACK+重试机制
java// 业务处理成功才返回CONSUME_SUCCESS if (success) { return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } else { return ConsumeConcurrentlyStatus.RECONSUME_LATER; } // 设置重试次数和间隔 consumer.setConsumeMessageBatchMaxSize(1); consumer.setConsumeThreadMin(16);
面试官视角 :
这道题在筛是否有生产级经验。候选人如果只答出"同步刷盘"、"多副本"这些基础概念,说明没有实战经验。有经验的候选人会提到:
- 生产端的confirm机制 和超时重试
- 消费端的业务成功才确认原则
- 死信队列的监控和处理机制
- 消息积压的预防性监控
- 消息轨迹 和监控告警的完整体系
直接挂掉的回答:
- "使用MQ自带的重试机制就行了"(不知道重试不是无限次的)
- "多副本就能保证不丢失"(不知道同步刷盘的重要性)
- "消费端用自动ACK"(不知道业务处理失败的处理)
- "设置重试次数越多越好"(不知道重试次数对性能的影响)
追问2:如何保证消息不重复消费?
候选人视角 :
保证消息不重复消费需要业务层幂等性:
-
唯一ID方案:
java// 消息中包含唯一业务ID Message msg = new Message("test-topic", ("order_" + System.currentTimeMillis() + "_" + UUID.randomUUID()).getBytes()); -
数据库唯一索引:
sqlCREATE TABLE message_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, message_id VARCHAR(64) NOT NULL, status TINYINT NOT NULL, UNIQUE KEY uk_message_id (message_id), INDEX idx_status (status), INDEX idx_create_time (create_time) ); -
Redis去重:
java// 使用Redis set去重,设置过期时间 String messageKey = "message:" + messageId; Boolean exists = redisTemplate.opsForSet().add(messageKey, "processed"); // 设置约24小时过期,防止内存泄漏 redisTemplate.expire(messageKey, 24, TimeUnit.HOURS); if (exists != null && exists) { // 第一次处理 processMessage(message); } -
布隆过滤器优化:
java// 使用Guava布隆过滤器快速判断 BloomFilter<String> bloomFilter = BloomFilter.create( Funnels.stringFunnel(Charset.defaultCharset()), 1000000, 0.01); if (!bloomFilter.mightContain(messageId)) { return; // 一定不存在,直接拒绝 }
面试官视角 :
这道题在筛是否有分布式系统经验。候选人如果只答出"用唯一ID",说明没有实际处理过重复消费问题。有经验的候选人会提到:
- 幂等性设计的权衡:唯一索引的性能影响、Redis的内存占用
- 去重策略的选择:Redis vs 数据库 vs 布隆过滤器的适用场景
- 消息重复的根源:网络抖动、消费者重启、Broker重试等场景
- 业务层面的幂等:订单状态机、支付状态校验、幂等性设计模式
- 性能优化:布隆过滤器快速过滤、Redis批量操作、数据库索引优化
直接挂掉的回答:
- "用UUID就能解决"(不知道UUID在分布式环境中的问题)
- "用数据库唯一索引就行"(不知道高并发下的性能问题)
- "MQ保证不重复"(不知道MQ只能保证至少一次投递)
- "用Redis缓存就行"(不知道Redis的内存泄漏和持久化问题)
追问3:顺序消息的性能优化方案?
候选人视角 :
顺序消息的性能优化需要多维度权衡:
-
分区策略优化:
java// 根据业务量选择合适的分区数 // 单个队列能处理约数千到数万TPS int partitionCount = calculateOptimalPartition(businessVolume); // 分区数量 = 业务量TPS / 单队列处理能力 * 安全系数 // 安全系数一般取1.5-2.0 -
批量消费优化:
java// 顺序消息不建议批量消费,但可以优化处理逻辑 @Override public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) { long startTime = System.currentTimeMillis(); for (MessageExt msg : msgs) { processSingleMessage(msg); // 每处理约100条消息检查一次超时 if (System.currentTimeMillis() - startTime > 1000) { return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE; } } return ConsumeOrderlyStatus.SUCCESS; } -
异步处理优化:
java// 顺序消息保证顺序,但后续处理可以异步 @Service public class AsyncOrderProcessor { private final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor(约10, 约50, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(约1000)); public void processAsync(OrderMessage message) { asyncExecutor.execute(() -> { asyncProcess(message); }); } private void asyncProcess(OrderMessage message) { // 异步处理逻辑 // 如:数据库写入、日志记录、通知发送等 } } -
业务拆分策略:
java// 按业务类型拆分,提高并行度 public void sendOrderMessage(Order order) { String topic = "ORDER_" + order.getType(); // 按类型拆分 String messageKey = "ORDER_" + order.getId(); // 确保同一订单有序 // 使用MessageQueueSelector选择队列 producer.send(new Message(topic, toJson(order)), (mqs, msg, arg) -> { String key = (String) arg; int index = key.hashCode() % mqs.size(); return mqs.get(index); }, messageKey); }
面试官视角 :
这道题在筛是否有高并发优化经验。候选人如果只答出"增加分区数",说明没有实际处理过顺序消息的性能问题。有经验的候选人会提到:
- 分区的数量权衡:太多分区影响顺序,太少分区影响吞吐,需要根据业务量精确计算
- 业务拆分策略:按时间、按业务类型、按用户ID等维度拆分,提高并行度
- 批量处理的边界:顺序消息vs批量消费的矛盾,需要权衡性能和顺序性
- 异步化处理:顺序保证与性能提升的平衡,哪些操作可以异步,哪些必须同步
- 性能监控:队列深度、消费延迟、积压情况的实时监控和预警
- 容错机制:消费者异常时的处理策略,如何保证不丢失消息
直接挂掉的回答:
- "用单队列保证顺序"(不知道性能瓶颈,单队列会成为性能瓶颈)
- "增加分区数就行"(不知道顺序消息的分区限制,增加分区会影响顺序性)
- "用异步处理提高性能"(不知道顺序消息的异步处理边界,异步处理可能影响顺序性)
- "用批量消费提高吞吐"(不知道顺序消息不适合批量消费,批量消费会破坏顺序性)
【总结延伸】
核心结论速记表
| 核心能力 | 关键技术点 | 注意事项 |
|---|---|---|
| 消息不丢 | 生产confirm+同步刷盘+手动ACK | 重试次数和间隔需要合理配置 |
| 消息不重 | 业务唯一ID+数据库唯一索引 | 高并发场景下需要考虑性能影响 |
| 顺序消费 | Message Group+哈希路由 | 分区数量需要权衡吞吐和顺序 |
| 事务消息 | 半消息+本地事务绑定 | 适用于强一致性场景 |
| 积压处理 | 分级响应+自动化监控 | 需要预防性监控和应急预案 |
延伸阅读
-
官方文档:
-
技术规范:
-
经典论文:
- "The End of ZooKeeper: Kafka's Move to Raft Consensus" (2025)
- "Distributed Message Queues: A Comparative Analysis" (2024)
-
最佳实践:
如果这篇对你有帮助,欢迎点赞+关注,专栏持续更新。