【RabbitMQ】消息可靠性保障
本章目标
掌握生产者确认(Publisher Confirms)机制,确保消息到达Broker。
深入理解消费者确认(Consumer Acknowledgments)的最佳实践。
学习死信队列(Dead Letter Exchange, DLX)处理失败消息。
实现完整的消息可靠性保障体系。
一、理论部分
1. 消息传递的生命周期与可靠性挑战
在分布式系统中,消息可能在任何环节丢失:
生产者 -> Broker:网络故障、Broker崩溃
Broker内部:服务器宕机、队列未持久化
Broker -> 消费者:消费者处理失败、连接中断
2. 生产者确认(Publisher Confirms)
这是RabbitMQ提供的一种生产者端的可靠性机制。当生产者启用确认模式后,Broker会异步通知生产者消息是否已经成功处理。
事务(Transactions):AMQP协议支持事务,但性能较差(同步,吞吐量降低约200-300倍)。
发布者确认(Publisher Confirms):性能更好的异步替代方案,是生产环境推荐的方式。
确认的两种结果:
ACK:消息已被Broker成功接收和处理(持久化到磁盘)。
NACK:消息未被Broker处理(通常由于内部错误)。
3. 消费者确认(Consumer Acknowledgments)
我们在第3章已经接触过,本章将深入探讨:
自动确认(autoAck: true):消息一送达就确认,风险高。
手动确认(autoAck: false):
BasicAck
:成功处理,消息从队列删除。BasicNack
:处理失败,可以要求重新入队或丢弃。BasicReject
:同BasicNack
,但不支持批量操作。
4. 死信队列(Dead Letter Exchange, DLX)
当消息遇到以下情况时,会成为"死信":
消息被消费者
basic.reject
或basic.nack
且requeue = false
消息因TTL(Time-To-Live)过期
队列达到最大长度限制
死信消息会被重新发布到配置的DLX,然后根据DLX的类型路由到死信队列。
5. 完整的可靠性保障体系
生产级应用需要多层次的保障:
生产者确认:确保消息到达Broker
消息持久化:队列持久化 + 消息持久化
消费者确认:确保消息被成功处理
死信队列:处理无法正常消费的消息
监控与告警:及时发现和处理问题
二、实操部分:构建完整的可靠消息系统
我们将构建一个包含完整可靠性保障的订单处理系统。
第1步:创建项目结构
创建新解决方案,包含以下项目:
ReliableProducer
- 支持确认的生产者ReliableConsumer
- 支持手动确认和死信处理的消费者DeadLetterProcessor
- 死信消息处理器
为所有项目添加
RabbitMQ.Client
NuGet包。
第2步:实现可靠生产者(ReliableProducer.cs)
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "localhost",
UserName = "myuser",
Password = "mypassword"
}; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
// 启用发布者确认模式
channel.ConfirmSelect(); // 声明持久化队列
channel.QueueDeclare(queue: "reliable_orders",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null); // 设置确认事件处理器
channel.BasicAcks += (sender, ea) =>
{
Console.WriteLine($" [✓] Message {ea.DeliveryTag} confirmed by broker");
}; channel.BasicNacks += (sender, ea) =>
{
Console.WriteLine($" [✗] Message {ea.DeliveryTag} not confirmed by broker");
// 在实际应用中,这里应该实现重试逻辑
}; for (int i = 1; i <= 10; i++)
{
var message = $"Order #{i} - Product XYZ";
var body = Encoding.UTF8.GetBytes(message); // 设置消息为持久化
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
properties.MessageId = Guid.NewGuid().ToString(); // 发布消息
channel.BasicPublish(exchange: "",
routingKey: "reliable_orders",
basicProperties: properties,
body: body); Console.WriteLine($" [x] Sent {message}"); // 等待确认(在实际应用中可能使用异步方式)
if (channel.WaitForConfirms(TimeSpan.FromSeconds(5)))
{
Console.WriteLine($" [✓] Message {i} confirmed");
}
else
{
Console.WriteLine($" [✗] Message {i} confirmation timeout");
// 实现重试逻辑
} Thread.Sleep(1000); // 模拟消息间隔
}
} Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
第3步:配置死信交换机和队列
在实际应用中,我们通常在生产者和消费者中都声明所需的交换机和队列。这里我们在消费者中配置完整的死信机制。
第4步:实现可靠消费者(ReliableConsumer.cs)
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "localhost",
UserName = "myuser",
Password = "mypassword"
}; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
// 1. 声明死信交换机
channel.ExchangeDeclare("dlx", ExchangeType.Direct, durable: true); // 2. 声明死信队列
channel.QueueDeclare("dead_letter_queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null); // 3. 绑定死信队列到死信交换机
channel.QueueBind("dead_letter_queue", "dlx", "dead_letter"); // 4. 声明主队列,并配置死信参数
var arguments = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "dlx" }, // 指定死信交换机
{ "x-dead-letter-routing-key", "dead_letter" } // 死信路由键
}; channel.QueueDeclare(queue: "reliable_orders",
durable: true,
exclusive: false,
autoDelete: false,
arguments: arguments); // 设置公平分发
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); Console.WriteLine(" [*] Waiting for orders. To exit press CTRL+C"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body); Console.WriteLine($" [x] Received {message}"); try
{
// 模拟业务处理
ProcessOrder(message, ea.DeliveryTag); // 处理成功,手动确认
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
Console.WriteLine($" [✓] Order processed successfully: {ea.DeliveryTag}");
}
catch (Exception ex)
{
Console.WriteLine($" [✗] Failed to process order {ea.DeliveryTag}: {ex.Message}"); // 处理失败,拒绝消息并不重新入队(发送到死信队列)
channel.BasicNack(deliveryTag: ea.DeliveryTag,
multiple: false,
requeue: false);
}
}; channel.BasicConsume(queue: "reliable_orders",
autoAck: false, // 手动确认模式
consumer: consumer); Console.ReadLine();
} void ProcessOrder(string message, ulong deliveryTag)
{
// 模拟业务逻辑 - 随机失败以测试可靠性机制
var random = new Random(); // 模拟10%的失败率
if (random.Next(0, 10) == 0)
{
throw new Exception("Simulated processing failure");
} // 模拟处理时间
Thread.Sleep(2000);
Console.WriteLine($" Processing order {deliveryTag}: {message}");
}
第5步:实现死信处理器(DeadLetterProcessor.cs)
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "localhost",
UserName = "myuser",
Password = "mypassword"
}; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
// 声明死信队列(确保存在)
channel.QueueDeclare("dead_letter_queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null); Console.WriteLine(" [*] Waiting for dead letters. To exit press CTRL+C"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body); var originalQueue = ea.BasicProperties.Headers?["x-first-death-queue"]?.ToString(); Console.WriteLine($" [DEAD LETTER] Received failed message:");
Console.WriteLine($" Original Queue: {originalQueue}");
Console.WriteLine($" Message: {message}");
Console.WriteLine($" Routing Key: {ea.RoutingKey}");
Console.WriteLine($" Delivery Tag: {ea.DeliveryTag}"); // 在实际应用中,这里可以实现:
// 1. 发送告警通知
// 2. 记录到错误日志
// 3. 人工干预
// 4. 重试机制 Console.WriteLine(" -> Sending alert to administrator...");
Console.WriteLine(" -> Logging to error system..."); // 确认死信消息
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
}; channel.BasicConsume(queue: "dead_letter_queue",
autoAck: false,
consumer: consumer); Console.ReadLine();
}
第6步:高级特性 - 带重试机制的消费者
创建RetryConsumer.cs
,实现更复杂的重试逻辑:
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "localhost",
UserName = "myuser",
Password = "mypassword"
}; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
// 配置重试队列(带TTL)
var retryArguments = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "" },
{ "x-dead-letter-routing-key", "reliable_orders" },
{ "x-message-ttl", 10000 } // 10秒后重试
}; channel.QueueDeclare("retry_queue", durable: true, exclusive: false,
autoDelete: false, arguments: retryArguments); channel.QueueDeclare(queue: "reliable_orders", durable: true, exclusive: false,
autoDelete: false, arguments: null); channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); Console.WriteLine(" [*] Waiting for messages with retry support."); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body); // 检查重试次数
var retryCount = GetRetryCount(ea.BasicProperties); Console.WriteLine($" [x] Received (attempt {retryCount + 1}): {message}"); try
{
ProcessOrderWithRetry(message, retryCount);
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
Console.WriteLine($" [✓] Successfully processed");
}
catch (Exception ex)
{
Console.WriteLine($" [✗] Processing failed: {ex.Message}"); if (retryCount < 3) // 最多重试3次
{
Console.WriteLine($" [↻] Scheduling retry {retryCount + 1}"); // 发布到重试队列
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
properties.Headers = new Dictionary<string, object>
{
{ "retry-count", retryCount + 1 }
}; channel.BasicPublish("", "retry_queue", properties, body);
channel.BasicAck(ea.DeliveryTag, false); // 确认原消息
}
else
{
Console.WriteLine($" [✗] Max retries exceeded, sending to DLQ");
channel.BasicNack(ea.DeliveryTag, false, false);
}
}
}; channel.BasicConsume("reliable_orders", false, consumer);
Console.ReadLine();
} int GetRetryCount(IBasicProperties properties)
{
if (properties.Headers?.ContainsKey("retry-count") == true)
{
var retryCountBytes = (byte[])properties.Headers["retry-count"];
return BitConverter.ToInt32(retryCountBytes, 0);
}
return 0;
} void ProcessOrderWithRetry(string message, int retryCount)
{
var random = new Random(); // 模拟处理,重试次数越多成功率越高(模拟系统恢复)
var failureChance = Math.Max(10 - retryCount * 3, 1); // 降低失败率 if (random.Next(0, failureChance) == 0)
{
throw new Exception($"Simulated failure on attempt {retryCount + 1}");
} Thread.Sleep(1000);
Console.WriteLine($" Processed successfully on attempt {retryCount + 1}");
}
第7步:运行与测试
启动所有服务
# 终端1:启动死信处理器
dotnet run --project DeadLetterProcessor # 终端2:启动主消费者
dotnet run --project ReliableConsumer # 终端3:启动生产者
dotnet run --project ReliableProducer测试场景1:正常流程
观察生产者确认日志
观察消费者处理成功的日志
测试场景2:消费者处理失败
在消费者处理时强制关闭消费者进程
观察消息重新投递到其他消费者
或者观察消息进入死信队列
测试场景3:死信处理
让消费者处理失败,消息进入死信队列
观察死信处理器的告警和日志记录
测试场景4:重试机制
使用
RetryConsumer
测试重试逻辑观察消息在重试队列中的行为
第8步:监控与管理
在RabbitMQ管理界面(http://localhost:15672)监控:
队列深度和消息状态
确认率和投递率
死信队列中的消息数量
本章总结
在这一章中,我们构建了一个完整的消息可靠性保障体系:
生产者确认:使用
ConfirmSelect
和确认事件确保消息到达Broker。消息持久化:队列持久化 + 消息持久化,应对服务器重启。
消费者确认:手动确认模式,确保消息被成功处理。
死信队列:处理无法正常消费的消息,防止消息丢失。
重试机制:实现带延迟的重试逻辑,提高系统韧性。
监控告警:通过死信处理器实现错误通知。
这些机制组合使用,可以构建出生产级的可靠消息系统。在下一章,我们将学习如何将RabbitMQ与ASP.NET Core集成,构建现代化的微服务应用。
【RabbitMQ】消息可靠性保障的更多相关文章
- RabbitMQ消息可靠性分析
消息中间件的可靠性是指对消息不丢失的保障程度:而消息中间件的可用性是指无故障运行的时间百分比,通常用几个 9 来衡量.不存在绝对的可靠性只能尽量趋向完美.并且通常可靠性也意味着影响性能和付出更大的成本 ...
- Storm系列三: Storm消息可靠性保障
Storm系列三: Storm消息可靠性保障 在上一篇 Storm系列二: Storm拓扑设计 中我们已经设计了一个稍微复杂一点的拓扑. 而本篇就是在上一篇的基础上再做出一定的调整. 在这里先大概提一 ...
- RabbitMQ消息可靠性分析 - 简书
原文:RabbitMQ消息可靠性分析 - 简书 有很多人问过我这么一类问题:RabbitMQ如何确保消息可靠?很多时候,笔者的回答都是:说来话长的事情何来长话短说.的确,要确保消息可靠不只是单单几句就 ...
- RabbitMQ消息可靠性分析和应用
RabbitMQ流程简介(带Exchange) RabbitMQ使用一些机制来保证可靠性,如持久化.消费确认及发布确认等. 先看以下这个图: P为生产者,X为中转站(Exchange),红色部分为消息 ...
- [转载]RabbitMQ消息可靠性分析
有很多人问过我这么一类问题:RabbitMQ如何确保消息可靠?很多时候,笔者的回答都是:说来话长的事情何来长话短说.的确,要确保消息可靠不只是单单几句就能够叙述明白的,包括Kafka也是如此.可靠并不 ...
- RabbitMQ消息可靠性传输
消息的可靠性投递是使用消息中间件不可避免的问题,不管是使用kafka.rocketMQ或者rabbitMQ,那么在RabbitMQ中如何保证消息的可靠性投递呢? 先再看一下RabbitMQ消息传递的流 ...
- RabbitMQ消息可靠性、死信交换机、消息堆积问题
目录 消息可靠性 生产者消息确认 示例 消费者消息确认 示例 死信交换机 例子 高可用问题 消息堆积问题 惰性队列 参考 消息可靠性 确保消息至少被消费了一次(不丢失) 消息丢失的几种情况: 消息在网 ...
- RabbitMQ消息可靠性
那些情况会失败 网络问题有很多原因出发失败.防火墙也可能会中断Idle连接,网络失败不是很快确定的. 硬件和软件也会导致系统崩溃.客户端软件保持运行,而逻辑错误也可能会导致channel和connec ...
- 消息中间件-RabbitMQ消息可靠性和插件化机制
package com.study.rabbitmq.a132.confirm; import com.rabbitmq.client.*; import java.io.IOException; i ...
- 解决RabbitMQ消息丢失问题和保证消息可靠性(一)
原文链接(作者一个人):https://juejin.im/post/5d468591f265da03b810427e 工作中经常用到消息中间件来解决系统间的解耦问题或者高并发消峰问题,但是消息的可靠 ...
随机推荐
- Java开发笔记(一百五十四)StringBuffer和StringBuilder的由来
本来String类已经能够完成字符串操作的所有功能,为何Java又提供了专门的StringBuffer和StringBuilder呢?这要从String类的设计说起了,查看String的源码,发现其内 ...
- 密度泛函理论(DFT)简介
密度泛函理论(DFT)简介 密度泛函理论(Density Functional Theory,DFT)是一种现代量子力学计算方法,广泛应用于原子.分子和固体材料的电子结构研究. DFT 的基本思想: ...
- leetcode 468 验证IP地址
简介 使用优雅的正则表达式 5种情况 情况: 块只包含一位数字,范围是 0 到 9. 块包含两位数字,第一位的范围是 1 到 9,第二位是 0 到 9. 块包含三位数字,且第一位为 1.第二.三位可以 ...
- 在服务器上安装 gitlab
简介 RT 核心参考链接 https://about.gitlab.com/install/#ubuntu?version=ce https://www.cnblogs.com/zhujingzhi/ ...
- Android List数组列表自定义排序
自定义排序 例如:根据文件的最后修改时间进行排序,最新文件在前 Collections.sort(lstFiles, new Comparator<FileListData>() { @O ...
- POLIR-Economics-Financial Management: 财务管理学: 财务管理的特点,财务管理的目标,财务管理的原则
Abbreviations Investor: 投资人.场景: 企业开放并接受投资,投资人主动调研并投资,直接投资. Creditor: 债权人.场景: 企业发行债券,主动融资,债权人认可并购买,间接 ...
- SciTech-Mathmatics-Physics-Particle Physics-Election+Photon+Quantum: Parallel Universe + Superposition + Wave-Particle Duality.
SciTech-Mathmatics-Quantum LaTex: https://tex.stackexchange.com/questions/483996/automatically-sized ...
- pandas速成笔记(4)-数据图表
接上篇继续,做数据分析,各种数据图表是必不可少的,还是以下面这张表为例: 一.单列柱状图 假设要把9月份,A.B这2个分类的Amount提取出来画一个柱状图,可以这么做: import pandas ...
- SpringBoot--如何创建自己的自动配置
在实际开发中,仅靠SpringBoot的自动配置是远远不够的,比如要访问多个数据源,自动配置就完全无能为力了. 自动配置的本质 本质就是在容器中预配置要整合的框架所需的基础Bean. 以MyBatis ...
- Win10纯净版无法打开图片提示错误的问题
有一位深度系统的win10纯净版用户,在打开一些图片无法正常的显示出来,而是显示了黑色的界面.遇到这个问题我们会无法预览图片的具体内容,影响非常大.那么接下来我们就来看看深度技术小编如何解决这个问题的 ...