ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RabbitMQ实战:消息队列异步解耦、削峰与可靠投递

RabbitMQ实战:消息队列异步解耦、削峰与可靠投递 消息中间件这个东西刚开始接触的时候我也觉得它有点多余——两个服务直接调接口不就完了为什么中间还要架一层直到有次做秒杀活动下单接口被瞬间打进来的请求冲垮数据库连接池直接打满整个系统跟着雪崩。那次事故之后我才真正把RabbitMQ啃了一遍。这篇就把我对它的理解、踩过的坑、以及从安装到实战的完整流程整理下来从它是什么讲到怎么用起来包括Windows下的安装、管理页面怎么进、用户怎么分配、MQTT插件怎么开、前端到底能不能直连这些高频问题全都覆盖到。不管你是刚听说过RabbitMQ这个名字还是已经写过几行发送消息的代码但总觉得没吃透希望这篇能帮你把这块知识串起来。1. RabbitMQ到底是什么先搞懂它在系统里扮演什么角色1.1 用快递驿站理解消息队列我不太喜欢一上来就抛消息代理AMQP协议这些词太劝退。换个说法把RabbitMQ想象成小区门口的快递驿站。你下单买东西生产者发送消息商家把包裹送到驿站消息进入Broker驿站按楼栋分拣Exchange路由最后你自己去取消费者拉取或推送。这中间最关键的三个特征一是你不需要知道商家怎么打包、走哪条物流两边不用互相认识二是商家把包裹放下就能走人不用站在驿站等你来取三是双十一包裹爆仓时驿站先堆着你什么时候有空什么时候取不会因为取件慢就把商家堵死。映射到技术世界这三个特征分别对应异步、解耦、削峰。异步是说生产者发完消息立刻返回不阻塞主流程解耦是说两个服务之间不再有直接的接口依赖上游改了下游不用跟着改削峰是说突发流量先堆在队列里后端按自己的节奏慢慢消费不至于被瞬间打垮。那为什么不用数据库表当队列我自己年轻时候真干过这事——建一张message表生产者insert消费者定时select。小流量下能跑但问题一堆轮询有延迟并发select会锁表消费者挂了消息没人管消息堆积到几百万行查询直接崩。专业的消息队列把这些脏活累活都封好了还额外提供了确认机制、持久化、路由、死信处理这些能力这就是它存在的意义。适用场景上RabbitMQ特别适合业务系统之间的异步通信注册成功后发短信和邮件、订单创建后扣减库存、支付完成后更新积分、日志收集、定时任务调度。它的强项是消息可靠投递和灵活路由而不是海量吞吐那个方向是Kafka的地盘。理解这个定位很重要选错工具比不会用工具更麻烦。1.2 核心概念与内部结构RabbitMQ的模型里有几个必须记住的角色我把它们和快递驿站的对应关系整理成表对照着记会快很多。概念类比说明Producer商家发送消息的一方Consumer取件人接收消息的一方Broker驿站RabbitMQ服务本身Vhost不同小区逻辑隔离权限和队列都在各自vhost下Connection一条主干道客户端到Broker的TCP连接Channel主干道上的车道复用TCP连接真正的收发都在Channel上Exchange分拣中心决定消息往哪个队列投递Queue货架消息实际存储的地方先进先出Binding分拣规则Exchange和Queue之间的绑定关系Routing Key地址标签生产者发消息时带的标记配合Binding决定去向这里面最容易搞混的是Connection和Channel。很多人写代码习惯每次发消息都建一个连接这是大忌。TCP连接的建立成本很高握手、认证、vhost权限检查一套下来几十毫秒就没了。正确做法是一个应用维护一到几个Connection每个线程或每次操作用一个Channel用完就关。Channel是轻量级的开销小到可以忽略。还有一个新手常常忽略的点Vhost是权限的最小隔离单位。默认有个/的vhost很多教程直接拿它用生产环境最好按业务建独立的vhost比如order、pay、user各一个。这样不同业务的队列名不会冲突权限也能分开控制某个业务要迁移或者清理时不会误伤别人。消息的完整流转路径是这样的生产者建立Connection开Channel声明Exchange和Queue并用Routing Key绑定然后往Exchange发消息Exchange根据类型和Routing Key把消息投到一个或多个Queue消费者从Queue取出消息并返回确认Broker收到确认后把消息从队列删除。这条链路里任何一个环节出问题消息都可能丢或者重复后面第4节会专门讲怎么堵这些漏洞。1.3 和Kafka、RocketMQ放在一起怎么选这个问题面试里问得特别多也是实际做架构时要拍板的。我按我的使用感受说一下区别不做绝对化结论因为选型永远看场景。RabbitMQ的优势在于路由灵活、延迟低、生态成熟。它支持四种Exchange类型能做到很复杂的路由逻辑单条消息延迟能压到毫秒级管理界面友好运维门槛低。缺点是单队列吞吐量有限堆积大量消息时性能下降明显官方自己也不建议把它当海量日志管道用。Kafka的强项是高吞吐和持久化日志。它天生为流式数据设计靠顺序写磁盘和分区把吞吐拉到很高适合日志采集、埋点、实时计算的数据源。但它的路由能力弱延迟通常比RabbitMQ高运维也重一些。RocketMQ在两者之间找平衡有事务消息、延时消息、消息回溯这些特性电商交易场景用得多。我的经验是业务解耦、任务分发、要求消息可靠且路由复杂选RabbitMQ数据管道、日志、需要超高吞吐和回溯能力选Kafka。别为了追热点硬上我见过一个小项目上Kafka就为发发短信通知结果运维成本和机器成本都翻倍得不偿失。2. RabbitMQ的主要功能五个核心能力逐条拆2.1 异步解耦把接口响应时间砍下一大截先讲异步。假设用户注册要干四件事写用户表、发欢迎邮件、发短信、初始化账户。同步写法就是串行执行假设写库50ms、发邮件200ms、发短信150ms、初始化80ms总共480ms用户就得等将近半秒。如果邮件服务当时抽风超时了整个注册接口直接失败体验极差。用RabbitMQ改造后写用户表和初始化账户这两件必须同步完成的事情照旧发邮件和发短信改成往队列里丢消息丢消息大概5ms。接口响应时间从480ms变成135ms左右而且邮件服务挂了也不影响注册成功消息在队列里等着等邮件服务恢复了自己慢慢消费。这就是解耦带来的直接收益上游不再被下游的可用性和性能绑架。但异步不是没有代价这里有个很多人绕不过去的坎本地事务和消息发送的一致性。你在一个方法里先写库再发消息写库成功但发消息失败怎么办或者发消息成功但事务回滚了怎么办这就是分布式事务问题。常见的处理办法有三种我按复杂度从低到高说。第一种是本地消息表在业务库里加一张消息表写业务数据和写消息记录放在同一个本地事务里保证要么都成功要么都失败然后再由定时任务扫这张表把消息投到RabbitMQ投递成功就把记录标记为已完成。这个方案不依赖任何高级特性最稳缺点是多了张表和一次轮询。第二种是RabbitMQ的事务机制txSelect、txCommit、txRollback但它会把吞吐量拉低一个数量级生产环境基本不用了解一下就行。第三种是Publisher Confirm加回调发送方开启confirm模式Broker确认收到后回调没收到就重发或者落库补偿。这个方案性能好是主流做法但要自己处理重发的幂等性。提示这套同步逻辑本身也要靠RabbitMQ的确认机制兜底所以第2.4节的可靠性内容务必先看完再动手。2.2 削峰填谷秒杀和抢购场景的流量缓冲削峰这个能力在秒杀场景里体现得最明显。假设某个活动瞬间涌入10万请求后端每秒只能处理2000单。同步处理的话多余的9.8万请求会把线程池占满数据库连接池打爆最后连查库存这种简单操作都超时整个服务雪崩。加一层RabbitMQ之后流程变成网关层先做一轮限流和校验通过校验的请求把下单消息写进队列队列容量足够大10万条消息堆进去毫无压力后端消费者按每秒2000条的节奏匀速拉取处理处理完写入数据库。用户端立即返回排队中的提示前端轮询或者通过WebSocket查结果。用户体验上从转圈半天然后报错变成秒出排队号后端从被打死变成稳定运行。队列在这里起到了蓄水池的作用。不过削峰有几个细节必须注意。队列要有上限保护不能让无限流量把内存撑爆RabbitMQ可以设置队列最大长度或者最大字节数超了就拒绝或者丢弃最老的消息。要有排队反馈机制不能让用户傻等前端要能查到当前排队位置或者直接推送结果。消费端要能水平扩容流量高峰时可以临时多开几个消费者实例用完再缩回去。还有个坑我踩过削峰时如果消费者处理逻辑里有同步调用第三方接口而这个接口本身很慢消费者会被拖住队列堆积越来越多。这时候要么把第三方调用也异步化要么给消费者设置合理的并发数prefetch别一次拉太多消息憋在手里。2.3 消息路由四种Exchange类型的实战差异路由是RabbitMQ区别于其他中间件最有特色的地方。Exchange有四种类型我一个个说清楚它们适合什么。Direct直连最常用Routing Key完全匹配才投递。比如Routing Key是order.create的消息只会进绑定了order.create的队列。适合点对点的任务分发。Fanout广播忽略Routing Key投给所有绑定的队列。适合配置刷新通知、缓存清理这类需要全量广播的场景。Topic主题按模式匹配*匹配一个单词#匹配零个或多个单词。比如绑定order.*.paid能收到order.book.paid和order.food.paid但收不到order.book.123.paid。这个类型在电商里特别有用用order.#一条绑定就能收所有订单相关消息。适合按业务维度分类订阅。Headers头匹配根据消息头里的键值对匹配跟Routing Key无关。实际项目里用得很少因为性能不如Topic可读性也差知道有这么个东西就行。我拿一个实际例子说下Topic的威力。假设一个订单系统要发出多种事件创建、支付、发货、完成、取消。如果用Direct得给每种事件建一个Exchange和一堆队列绑定配置起来很啰嗦。用Topic就一个Exchange搞定消费者按需绑定物流服务绑order.*.shipped财务服务绑order.*.paid客服系统绑order.#收全量。新增事件类型时只要路由键设计得规范消费者几乎不用改配置。路由键的命名规范我建议用点分格式从大到小比如业务.模块.动作.结果。别用下划线或者驼峰容易看起来乱也容易和正则冲突。这个规范一开始定好后面扩起来顺很多。2.4 可靠投递三层防护堵住消息丢失消息丢失是生产事故的重灾区我见过的最惨一次是订单支付成功但库存没扣最后靠人工对账补数据。要保证消息不丢得在生产者、Broker、消费者三个环节都做防护。生产端开启Publisher Confirm。发送消息后Broker会回一个ack或者nack收到ack说明消息到了Exchange。如果Exchange路由不到任何队列消息会被丢弃这时候要配合Mandatory参数加ReturnCallbackBroker会把无法路由的消息退回给生产者。这两个机制一个管到没到Exchange一个管有没有队列接合起来才完整。Broker端交换机和队列都要设置持久化durable消息发送时要设置deliveryMode为2持久化。这样即使Broker重启消息也还在。但要注意持久化会带来磁盘IO开销吞吐会下降不是所有消息都需要像心跳检测之类的消息完全可以不持久化。消费端关掉自动ack改用手动ack。自动ack是消息一投出去就算消费成功消费者处理到一半崩了消息就没了。手动ack是在业务逻辑真正执行完再调basicAck处理失败就调basicNack并设置requeue重新入队或者转死信。这里有个坑如果业务逻辑抛异常了你没catch住然后连接断开RabbitMQ会把没ack的消息重新投给其他消费者可能导致重复消费所以消费逻辑必须做幂等。说到幂等常见做法有三种用数据库唯一索引挡住重复插入用Redis记录已处理的消息ID处理前先查一下或者业务上设计成天然幂等比如设置状态为已支付这种操作重复执行也只有一个结果。我一般优先选唯一索引简单直接还不用额外依赖。死信队列是配套的重要机制。消息变成死信有三种情况被拒绝且requeue为false、超过TTL过期、队列达到最大长度被丢弃。这些消息不会凭空消失会转到绑定的死信交换机你可以把它们收进一个专门的死信队列后面人工排查或者自动重试。生产环境务必备一个死信队列否则出了问题是真的一点线索都没有。2.5 延时任务与顺序消费插件和方案的取舍延时任务是实际业务里需求非常多的功能比如订单30分钟未支付自动取消、预约提前15分钟提醒。RabbitMQ原生没有延时队列得靠其他方式实现。老牌方案是TTL加死信队列建一个没有消费者的队列设置消息TTL为30分钟消息过期后变成死信自动转到真正的消费队列。这个方案不用装插件缺点是不同延时时间要建不同的队列而且RabbitMQ只保证过期消息在队头时才会被检查如果队列里混着不同TTL的消息可能出现延时不准的情况。另一个方案是rabbitmq_delayed_message_exchange插件装完之后多出一种x-delayed-message类型的Exchange发消息时指定延迟毫秒数就行灵活得多不用为每个延时时间建队列。缺点是这是社区插件官方不提供支持版本升级时要注意兼容性。顺序消费也是常被问到的问题。RabbitMQ的队列本身是先进先出的但如果有多个消费者并发消费同一个队列顺序就保不住了。要保证顺序得把需要保序的消息路由到同一个队列并且这个队列只用一个消费者消费。代价是并发度下降所以只对真正需要保序的业务这么做比如状态流转类消息。或者更细粒度一点按业务ID做哈希路由同一个订单的消息进同一队列不同订单之间还能并发。这个思路在Kafka里叫分区RabbitMQ里可以用一致性哈希Exchange达到类似效果。3. Windows下从零部署安装、启动与管理页面3.1 Erlang和RabbitMQ的版本匹配是第一个坑RabbitMQ是Erlang写的所以必须先装Erlang运行时。版本匹配是新手栽的第一个跟头——版本不对服务要么起不来要么起来后各种诡异报错。所以第一步不是下载安装包而是去官网查版本对应表确认你的RabbitMQ版本支持哪个Erlang大版本范围。我的建议是不要追最新版选一个稳定且社区资料多的组合比如RabbitMQ 3.11.x或3.12.x配对应的Erlang 25/26。下载时注意Windows下要选带有OTP标识的Windows installer别下成源码包。安装Erlang时有两个细节。一是安装路径不要带空格和中文否则后面配置环境变量和启动脚本时容易出各种识别错误。二是安装完成后要配ERLANG_HOME环境变量并把%ERLANG_HOME%\bin加到Path里。配完之后开个新的命令行窗口敲erl看看能不能进去能进去说明环境没问题这一步别跳过很多人后面RabbitMQ启动失败就是因为Erlang环境没配对。3.2 安装步骤和服务启动方式Erlang装好之后装RabbitMQ同样是双击安装包一路下一步路径同样避开中文和空格。装完之后RabbitMQ的sbin目录下有几个关键脚本比如rabbitmq-server.bat前台启动、rabbitmqctl.bat管理命令行、rabbitmq-plugins.bat插件管理。启动有两种方式。一种是注册成Windows服务装的时候通常会自动注册服务名一般叫RabbitMQ可以在服务管理界面里设置成自动启动另一种是手动在命令行启动进sbin目录执行rabbitmq-server.bat这种方式的好处是能实时看到日志输出排查启动失败时特别有用。开发环境我建议先用命令行方式跑通确认没问题再设成服务。启动成功的标志是日志里出现Server startup complete之类的提示并且能看到监听的端口信息。默认端口是5672这是给客户端连接用的AMQP端口。如果这个端口被占用比如你之前装过别的MQ启动会失败先排查端口占用情况。3.3 管理页面怎么打开、怎么用光有命令行管理太不直观了RabbitMQ提供了一个Web管理界面但默认不开启需要手动装插件。命令是在sbin目录下执行rabbitmq-plugins enable rabbitmq_management执行完会提示启用成功通常不用重启服务。然后浏览器访问http://本机IP:15672看到登录页就说明成功了。登录账号默认是guest/guest但guest用户只能从本机localhost访问远程连会被拒绝这是官方的安全设计。所以如果你在服务器上部署必须新建一个用户并授予权限这一步后面单独讲。管理界面里几个我常用的功能Overview页能看到消息吞吐速率、连接数、队列数排查性能问题第一步就看这里Connections页能看每个连接的来源IP和Channel数发现连接泄漏就靠它Queues页能看到每个队列的消息堆积量、消费者数量、消费速率某个队列堆积暴涨基本就说明消费者出问题了Exchanges页可以手动发消息测试路由是否正确调试时很好用还有一个很实用的功能是在队列详情里能直接查看消息内容并手动重新投递排查问题省事。3.4 用户、Vhost和权限分配生产环境必须做这几件事。第一步新建用户rabbitmqctl add_user 用户名 密码。第二步设置标签标签决定这个用户在管理界面能看到什么administrator能管所有东西monitoring只能看监控management能管自己的vhost还有一个policymaker能管策略。第三步建Vhostrabbitmqctl add_vhost 业务名。第四步授权rabbitmqctl set_permissions -p 业务名 用户名 .* .* .*三个参数分别对应配置、写、读的正则权限生产上建议收窄别一股脑用.*。权限分配我踩过一个坑给应用账号授了administrator标签结果某次误操作在管理界面删了个队列消息全没了。后来统一改成应用账号只给management标签加最小权限管理员账号单独保留且只在运维手里。这个习惯养成了能避免很多误操作。另外提醒一点rabbitmqctl的某些参数在不同版本里有变化比如早期添加用户是add_user后来一些版本有调整遇到命令报错先rabbitmqctl help看一下当前版本的用法别死记教程。3.5 开启MQTT插件并用MQTTX连接测试物联网场景经常需要MQTT协议RabbitMQ通过插件支持。开启命令是rabbitmq-plugins enable rabbitmq_mqtt开完之后MQTT默认监听1883端口。如果要用WebSocket方式连比如网页端还需要开rabbitmq_web_mqtt它会额外监听15675端口WebSocket路径是/ws。用MQTTX测试连接时几个参数要填对主机填服务器IP端口1883TCP直连或15675WebSocket如果是WebSocket方式路径要填/ws协议选mqtt或者ws。用户名密码用RabbitMQ里建的那个账号注意这个账号必须有对应vhost的权限否则连接会被拒。消息在RabbitMQ内部是怎么对应的MQTT的Topic会映射成AMQP的Routing KeyExchange类型一般用topic。所以一个MQTT客户端往device/sensor/temp发消息你在管理界面能看到它进入了对应的Exchange只要绑定规则写对了AMQP的消费者也能消费到这些消息。这个互通性挺有意思等于一套Broker同时服务两种协议。有几个坑我说一下。一是MQTT的Topic不支持某些特殊字符设计Topic层级时别用奇怪符号。二是clean session和持久会话的行为差异设备离线期间的消息要不要保留取决于客户端连接时的clean session标志和队列配置。三是默认MQTT账号权限某些版本里MQTT插件默认只允许特定用户连不上先查用户权限和vhost配置。4. 前端到底能不能直连RabbitMQ4.1 为什么浏览器不能直接说AMQP这个问题被问得特别多我自己也纠结过。先说结论浏览器不能直接连RabbitMQ的5672端口因为AMQP是一个基于TCP的自定义二进制协议而浏览器里的JavaScript只能发起HTTP、WebSocket这类请求没法直接说AMQP。所以前端要连RabbitMQ必须走一个协议转换的中间层。那为什么很多架构里前端又不直接连消息队列更重要的原因是安全。如果把MQ的账号密码放到前端代码里任何人打开开发者工具就能拿到等于把整个消息系统的钥匙公开了——他可以随便发消息、消费消息、甚至删队列。除此之外还有权限控制的问题消息队列的权限粒度是vhost级别的没法精确控制到这个用户只能订阅这个topic。所以正规做法是让前端连自己的后端服务由后端代理去和RabbitMQ交互。4.2 WebSocket方案Web-STOMP和Web-MQTT那有没有例外有的就是只读订阅类的场景比如大屏展示实时数据、网页端看设备状态。这种场景下如果要求消息实时推送到浏览器用WebSocket桥接是合理的。RabbitMQ提供两个WebSocket插件。一个是Web-STOMPrabbitmq_web_stomp把STOMP协议通过WebSocket暴露出来默认15674端口路径/ws。STOMP是一种基于文本的简单协议比AMQP好实现前端用stomp.js这类库能连。另一个是Web-MQTTrabbitmq_web_mqtt端口15675路径/ws前端用MQTT.js就能连这个在物联网前端展示里更常见。即便如此我还是建议加一层自己的鉴权网关。做法是前端连你自己的WebSocket服务后端服务持有一个权限受限的MQ账号只能订阅特定的几个队列或topic然后把消息转发给前端。这样前端永远拿不到MQ的凭据权限也能精确控制到业务层面。多写这一个服务长期看省心得多。4.3 我推荐的前后端协作架构综合下来我的推荐是这样的前端一律通过HTTP接口或者自己的WebSocket服务交互不直接碰RabbitMQ需要实时推送时后端服务订阅MQ然后通过SSE、WebSocket推给前端如果是物联网大屏这类对实时性要求高、用户群体可控的场景才考虑用Web-MQTT让前端直连但一定要用只读账号并限制订阅范围。这个方案看起来多了一层但它把安全边界划清楚了。业务量小的时候可能觉得麻烦等到系统复杂起来你会发现这一层是必须的。5. 踩坑实录启动失败、消息堆积与排查速查表5.1 启动失败的几种典型原因RabbitMQ在Windows上启动失败太常见了我把遇到过的原因列一遍。最常见的是Erlang环境变量没配好表现为启动脚本一闪而过或者提示找不到erl。解决方法是确认ERLANG_HOME指向Erlang安装根目录并且Path里有%ERLANG_HOME%\bin配完要重开命令行窗口。第二种是版本不匹配日志里会出现类似incompatible with或者要求某个Erlang版本的提示。解决方法是按官网版本表重新下对应的Erlang。第三种是端口被占用尤其是5672、15672、25672这几个端口可能被之前装的老版本MQ或者其他软件占着。用netstat -ano | findstr 端口号查一下找到占用进程处理掉再启动。第四种是**.erlang.cookie文件问题**。RabbitMQ靠这个文件做节点间认证如果Windows环境里存在多个cookie文件或者文件权限有问题启动会失败。可以检查用户目录下的.erlang.cookie和RabbitMQ数据目录下的cookie是否一致不一致时手动同步。第五种是数据目录权限或磁盘满。RabbitMQ的数据目录要可写磁盘满了也会启动失败。生产环境务必监控磁盘队列堆积和日志增长都很吃空间。第六种是节点名冲突日志里会看到nodedown或者节点名解析失败。Windows下有时候主机名带特殊字符会出问题可以在配置里显式指定节点名。排查思路总结起来就是先看日志数据目录下的log文件夹或者命令行启动时的输出再查端口然后验环境变量最后看版本和cookie。按这个顺序基本都能定位。5.2 消息堆积和消费异常的排查路径消息堆积是最常见的线上问题管理界面里队列的Ready数量一直涨消费速率跟不上。排查顺序我一般这样走先看消费者数量如果消费者数为0说明消费端挂了或者没连上先查应用日志如果消费者数正常但消费速率低看消费逻辑是不是有阻塞比如有同步的远程调用或者慢SQL再看prefetch设置prefetch设得太小会导致每次只拉一两条效率上不去设得太大又会一次憋一堆消息在消费者手里内存和公平性都有问题一般设成几十到几百之间按单条处理时间调。如果发现消息重复消费优先查消费者有没有正确ack、业务逻辑有没有中途抛异常导致消息重新入队。根本解法还是幂等别指望消息只来一次。内存告警也很常见RabbitMQ在内存或磁盘超过阈值时会阻塞生产者这叫做流控表现为发送消息卡住。这时候要么加快消费要么扩内存要么给队列设TTL让消息自动过期别硬扛。生产环境一定要配置惰性队列lazy queue消息直接落盘而不是全放内存虽然吞吐低一点但能扛堆积。5.3 常见问题速查表现象可能原因排查方向启动失败无日志Erlang环境变量错误检查ERLANG_HOME和Path启动失败提示版本不兼容Erlang与MQ版本不匹配查官网版本对应表远程访问管理页面被拒guest用户仅限本机新建用户并授权MQTTX连不上插件未开或端口错检查1883/15675和插件状态消息堆积持续增长消费端异常或速率低查消费者数量、慢逻辑、prefetch消息重复消费未正确ack或异常重入检查ack逻辑业务加幂等生产者发送卡住内存/磁盘告警触发流控加快消费或扩容配惰性队列消息莫名丢失未持久化或自动ack开启持久化、confirm、手动ack队列里消息不消费绑定关系或Routing Key错管理界面查Binding和路由WebSocket连不上插件未开或路径不对检查插件路径填/ws6. 高频面试问题我的回答思路6.1 概念和原理类问题RabbitMQ怎么保证消息不丢失这是出现频率最高的问题。回答要分三段说生产端开confirm和mandatoryBroker端交换机和队列持久化加消息持久化消费端手动ack。少说一段就不完整面试官一般会追问如果消费失败怎么办这时候接死信队列和重试机制。怎么保证消息不重复消费先说明RabbitMQ本身不保证不重复只能保证至少一次投递所以幂等必须由业务自己实现。然后给出具体方案唯一索引、Redis去重表、状态机判断。举一个实际例子会更有说服力比如支付回调。RabbitMQ的消息什么时候会变成死信三种情况被拒绝且requeue为false、消息TTL过期、队列超长被丢弃。然后再补一句死信可以配置死信交换机和死信队列做后续处理。Exchange有哪几种类型分别什么场景用四种每种配一个真实场景。Direct做任务分发Fanout做广播Topic做分类订阅Headers基本不用。能说出Topic的匹配规则会加分。6.2 场景设计类问题秒杀系统怎么用RabbitMQ削峰回答要完整网关限流做前置过滤通过校验的请求写队列返回排队提示消费端按数据库承载能力匀速消费多实例水平扩展队列设最大长度防内存爆前端轮询或推送查结果超时未处理的进死信队列做补偿。怎么实现延时任务两条路都说TTL加死信队列优点是原生支持缺点是每个延时时间要建队列且只保证队头过期检查延迟插件优点是灵活缺点是社区插件要考虑版本兼容。然后补一句业务上如果延时精度要求不高定时任务扫表也是可选方案。MQ集群和镜像队列是怎么回事简单说就是多个节点组成集群队列可以镜像到多个节点主节点挂了从节点顶上保证高可用。新版本推荐用**仲裁队列quorum queue**替代镜像队列因为镜像队列在极端情况下有数据不一致的风险。这个点能提一下会让面试官觉得你关注的是较新的实践。如果队列堆积了几百万条消息怎么处理应急手段临时扩容消费者多实例消费但要注意数据库压力或者用工具把消息导出到别的地方慢慢处理。根治手段加惰性队列、设置消息TTL、优化消费逻辑、增加限流避免上游打爆。如果消息确认没用了直接清空队列也是一种选择要知道怎么清。我个人在面试里被问最深的一次是**你说的这些可靠性机制会不会带来性能问题怎么权衡**这个问题没有标准答案考的是你有没有真正在业务里做过取舍。我的回答是不需要可靠的消息就别开持久化和confirm比如日志类、监控类消息核心业务消息全开性能损失可以通过增加生产者并发、批量发送来弥补。能说出取舍标准比背一堆名词有用得多。最后分享个我自己养成的习惯每次上线涉及消息队列的改动先在测试环境把生产者停掉、Broker杀掉重启、消费者中途中断看看消息会不会丢、会不会重复、能不能自动恢复。这几个破坏性测试跑一遍比看十遍文档都管用。另外队列和Exchange的声明代码务必放进版本管理别在生产控制台上手动建时间一长没人记得当初是什么绑定关系出问题连线索都找不到。
返回列表