ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RabbitMQ实战指南:从安装部署到消息可靠性配置

RabbitMQ实战指南:从安装部署到消息可靠性配置 先说一个我自己踩过不少次的场景多个系统之间同步数据最初的方案是定时任务每隔几分钟去查一次数据库有新数据就拉取。系统少的时候还好系统一多轮询风暴、接口超时、事务不一致全来了。后来想明白一件事系统之间的数据流动不应该由“主动去查”驱动而应该由“事件通知”驱动。RabbitMQ就是做这件事用得最广泛的消息中间件之一。这篇文章我不会给你讲那种“概念十连击再给几个直升机式的Demo”的教程。我打算把我从安装到跑通、再到踩坑的全过程拆给你看包括Windows和Docker两种部署方式、管理界面里那个坑了无数人的admin账号和Virtual Host权限问题、生产者和消费者的代码怎么写以及到底要不要在项目里上RabbitMQ。你如果是刚接触消息队列看完这篇应该能自己搭起一套能用的环境如果你已经在用了重点看第三章和第五章那些问题我敢说你迟早会碰上。1. 先把核心模型搞清楚交换机、队列、绑定关系1.1 从快递分拣理解RabbitMQ架构RabbitMQ的核心模型是生产者 - 交换机 - 队列 - 消费者。很多人一开始不理解明明自己有队列为什么上面还要套一个交换机这层抽象不是多余的。我用快递分拣来打比方。生产者是各个营业网点消费者是收件人。队列是快递员的送货路线每条路线对应一个站点。那交换机是什么是分拣中心。你寄快递不会直接把包裹塞给某个快递员你得把包裹交到分拣中心分拣中心根据面单上的规则决定包裹进哪条运输线路。如果没有分拣中心每发一条消息你都得知道“哪个队列会收”这就等于让生产者和队列直接耦合了。今天队列A换成队列B生产者代码就得改。有了交换机生产者只关心“我把消息发给哪个交换机”至于消息最终落到哪个队列那是交换机通过绑定规则决定的。在这个模型里Connection和Channel也值得多说一句。Connection是客户端和RabbitMQ服务器之间的TCP长连接Channel是建立在Connection之上的虚拟信道。很多人第一次写代码会问为什么不直接在Connection上收发消息因为一个TCP连接的生命周期和资源开销比较大实际生产环境里一个连接上会同时跑多个业务流。Channel就是用来做逻辑隔离的每个业务一个Channel互不干扰Connection由多个Channel共享。你写代码的时候记住一句话连接是长生命周期的信道是短生命周期的用完就关省资源。1.2 四种交换机类型确认别选错RabbitMQ提供四种交换机实际高频使用的只有三种第四种基本可以当历史遗留看。类型路由规则使用场景Direct路由键完全匹配点对点发送按指定队列投递Fanout忽略路由键广播到所有绑定的队列事件广播、配置刷新Topic路由键按通配符匹配按主题分类订阅Headers按消息头的键值匹配很少使用性能不如以上三种Direct交换机是默认类型也是很多初学者第一个接触到的。它要求路由键完全一致比如消息带上order.created这个键绑定了order.created的队列才会收到。Fanout最粗暴不认路由键所有绑定的队列人手一份适合做全局广播比如所有服务都要刷新本地缓存的场景。Topic是我个人用的最多的类型。它支持两个通配符*代表匹配一个单词#代表匹配零个或多个单词。比如路由键order.created可以被order.#匹配到也能被*.created匹配到。这样最大的好处是生产者可以按业务事件名发消息消费者按自己关心的事件范围订阅谁关心谁绑定不用改生产者代码。Headers交换机需要匹配多个消息头字段性能和灵活性都一般除非是遗留系统否则我建议直接绕开。理解了这四个类型你对RabbitMQ的基本模型就有底了。接下来是安装部署这一步看起来简单坑却一点都不少。2. 安装部署Windows和Docker两种方式实测记录2.1 Windows安装Erlang版本不对是启动失败第一杀手如果你在Windows上安装RabbitMQ最容易踩的第一个坑就是下载了最新版RabbitMQ却没有对应版本的Erlang然后服务启动失败。RabbitMQ对Erlang版本有严格的兼容范围不是随便装一个新版Erlang就能跑。我建议先查官方版本兼容表再下载对应的Erlang安装包。安装顺序是先装Erlang再装RabbitMQ。装完后用管理员权限运行RabbitMQ的“RabbitMQ Command Prompt”命令窗口执行rabbitmq-service.bat start启动失败的时候不要干瞪眼先看日志。日志在%APPDATA%\RabbitMQ\log目录下文件名一般叫rabbit你的主机名.log。我第一次排查时在日志里看到的是Erlang版本警告把Erlang换掉就好了。还有一个隐蔽的坑Windows主机名如果包含中划线或点号节点名解析会出问题RabbitMQ服务可能起不来。具体表现是启动报错nodedown但服务明明注册了。这个解决方法是修改系统主机名为纯字母加数字或者用rabbitmq-server.bat start在前台跑看完整报错。Windows下我建议还要装一个插件再开始用不然管理界面是打不开的。命令如下rabbitmq-plugins.bat enable rabbitmq_management装完插件重启服务浏览器访问http://localhost:15672用默认账号guest/guest登录。Windows本机访问默认账号是没问题的问题出在Docker部署上下面细说。2.2 Docker部署一条命令跑起来但默认账号是个陷阱Docker方式比Windows省心很多一条命令就能拉起带管理界面的服务docker run -d --name rabbitmq \ -p 5672:5672 -p 15672:15672 \ -e RABBITMQ_DEFAULT_USERadmin \ -e RABBITMQ_DEFAULT_PASSadmin123 \ rabbitmq:3-management注意我用了rabbitmq:3-management这个镜像它自带管理插件。如果你用rabbitmq:3这种不带management后缀的镜像容器里是没有管理界面的需要进去手动启用插件多一步操作。端口方面5672是AMQP协议端口客户端连消息用这个15672是管理界面和HTTP API端口。很多人容器起来了网页也打开了但客户端代码连不上去就是因为只映射了15672把5672漏了。端口映射缺一个后面调试起来特别容易误判成代码问题。默认情况下容器里的guest账号只能在localhost访问外部IP一律拒绝。这是RabbitMQ的安全机制防止你用默认账号裸奔。所以我上面创建了一个admin账号并设置了密码。这里就要引出热搜里那个高频问题为什么我用admin账号登录了管理界面却什么都干不了下一章专门讲。3. 管理界面的坑账号、Virtual Host和权限排查链路3.1 admin账号为什么不能创建虚拟主机很多人在Docker部署成功后进入管理界面想新建一个Virtual Host结果发现界面上的按钮是灰的或者点击后报错没有权限。这时候你会觉得很诡异我明明是admin账号为什么连建个虚拟主机都不行问题出在“admin”这个名字上。RabbitMQ判断用户权限不是看用户名而是看用户的tags标签。如果你用RABBITMQ_DEFAULT_USERadmin创建账号Docker镜像默认只给它设置administrator标签吗不是的。默认情况下这个账号的标签是空的或者最多带上administrator权限但标签本身需要你明确配置。我实际测下来的情况是用环境变量创建的用户确实带有administrator标签可以管理用户和策略。但在某些镜像版本里新建用户或操作Virtual Host的权限需要额外确认。更稳的做法是创建完账号后手动在管理界面或命令行里再设置一遍标签和权限确保万无一失。Virtual Host是RabbitMQ做资源隔离的最小单位相当于一个独立命名空间。在每个Virtual Host里面用户可以定义自己的交换机、队列、绑定。不同的Virtual Host之间完全隔离互不可见。你以后要做的第一件事通常是创建一个专用Virtual Host比如/order或者/app1然后把某个用户绑定到该Virtual Host并授予权限。3.2 Virtual Host和权限配置的完整操作路径在管理界面操作步骤如下用鼠标点击顶部导航栏的Admin进入用户管理页面。在用户列表里找到你的账号点击用户名进入详情页。在Permissions区域找到Virtual Host下的权限配置。在Virtual Host输入框填上要授权的虚拟主机名比如/order。配置三个权限位Configure、Write、Read。这三个权限位具体控制什么我建议你别只看名称猜。Configure表示能否配置资源创建和销毁交换机、队列Write表示能否发送消息到交换机或队列Read表示能否从队列消费消息以及清理消息。实际业务里一般给全套权限即可输入.*三个正则表达式框分别填上代表对所有资源都有权限。如果不想在网页上点可以用命令行权限模型完全一致docker exec -it rabbitmq rabbitmqctl add_user admin admin123 docker exec -it rabbitmq rabbitmqctl set_user_tags admin administrator docker exec -it rabbitmq rabbitmqctl add_vhost /order docker exec -it rabbitmq rabbitmqctl set_permissions -p /order admin .* .* .*这几条命令的含义是创建用户、设置管理员标签、创建虚拟主机、授予该用户在指定虚拟主机上的全部权限。这里的-p /order指定了Virtual Host.* .* .*分别对应配置、写、读权限。3.3 管理界面显示“无法连接服务器”的排查链路还有个热搜问题值得单独说rabbitmqctl能创建用户但Web管理界面显示不能连接到服务器。这个问题的迷惑性很强因为命令行工具和应用界面似乎“连的不是同一个服务”。实际情况通常有两种。第一种是epmd进程没起来。epmd是Erlang的端口映射守护进程负责解析RabbitMQ节点名到TCP端口的映射。如果它挂了命令行工具和节点之间的通信就会异常。重启RabbitMQ服务或者重启容器可以解决大部分epmd问题。第二种更常见管理插件没启用。命令行工具走的是CLI协议管理界面走的是HTTP插件协议。CLI能通不代表HTTP插件也能通。你需要在容器里执行rabbitmq-plugins list rabbitmq-plugins enable rabbitmq_management执行完再重启一次容器。我先列了list是为了让你看清楚当前状态带[E*]前缀表示已启用不带就是停用。这个问题我遇到过不止一次尤其是用自定义镜像或者通过docker commit制作的镜像管理插件很容易没带进去。排查这类问题的顺序我的经验是先看容器日志docker logs rabbitmq再检查插件列表再检查端口映射最后才看账号权限。很多人一上来就怀疑权限折腾半天结果只是插件没启用方向完全错了。4. 基本使用实战从生产者到消费者4.1 环境准备和连接参数说明部署好环境后我们用代码把消息真正跑起来。语言我选Python做演示因为pika库足够简单五分钟就能跑通。你如果是Java或C#背景概念完全通用只是客户端API略有差异。安装依赖pip install pika连接RabbitMQ时需要填写以下参数host服务端IP本地写localhost即可port5672这是AMQP协议默认端口virtual_host指定要连接的Virtual Host默认是/credentials账号密码用之前我们创建的账号我强烈建议连接时明确指定virtual_host而不是依赖默认值。因为默认/虚拟主机里全是guest账号的资源如果你用自己的账号去操作会发现根本看不到任何队列这也是新手会困惑的点明明连上去了为什么管理界面有队列代码里却消费不到大概率是Virtual Host不在一个频道里。4.2 生产者代码发送消息到指定交换机下面这个例子我用Topic交换机声明一个名为order_exchange的交换机然后发送一条路由键为order.created的消息import pika # 建立连接 connection pika.BlockingConnection( pika.ConnectionParameters( hostlocalhost, port5672, virtual_host/order, credentialspika.PlainCredentials(admin, admin123) ) ) channel connection.channel() # 声明交换机 channel.exchange_declare( exchangeorder_exchange, exchange_typetopic, durableTrue ) # 发送消息 channel.basic_publish( exchangeorder_exchange, routing_keyorder.created, bodyorder_no_20240901, propertiespika.BasicProperties( delivery_mode2, # 消息持久化 content_typetext/plain ) ) print(消息已发送) connection.close()这里有几个细节我解释一下。exchange_declare里的durableTrue表示交换机是持久的服务重启后交换机会自动恢复。basic_publish里的delivery_mode2表示消息要持久化RabbitMQ会把消息落盘服务重启后不丢。这两者的区别是durable管的是交换机本身存在与否delivery_mode管的是消息是否写磁盘不要混为一谈。routing_key直接决定了消息被哪些队列接收。你现在只是发了消息还没创建任何队列所以消息会直接丢弃。这是正常现象RabbitMQ不会为没被队列匹配的消息做持久等待除非你配置了死信或者延迟队列。4.3 消费者代码订阅队列并处理消息消费者的代码比生产者复杂一点因为你要处理“订阅、回调、确认”三个环节import pika connection pika.BlockingConnection( pika.ConnectionParameters( hostlocalhost, port5672, virtual_host/order, credentialspika.PlainCredentials(admin, admin123) ) ) channel connection.channel() # 声明交换机保持与生产者一致 channel.exchange_declare( exchangeorder_exchange, exchange_typetopic, durableTrue ) # 声明队列 channel.queue_declare(queueorder_queue, durableTrue) # 绑定队列到交换机 channel.queue_bind( queueorder_queue, exchangeorder_exchange, routing_keyorder.* ) def callback(ch, method, properties, body): print(f收到消息: {body.decode()}) # 手动确认消息已处理 ch.basic_ack(delivery_tagmethod.delivery_tag) channel.basic_consume( queueorder_queue, on_message_callbackcallback ) print(开始消费按CtrlC退出) channel.start_consuming()消费者要记住三步声明队列、绑定交换机、启动消费。有人只声明队列忘了绑定结果消息一直进不到队列里管理界面上什么都看不到。绑定时的routing_keyorder.*表示这个队列接收所有以order.开头且后面只有一个单词的消息。重点说下basic_ack。这里的回调函数里我调用了ch.basic_ack(delivery_tagmethod.delivery_tag)这表示“这条消息我已经处理完了”。如果你不调用basic_ack而直接退出程序RabbitMQ看到消息没有被确认会在连接关闭后把消息重新投递给其他消费者。这就是消息不丢的最后一层保障。如果你把basic_consume的auto_ack参数设置为True那就表示“收到消息就算处理完不需要确认”。这个参数看着省事实际上隐患很大。消息还在处理中程序崩了这条消息就彻底丢了因为RabbitMQ已经认为它被成功消费。生产环境我强烈建议用False加手动basic_ack宁可在重启时让部分消息重新处理一次也不能让消息无声无息地消失。5. 不丢消息的可靠性配置三层保障缺一不可5.1 持久化、生产者Confirm与消费者ACK消息从生产到消费要经过三个环节每个环节都会有丢消息的风险。RabbitMQ的可靠性不是靠某一个开关而是三层各自防护环节风险解决手段参数/配置生产者发送消息发出后服务器没收到生产者确认Confirmpublisher_confirms消息存储服务器重启后消息丢失交换机、队列、消息持久化durableTruedelivery_mode2消费者处理消费者收到后未处理完就崩溃手动ACK确认后删除basic_ackauto_ackFalse生产者确认这个点很多新手会忽略。basic_publish本身只能保证“你能把消息发给RabbitMQ”并不能保证RabbitMQ已经把消息成功写入队列。开启Confirm模式的做法是channel.confirm_delivery()开启之后basic_publish会等待服务端返回确认。如果发送后一段时间内没有确认你可以选择重新发送。在数据一致性要求高的场景这一层必须开启。我见过不少项目消费端ACK做得很完善但生产端完全没做确认机制消息在网络抖动中丢了也毫无感知。三层里任何一层漏掉都不能说你的消息链路是可靠的。5.2 手动ACK误用最常见的数据丢失原因手动ACK看似简单实际使用中误用场景非常多。我举几个真实案例。第一个案例用auto_ackTrue图省事。有一个做日志采集系统的朋友消费端每收一条消息就把日志写入数据库刚开始数据量小一切正常。后来某天服务器宕机重启后数据库里缺了一大段日志。查了很久才发现消息在RabbitMQ里也没了因为自动确认模式下消息一到消费者手里RabbitMQ就删了消费者还没来得及入库。这就是典型的没有手动ACK导致的数据流失。第二个案例手动ACK放在了业务逻辑之前。回调函数里先调用了basic_ack然后再去处理业务。结果处理业务时抛异常消息已经确认删除了错误处理逻辑只能记日志消息本身就是有问题的。正确的做法是先执行完整的业务处理确认没有异常后再执行basic_ack如果处理失败调用basic_nack并设置requeueTrue让消息重新投递。第三个案例basic_qos没设置导致消息全部一股脑推给一个消费者。RabbitMQ默认会先把所有消息派发给当前空闲的消费者如果消费速度跟不上生产速度消息会堆积在消费者的内存缓冲区里导致内存飙升。解决方案是在basic_consume之前调用channel.basic_qos(prefetch_count1)这表示一次最多推送一条消息给消费者处理完并确认后才投递下一条。这样虽然略微降低了吞吐但显著提高了稳定性尤其适合那种单条消息处理耗时较长的场景。可靠性的精髓说穿了就一句话不要相信任何默认配置。每个环节的默认行为都是为了省算力而不是为了保数据。你把每一层的参数打开之后消息才真正做到了“不丢”。6. 选型视角RabbitMQ和Kafka、RocketMQ怎么选6.1 三种消息中间件的核心差异最近几年消息中间件的选择越来越让人头疼Kafka、RocketMQ各有粉丝问社区也是公说公有理。我自己的判断标准很简单先看你的消息是“任务”还是“数据流”。对比项RabbitMQKafkaRocketMQ吞吐量中高单机万级到十万级极高单机百万级高单机十万到百万级路由灵活性四种交换机路由能力最强仅按Topic分区支持Tag过滤比Kafka灵活消费模式队列消息消费即删除日志流按offset顺序消费队列 顺序消费顺序性不做强保证需额外设计分区内严格顺序队列内严格顺序重试与死信内置死信和延迟队列生态完善需自研或引入额外系统内置重试与死信社区生态与语言几乎所有语言都有成熟客户端大数据生态最丰富Java系为主如果业务核心是“系统集成”比如订单系统要通知库存系统、支付回调要触发多个下游服务我建议选RabbitMQ。它的路由模型最灵活Direct、Topic、Fanout各管一摊用起来非常顺手。而且RabbitMQ的延迟消息和死信队列是内置的实现“超时未支付自动关单”这种需求改动量很小。如果核心是高吞吐日志或事件流比如用户行为采集、服务器日志聚合、指标监控Kafka是更合适的方案。它的顺序读写和批量传输特性天生就是为大规模数据流设计的消息保留时间还可以按天配置。只是在系统集成这种低吞吐、高路由灵活性的场景里Kafka反而显得笨重。RocketMQ介于两者之间。它在电商、金融等业务场景里表现最稳事务消息、定时消息、消息轨迹都是开箱即用而且单队列内保证顺序适合对一致性和顺序都有要求的场景。Rediss前置缓存、本地消息表这些“分布式事务”方案也经常和RocketMQ搭配出现。6.2 RabbitMQ的一些新变化值得留意RabbitMQ 3.8以后推出的Quorum Queue仲裁队列值得说一下。传统的镜像队列在高并发下有性能瓶颈仲裁队列用Raft协议实现集群环境下天然支持高可用不会因为单个节点故障导致队列不可用。如果你要用RabbitMQ支撑生产级业务我建议新项目一律用仲裁队列不再使用经典镜像队列。另外我多说一句针对小团队的建议不要一上来就追求Kafka的吞吐量。大多数项目的消息量其实只有每秒几百条RabbitMQ完全够用而且运维成本比Kafka低得多。我见过不止一个团队为了“面向未来”上Kafka结果一个Topic变更折腾半天消费组调优又花了一个月业务早就变了。选型不是一个纯技术问题它包含团队熟悉度和运维成本这些软性因素。最后分享一点个人体会。消息中间件这东西看着简单用起来全是细节。我在实际使用中总结的优先级是先把权限模型和Virtual Host搞清楚再考虑消息可靠性最后才是追求性能。顺序不能反我见过太多团队把性能调参玩出花结果权限漏配置某个消费者连不到队列数据丢了一晚上才发现。你把这几个基础问题处理干净RabbitMQ基本不会给你惹什么麻烦。
返回列表