一、Demo场景
基于 RabbitMQ 实现带死信队列的消息收发场景,生产者发送消息至 Direct 交换机,普通消费者处理消息(50% 失败率),失败 / 超时消息自动进入死信队列,由死信消费者兜底处理,避免消息丢失或阻塞。
二、代码思路
- 封装连接管理器统一管理 MQ 连接;
- 生产者声明交换机并发送持久化 JSON 消息;
- 消费者分普通 / 死信类型,普通队列绑定死信参数,消费失败则转发至死信队列处理。
三、代码效果
生产者循环发送 5 条测试消息,普通消费者随机处理成败,失败 / 超时消息进入死信队列;死信消费者接收并移除死信消息,完成消息全链路兜底处理。
四、代码
cs
using ConsoleApp1.Model;
using DocumentFormat.OpenXml.Wordprocessing;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace CloneDemo
{
// 客户端代码
class Program
{
/// <summary>
/// MQ管理者:配置url、账密
/// </summary>
public static class RabbitMQConnectionManager
{
private static readonly Lazy<Task<IConnection>> _connection = new Lazy<Task<IConnection>>(() =>
{
var factory = new ConnectionFactory()
{
HostName = "localhost",
UserName = "consumer",//账号
Password = "consumer",//密码
AutomaticRecoveryEnabled = true, // 自动重连
NetworkRecoveryInterval = TimeSpan.FromSeconds(5)
};
return factory.CreateConnectionAsync();
});
public static Task<IConnection> Connection => _connection.Value;
public static async Task<IChannel> CreateChannelAsync()
{
var connection = await Connection;
return await connection.CreateChannelAsync();
}
}
/// <summary>
/// MQ生产者:配置交换机、路由键
/// </summary>
public class RabbitMQProducerService
{
private readonly string _exchangeName = "mydirect";
private readonly string _routingKey = "myroot";
/// <summary>
/// 构造函数
/// </summary>
/// <param name="exchangeName"></param>
/// <param name="routingKey"></param>
public RabbitMQProducerService(string exchangeName, string routingKey)
{
_exchangeName = exchangeName;
_routingKey = routingKey;
}
/// <summary>
/// 发送消息
/// </summary>
public async Task<bool> SendMessage<T>(T input)
{
// 01 建立队列连接
var channel = await RabbitMQConnectionManager.CreateChannelAsync();
try
{
// 02 声明交换机(Direct类型,适用于路由器模式)
await channel.ExchangeDeclareAsync(exchange: _exchangeName, durable: true, type: ExchangeType.Direct);
// 03 发送内容到交换机
var message = JsonConvert.SerializeObject(input);
var body = Encoding.UTF8.GetBytes(message);
// 4.将消息路由给消费者
await channel.BasicPublishAsync(
exchange: _exchangeName, // 交换机名称
routingKey: _routingKey, // 路由键
mandatory: false, // 非强制路由(无匹配队列则丢弃)
basicProperties: new BasicProperties
{
DeliveryMode = DeliveryModes.Persistent, // 消息持久化
ContentType = "application/json" // 标识JSON格式
}, // 消息属性
body: body.AsMemory());
Console.WriteLine($"生产者发送了: {message}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"生产者异常:{ex.ToString()}");
return false;
}
finally
{
if (channel != null && channel.IsOpen)
{
await channel.CloseAsync();
await channel.DisposeAsync();
}
}
}
}
/// <summary>
/// MQ消费者:配置交换机(同生产者一样)、路由键(同生产者一样)、队列名称(自由命名)、处理委托
/// </summary>
public class RabbitMQConsumerService<T>
{
private readonly string _exchangeName = "mydirect";
private readonly string _routingKey = "myroot";
private readonly string _qName = "myqname";
/// <summary>
/// 业务处理委托:入参为消费的消息,返回true表示处理成功(会Ack移除消息),false表示处理失败
/// </summary>
private readonly Func<T, bool> _processMethod = null;
/// <summary>
/// 是否为死信队列消费者,默认为false(业务消费者)
/// </summary>
private readonly bool _isDLX = false;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="exchangeName">交换机名称(同生产者一样)</param>
/// <param name="routingKey">路由键名称(同生产者一样)</param>
/// <param name="qName">队列名称(自由命名)</param>
/// <param name="processMethod">处理的方法</param>
/// <param name="isDLX">当前是否是死信队列的消费者(默认false,死信队列是各组件后面加_dlx后缀)</param>
public RabbitMQConsumerService(string exchangeName, string routingKey, string qName, Func<T, bool> processMethod, bool isDLX = false)
{
_isDLX = isDLX;
string suffix = isDLX ? "_dlx" : string.Empty;//后缀
_exchangeName = exchangeName + suffix;
_routingKey = routingKey + suffix;
_qName = qName + suffix;
_processMethod = processMethod;
}
/// <summary>
/// 开启消费
/// </summary>
public async Task StartConsuming()
{
// 01 建立队列连接
var channel = await RabbitMQConnectionManager.CreateChannelAsync();
try
{
// 02 死信队列的声明
Dictionary<string, object> queueArgs = new Dictionary<string, object>();
if (!_isDLX)//如果不是死信队列消费者
{
queueArgs = await DeclearDLXAsync(channel);
}
// 03 声明交换机(Direct类型,适用于路由模式,必须与生产者相同)
await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Direct, durable: true);
// 04 声明一个独立的队列,用于接收消息
await channel.QueueDeclareAsync(_qName, true, false, false, queueArgs);
// 05 绑定队列到交换机
await channel.QueueBindAsync(queue: _qName, exchange: _exchangeName, routingKey: _routingKey);
// 06 消费的逻辑方法
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.ReceivedAsync += async (model, ea) =>
{
try
{
// 获取入参
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
// 委托处理
var success = _processMethod(JsonConvert.DeserializeObject<T>(message));
// 根据结果进行是否确认
if (success)
{
await channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false);//成功了则移除消息
}
else
{
await channel.BasicNackAsync(deliveryTag: ea.DeliveryTag, multiple: false, requeue: false);//普通消费者→消息移入死信队列;死信消费者→消息直接移除(无后续转移)
}
}
catch (Exception ex)
{
Console.WriteLine($"消费者出错:{ex.ToString()}");
await channel.BasicNackAsync(deliveryTag: ea.DeliveryTag, multiple: false, requeue: false);
}
};
// 07 启动消费
await channel.BasicConsumeAsync(queue: _qName,
autoAck: false,//禁止使用消息自动确认
consumer: consumer);
Console.WriteLine("点击退出消费者");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine($"消费者出错:{ex.ToString()}");
}
}
/// <summary>
/// 死信队列声明
/// </summary>
private async Task<Dictionary<string, object>> DeclearDLXAsync(IChannel channel)
{
string dlxExchangeName = $"{_exchangeName}_dlx"; // 死信交换机:业务交换机+_dlx
string dlxRoutingKey = $"{_routingKey}_dlx"; // 死信路由键:业务路由键+_dlx
string dlxQueueName = $"{_qName}_dlx"; // 死信队列:业务队列+_dlx
await channel.ExchangeDeclareAsync(exchange: dlxExchangeName, type: ExchangeType.Direct, durable: true);
await channel.QueueDeclareAsync(dlxQueueName, true, false, false);
await channel.QueueBindAsync(queue: dlxQueueName, exchange: dlxExchangeName, routingKey: dlxRoutingKey);
var queueArgs = new Dictionary<string, object>
{
["x-dead-letter-exchange"] = dlxExchangeName, // 死信转发到的交换机
["x-dead-letter-routing-key"] = dlxRoutingKey, // 死信使用的路由键
["x-message-ttl"] = 60000 // 业务队列消息超时时间:60秒未被消费的消息,自动移入死信队列
};
return queueArgs;
}
}
/// <summary>
/// 主函数:注意要先部署消费者绑定好队列,再部署生产者才能正确路由到指定消费者
/// </summary>
static async Task Main(string[] args)
{
// 以下1、2、3请在不同的exe进程里面启动!
// 1. 生产者发送测试消息
for (int i = 0; i < 5; i++)
{
var producer = new RabbitMQProducerService("mydirect", "myroot");
await producer.SendMessage(new { Id = i, Content = "测试死信消息" });
}
// 2. 启动普通消费者(模拟处理失败,消息进死信)
var normalConsumer = new RabbitMQConsumerService<dynamic>(
exchangeName: "mydirect",
routingKey: "myroot",
qName: "myqname",
processMethod: msg =>
{
var issucess = new Random().Next(0, 100) >= 50;// 50%概率处理成功
Console.WriteLine($"普通消费者处理消息:处理的结果是{issucess}");
return issucess; // 返回false,消息进入死信队列
},
isDLX: false);
await normalConsumer.StartConsuming();
// 3. 启动死信消费者(处理死信消息)
var dlxConsumer = new RabbitMQConsumerService<dynamic>(
exchangeName: "mydirect",
routingKey: "myroot",
qName: "myqname",
processMethod: msg =>
{
Console.WriteLine($"死信消费者处理消息:{msg} ");
return true; // 【重要】为避免阻塞队列,死信队列强烈建议返回true,即无论处理是否成功,都将从死信队列中移除
},
isDLX: true);
await dlxConsumer.StartConsuming();
}
}
}