NET 8 使用 rabbitMQ

RabbitMQ.Client 7.2 推荐使用异步

var connection = factory.CreateConnection();
var channel = connection.CreateModel();

//替换为下面
using var connection = await factory.CreateConnectionAsync();
using var channel = await connection.CreateChannelAsync();
教程主题 核心功能 适用场景 关键区别
1. "Hello World!" 最简单的点对点通信:生产者发送消息,单个消费者接收消息。 入门演示、简单服务间单对单消息传递。 最基础的消息队列模型,仅展示 “生产 - 消费” 的最小闭环。
2. Work Queues 工作队列(任务队列):多个消费者竞争消费队列中的消息,实现任务分发与负载均衡。 耗时任务的并行处理(如图片处理、数据计算),确保任务不重复执行。 引入 “竞争消费” 和 “消息确认” 机制,解决多消费者的任务分配问题。
3. Publish/Subscribe 发布 / 订阅:生产者将消息发送到交换机,所有绑定该交换机的队列都会收到消息(扇形分发)。 消息广播场景(如系统通知、日志收集),需多消费者同时接收同一份消息。 基于 “交换机 - 队列绑定” 实现消息扇出,是 “一对多” 通信的基础模型。
4. Routing 路由模式:生产者通过指定 “路由键(Routing Key)”,让交换机将消息投递到匹配路由键的队列。 按规则过滤消息的场景(如 “错误日志仅发送到告警队列, info 日志仅发送到统计队列”)。 引入 “路由键” 实现消息的精准路由,是 “按规则分发” 的基础模型。
5. Topics 主题模式:基于 “通配符路由键” 实现更灵活的消息过滤(如*.order.*匹配所有订单相关消息)。 复杂消息分类场景(如按 “业务类型 + 操作类型” 过滤消息,支持模糊匹配)。 路由键支持通配符(*匹配单个词,#匹配多个词),是 Routing 模式的灵活扩展。
6. RPC 远程过程调用:通过消息队列实现同步的 “请求 - 响应” 通信(如客户端调用服务端方法并等待返回)。 需要同步获取执行结果的场景(如分布式系统中的服务调用)。 引入 “回调队列” 和 “ correlation ID”,将异步队列转化为同步 RPC 通信。
7. Publisher Confirms 生产者确认:确保生产者发送的消息被 RabbitMQ 正确接收(包含普通确认、批量确认等机制)。 对消息可靠性要求极高的场景(如金融交易、订单系统),需保证消息不丢失。 属于 “可靠性机制”,而非通信模式,用于增强生产者侧的消息投递可靠性。

1 简单点对点 - Hello word

生产者

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace codeMan.rabbitMQ.Producer.Producer
{
    public class HelloSender
    {

        public async static Task HelloSend()
        {
            //创建连接工厂
            var factory = new ConnectionFactory()
            {
                UserName = "admin",//用户名
                Password = "admin123",//密码
                HostName = "192.168.49.151",//rabbitmq ip
            };

            using var connection = await factory.CreateConnectionAsync();
            using var channel = await connection.CreateChannelAsync();

            /****
            用于声明(创建)一个队列:
                queue: "hello" - 队列名称
                durable: false - 队列不持久化(重启后丢失)
                exclusive: false - 不是排他队列(允许多个消费者访问)
                autoDelete: false - 消费者断开连接后不自动删除队列
                arguments: null - 没有额外的队列参数
             */
            await channel.QueueDeclareAsync(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null);

            const string message = "Hello World!";
            var body = Encoding.UTF8.GetBytes(message);

            /*
             * 发送消息
             *
             * 参数:
             * exchange - 交换器名称,如果没有指定交换器,则使用默认的交换器
             * routingKey - 路由键,如果没有指定路由键,则使用队列名称
             * basicProperties - 消息属性
             * body - 消息内容
             */
            await channel.BasicPublishAsync(exchange: string.Empty, routingKey: "hello", body: body);
            Console.WriteLine($" [x] Sent {message}");

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }
}

image

消费者

  1. 创建连接工厂并配置连接信息(用户名、密码、主机IP)
  2. 建立到RabbitMQ服务器的连接和通道(channel)
  3. 声明队列(确保队列存在)
  4. 创建消费者对象(AsyncEventingBasicConsumer)
  5. 注册消息接收事件处理程序
  6. 开始消费队列中的消息
  7. 等待用户输入以保持程序运行
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace codeMan.rabbitMQ.Consumer01.Consumer
{
    public class HelloReceive
    {
        public async static Task HelloRecevie()
        {
            //1 创建连接工厂
            var factory = new ConnectionFactory()
            {
                UserName = "admin",//用户名
                Password = "admin123",//密码
                HostName = "192.168.49.151",//rabbitmq ip
            };

            //2 建立到RabbitMQ服务器的连接和通道(channel)
            using var connection = await factory.CreateConnectionAsync();
            using var channel = await connection.CreateChannelAsync();
            
            // 3. 声明队列确保其存在
            /****
            用于声明(创建)一个队列:
                queue: "hello" - 队列名称
                durable: false - 队列不持久化(重启后丢失)
                exclusive: false - 不是排他队列(允许多个消费者访问)
                autoDelete: false - 消费者断开连接后不自动删除队列
                arguments: null - 没有额外的队列参数
             */
            await channel.QueueDeclareAsync(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null);

            Console.WriteLine(" [*] Waiting for messages.");
            
             // 4. 创建消费者实例
            /*
             * 用于创建消费者:
             * channel - 频道
             * autoAck - 自动确认(false为手动)
             * consumer - 消费者
             */
            var consumer = new AsyncEventingBasicConsumer(channel);

            // 5. 注册消息接收事件处理程序
            /*
             * 用于接收消息:
             * consumer - 消费者
             * ReceivedAsync - 用于接收消息的异步方法
             */
            consumer.ReceivedAsync += (model, ea) =>
            {
                // 6. 处理接收到的消息
                var body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                Console.WriteLine($" [x] Received {message}");
                return Task.CompletedTask;
            };

             // 7. 开始消费队列中的消息
            /***
             * 用于开始接收消息:
             * "hello" - queue: 要消费的队列名称
             * autoAck: true - 是否自动确认消息:
                        true:消息一旦投递给消费者就自动确认,RabbitMQ会将其从队列中删除
                        false:需要手动调用确认方法(如BasicAck),否则消息会在消费者断开后重新入队
             * consumer: consumer - 消费者对象,定义了如何处理接收到的消息
             */
            await channel.BasicConsumeAsync("hello", autoAck: true, consumer: consumer);

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }
}

2 Work Queues:任务分发与负载均衡

Work Queues 是 RabbitMQ 实现任务分发与负载均衡的核心机制,通过引入多消费者协作模式,显著提升了消息处理的效率与可靠性。相较于基础的 Hello World 模型,其核心改进体现在任务分发策略优化、消息可靠性保障及负载均衡能力三个维度。

任务分发策略对比

Work Queues 支持两种主要分发模式:轮询分发公平分发。轮询模式下,RabbitMQ 将消息平均分配给消费者,不考虑实际处理能力差异,可能导致资源利用率失衡;公平分发则通过 BasicQos 方法限制消费者未确认消息数量(prefetchCount 参数),确保处理速度快的消费者获得更多任务。

关键参数解析:prefetchCount = 1 表示消费者在确认当前消息前,不会接收新消息,实现按处理能力动态分配任务,避免消息堆积。

消息可靠性保障机制

为防止服务重启或崩溃导致消息丢失,Work Queues 引入双重保障:

  1. 队列持久化:声明队列时设置 durable = true,确保队列元数据不丢失

  2. 消息持久化:发布消息时指定 Persistent = true,将消息写入磁盘

  3. 手动确认机制:消费者通过 BasicAck 显式确认消息处理完成,避免任务意外中断时消息丢失

实现要点:持久化配置需同时应用于队列与消息,单独设置队列持久化无法保障消息存活;手动确认需在业务逻辑执行成功后调用,避免过早确认导致数据不一致。

C# 实现核心代码片段

生产者需设置消息持久化属性:

var properties = channel.CreateBasicProperties();
properties.Persistent = true; // 消息持久化
channel.BasicPublish(exchange: "", routingKey: "task_queue", basicProperties: properties, body: body);

消费者需配置 BasicQos 与手动确认:

/* 
 * 用于设置队列的消费者数量限制:
 * prefetchSize: 0 - 不限制消息大小
 * prefetchCount: 1 - 每次只处理一条消息
 * global: false - 只针对当前消费者
 */
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); // 公平分发
channel.BasicConsume(queue: "task_queue", autoAck: false, consumer: consumer); // 禁用自动确认

// 处理完成后手动确认
consumer.Received += (model, ea) => {
    // 业务逻辑处理...
    channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};

通过上述机制,Work Queues 实现了从简单消息传递到可靠任务分发的跨越,为分布式系统中的负载均衡与故障恢复提供了关键支持。

image

生产者

using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace codeMan.rabbitMQ.Producer.Producer
{
    internal class WorkSender
    {
        public async static Task WorkSned()
        {
            //创建连接工厂
            var factory = new ConnectionFactory()
            {
                UserName = "admin",//用户名
                Password = "admin123",//密码
                HostName = "192.168.49.151",//rabbitmq ip
            };

            using var connection = await factory.CreateConnectionAsync();
            using var channel = await connection.CreateChannelAsync();

            /****
            用于声明(创建)一个队列:
                queue: "hello" - 队列名称
                durable: true - 队列持久化(重启后保留) 
                exclusive: false - 不是排他队列(允许多个消费者访问)
                autoDelete: false - 消费者断开连接后不自动删除队列
                arguments: null - 没有额外的队列参数
             */
            await channel.QueueDeclareAsync(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null);

            for (int i = 0; i < 1000; i++)
            {
                string message = $"乘客{i},136000000{i},已经到您了";
                var body = Encoding.UTF8.GetBytes(message);

                /**
                    用于设置消息属性:
                    persistent - 消息持久化
                 */
                var properties = new BasicProperties
                {
                    Persistent = true
                };

                /***
                用于发送消息:
                    exchange: string.Empty - 使用默认交换机
                    routingKey: "hello" - 路由键
                    mandatory: true - 如果消息无法路由到队列,则返回给发送者
                    basicProperties: properties - 消息属性
                    body: body - 消息内容
                 */
                await channel.BasicPublishAsync(exchange: string.Empty, routingKey: "task_queue", mandatory: true, basicProperties: properties, body: body);
                Console.WriteLine($" [x] Sent {message}");
            }
        }
    }
}

2个消费者同样的代码

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace codeMan.rabbitMQ.Consumer01.Consumer
{
    public class WorkReceive
    {
        public async static Task WorkRecevie()
        {
            //1 创建连接工厂
            var factory = new ConnectionFactory()
            {
                UserName = "admin",//用户名
                Password = "admin123",//密码
                HostName = "192.168.49.151",//rabbitmq ip
            };

            //2 建立到RabbitMQ服务器的连接和通道(channel)
            using var connection = await factory.CreateConnectionAsync();
            using var channel = await connection.CreateChannelAsync();

            // 3. 声明队列确保其存在
            /****
            用于声明(创建)一个队列:
                queue: "hello" - 队列名称
                durable: false - 队列不持久化(重启后丢失)
                exclusive: false - 不是排他队列(允许多个消费者访问)
                autoDelete: false - 消费者断开连接后不自动删除队列
                arguments: null - 没有额外的队列参数
             */
            await channel.QueueDeclareAsync(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null);
            
            /* 4. 设置QoS(服务质量)
             * 用于设置队列的消费者数量限制:
             * prefetchSize: 0 - 不限制消息大小
             * prefetchCount: 1 - 每次只处理一条消息
             * global: false - 只针对当前消费者
             */
            await channel.BasicQosAsync(prefetchSize: 0, prefetchCount: 1, global: false);

            Console.WriteLine(" [*] Waiting for messages.");
                /* 5. 创建消费者:
             * channel - 频道
             * autoAck - 自动确认(false为手动)
             * consumer - 消费者
             */
            var consumer = new AsyncEventingBasicConsumer(channel);
            // 6. 注册消息接收事件处理程序
            consumer.ReceivedAsync += async (model, ea) =>
            {
                // 7. 处理接收到的消息
                byte[] body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                Console.WriteLine($" [x] Received {message}");
                Console.WriteLine(" [x] Done");

                /***
                 * 用于确认消息:
                 * channel - 频道
                 * deliveryTag - 消息的标识
                 * multiple - 是否批量确认
                 */
                await channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false);
            };
            
            //8. 启动消费者
            await channel.BasicConsumeAsync("task_queue", autoAck: false, consumer: consumer);

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }
}

如果consumer2消费者性能差一点

这时候consumer2 就会处理的少

consumer.ReceivedAsync += async (model, ea) =>
{
    byte[] body = ea.Body.ToArray();
    var message = Encoding.UTF8.GetString(body);
    await Task.Delay(100);      				//性能差一点
    Console.WriteLine($" [x] Received {message}");
    Console.WriteLine(" [x] Done");

    await channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false);
};

image

3 Publish/Subscribe:广播消息分发模式

Publish/Subscribe(发布/订阅)模式是 RabbitMQ 中实现消息广播的核心通信范式,其核心机制在于通过 fanout 交换机 实现消息的一对多分发。与直接路由模式不同,fanout 交换机会忽略消息的路由键(routing key),将所有接收到的消息 无差别地广播至所有绑定的队列,从而实现“一次发送、多方接收”的通信效果。

在技术实现层面,该模式依赖两个关键组件:临时队列队列绑定机制。临时队列具有两大特性:一是消费者独占性,确保队列仅被当前连接的消费者使用;二是自动删除机制,当消费者断开连接后队列会被 RabbitMQ 自动清理,有效避免资源浪费。队列与交换机的绑定通过 QueueBindAsync 方法实现,此时需将路由键参数设置为空字符串(routingKey: ""),以匹配 fanout 交换机的广播特性。

代码实现上,发送端与接收端呈现显著差异。发送端需显式指定交换机名称(如 exchange = "logs")而非使用默认交换机,并通过 BasicPublish 方法将消息直接发送至交换机;接收端则需动态创建临时队列(通过不指定队列名实现),并将其绑定到目标交换机。这种架构使得消息能够被所有在线的接收端同时获取,与 Hello World 教程中的“点对点”单消费者模式形成鲜明对比。

核心特点总结

  • 广播范围:消息被分发至所有绑定队列,无选择性过滤
  • 路由键作用:在 fanout 模式下路由键被忽略,绑定关系决定分发目标
  • 队列生命周期:临时队列随消费者连接自动创建与销毁,适合日志收集、事件通知等场景

通过 Publish/Subscribe 模式,系统可轻松实现分布式日志收集、实时数据同步、事件通知等需要多节点协同处理的业务场景,显著提升消息系统的灵活性与可扩展性。

4 Routing:基于路由键的精确匹配

Routing 模式是 RabbitMQ 实现选择性消息分发的核心机制,其核心在于通过 direct 交换机实现路由键(routing key)与绑定键(binding key)的精确匹配。与 Publish/Subscribe 模式的“无差别广播”不同,该模式允许消息生产者通过指定路由键精确控制消息流向,消费者则通过绑定特定路由键过滤接收目标消息。

核心特性

  • 路由逻辑:direct 交换机仅将消息转发至绑定键与消息路由键完全一致的队列

  • 多绑定支持:单个队列可绑定多个路由键(如队列 Q2 同时绑定 "black" 和 "green")

  • 精准过滤:实现基于业务标签(如日志级别 "error"、"info")的消息分类分发

在技术实现中,生产者通过 BasicPublish 方法显式指定 routingKey 参数(如

channel.BasicPublish(exchange: "direct_logs", routingKey: "error", body: messageBytes);

消费者则需在队列与交换机绑定时循环声明多个路由键

 foreach (var severity in args) { 
 	channel.QueueBind(queue: queueName, exchange: "direct_logs", routingKey: severity); 
 }

。这种设计使消息系统能够根据业务语义灵活实现多维度的消息过滤与分发策略。

通过对比两种模式可见:Publish/Subscribe 模式通过 fanout 交换机实现“一对多”的广播通信,而 Routing 模式通过 direct 交换机的精确匹配机制,进一步实现了“多对多”的选择性通信,显著提升了消息分发的灵活性与资源利用率。

生产者
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace codeMan.rabbitMQ.Producer.Producer
{
    public class PuiblishSender
    {
        public async static Task PuiblishSned()
        {
            //创建连接工厂
            var factory = new ConnectionFactory()
            {
                UserName = "admin",//用户名
                Password = "admin123",//密码
                HostName = "192.168.49.151",//rabbitmq ip
            };

            using var connection = await factory.CreateConnectionAsync();
            using var channel = await connection.CreateChannelAsync();

            await channel.QueueDeclareAsync(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null);

            //[+新增]:创建交换机
            await channel.ExchangeDeclareAsync(exchange: "publish/subscribe/Route", type: ExchangeType.Direct);

            // [+新增]:将交换机与队列通过路由键绑定
            await channel.QueueBindAsync(
                queue: "task_queue",
                exchange: "publish/subscribe/Route",
                routingKey: "black" // 路由task_queue 键需与发送时的routingKey一致
            );

            for (int i = 0; i < 1000; i++)
            {
                string message = $"乘客{i},136000000{i},已经到您了";
                var body = Encoding.UTF8.GetBytes(message);

                /**
                    用于设置消息属性:
                    persistent - 消息持久化
                 */
                var properties = new BasicProperties
                {
                    Persistent = true
                };

                /***
                用于发送消息:
                    exchange: string.Empty - 使用默认交换机
                    routingKey: "hello" - 路由键
                    mandatory: true - 如果消息无法路由到队列,则返回给发送者
                    basicProperties: properties - 消息属性
                    body: body - 消息内容
                 */
                await channel.BasicPublishAsync(exchange: "publish/subscribe/Route", routingKey: "black", mandatory: false, basicProperties: properties, body: body);
                Console.WriteLine($" [x] Sent {message}");
            }
        }
    }
}

消费者

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace codeMan.rabbitMQ.Consumer02.Consumer
{
    public class Subscribe
    {

        public async static Task SubscribeRecevie()
        {
            //1 创建连接工厂
            var factory = new ConnectionFactory()
            {
                UserName = "admin",//用户名
                Password = "admin123",//密码
                HostName = "192.168.49.151",//rabbitmq ip
            };

            //2 建立到RabbitMQ服务器的连接和通道(channel)
            using var connection = await factory.CreateConnectionAsync();
            using var channel = await connection.CreateChannelAsync();

            // 3. 声明队列确保其存在
            await channel.QueueDeclareAsync(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null);

            //[+新增]:创建交换机
            await channel.ExchangeDeclareAsync(exchange: "publish/subscribe/Route", type: ExchangeType.Direct);
            // [+新增]:将交换机与队列通过路由键绑定
            await channel.QueueBindAsync(queue: "task_queue", exchange: "publish/subscribe/Route", routingKey: "black");



            //4. 设置QoS(服务质量)
            await channel.BasicQosAsync(prefetchSize: 0, prefetchCount: 1, global: false);

            Console.WriteLine(" [*] Waiting for messages.");
            //5. 创建消费者
            var consumer = new AsyncEventingBasicConsumer(channel);
            // 6. 注册消息接收事件处理程序
            consumer.ReceivedAsync += async (model, ea) =>
            {
                // 7. 处理接收到的消息
                byte[] body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);

                var routingKey = ea.RoutingKey;
                Console.WriteLine($" [x] 02.Received '{routingKey}':'{message}'");
                /***
                 * 用于确认消息:
                 * channel - 频道
                 * deliveryTag - 消息的标识
                 * multiple - 是否批量确认
                 */
                await channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false);
            };

            //8. 启动消费者
            await channel.BasicConsumeAsync("task_queue", autoAck: false, consumer: consumer);

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }
}

image

5 Topics:通配符路由与灵活匹配

Topics 模式作为 Routing 模式的扩展,通过引入通配符路由机制实现更灵活的消息分发。其核心特性在于采用点分隔的层级化路由键(如 "stock.usd.nyse"),并支持两种通配符匹配:*(匹配单个单词)和 #(匹配零个或多个单词)2。例如 *.orange.* 可匹配 "quick.orange.rabbit",而 lazy.# 能匹配 "lazy.orange.new.rabbit" 等多级路由键2。

路由匹配规则与行为特性

  • 精准匹配示例:"quick.orange.rabbit" 同时匹配 .orange...rabbit 绑定键,将被路由至两个队列;"quick.brown.fox" 因无匹配绑定键被直接丢弃2。
  • 特殊场景处理:当绑定键为 # 时,队列接收所有消息(模拟 fanout 交换机);若绑定键不含通配符,则等价于 direct 交换机2。

C# 实现关键代码解析

发送端通过声明 topic 交换机并指定多级路由键发送消息:

await channel.ExchangeDeclareAsync(exchange: "topic_logs", type: ExchangeType.Topic);
await channel.BasicPublishAsync(exchange: "topic_logs", routingKey: "kern.critical", body: body);

接收端则利用临时队列绑定多个通配符键:

foreach (var bindingKey in new[] { "kern.*", "*.critical" })
{
    await channel.QueueBindAsync(queue: queueName, exchange: "topic_logs", routingKey: bindingKey);
}

最佳实践:建议采用 <facility>.<severity> 路由键结构(如 "auth.error"),通过 * 实现单层级过滤,# 实现多级模糊匹配,兼顾灵活性与可维护性。

该模式凭借"按需匹配、兼容多类型交换机"的特性,成为 RabbitMQ 中最常用的高级路由方案

无法路由到队列,返回发送者 mandatory=true

生产者

1 mandatory: true 如果消息无法路由到队列,则返回给发送者
2 BasicReturnAsync 设置回调处理返回的消息

// 设置回调处理返回的消息
channel.BasicReturnAsync += async (sender, args) => 
{
    // 处理返回的消息
    var returnedMessage = Encoding.UTF8.GetString(args.Body.Span);
    Console.WriteLine($"Message returned: {returnedMessage}");
    Console.WriteLine($"Reply Code: {args.ReplyCode}, Reply Text: {args.ReplyText}");
    await Task.CompletedTask;
};
/**
    用于设置消息属性:
    persistent - 消息持久化
 */
var properties = new BasicProperties
{
    Persistent = true
};

/***
用于发送消息:
    exchange: string.Empty - 使用默认交换机
    routingKey: "hello" - 路由键
    mandatory: true - 如果消息无法路由到队列,则返回给发送者
    basicProperties: properties - 消息属性
    body: body - 消息内容
 */
await channel.BasicPublishAsync(exchange: string.Empty, routingKey: "task_queue1", mandatory: true, basicProperties: properties, body: body);

消息超时 TTL

 var properties = new BasicProperties
 {
     Persistent = true,
     Expiration = "60000" //消息过期时间,自动从队列删除
 };

 /***
 用于发送消息:
     exchange: string.Empty - 使用默认交换机
     routingKey: "hello" - 路由键
     mandatory: true - 如果消息无法路由到队列,则返回给发送者
     basicProperties: properties - 消息属性
     body: body - 消息内容
  */
 await channel.BasicPublishAsync(exchange: "publish/subscribe/Route", routingKey: "black", mandatory: false, basicProperties: properties, body: body);

死信队列

在 RabbitMQ 中,死信队列(Dead-Letter Queue,DLQ) 是一种用于处理 “异常消息” 的机制 —— 当消息因各种原因无法被正常消费时(如消费失败、消息过期、队列长度超限),会被路由到一个专门的 “死信队列” 中,便于后续分析和处理。它是保障消息可靠性、排查系统问题的关键手段。

一、死信队列的核心价值

场景 传统问题 死信队列的解决方式
消费失败 消息无限重试导致死循环,或直接丢失 失败消息路由到死信队列,避免循环
消息过期 过期消息直接被丢弃,无迹可查 过期消息路由到死信队列,便于追溯
队列长度超限 新消息被拒绝,业务感知不到 超限消息路由到死信队列,触发告警

二、死信队列的触发条件

消息会被标记为 “死信” 并路由到死信队列,需满足以下任一条件:

  1. 消费端拒绝且不重新入队channel.BasicNack(deliveryTag, false, requeue: false) 或 channel.BasicReject(deliveryTag, requeue: false)
  2. 消息过期:队列或消息自身配置了 x-message-ttl,超时后未被消费;
  3. 队列长度超限:队列配置了 x-max-length 或 x-max-length-bytes,新消息进入时旧消息被挤掉。

三、死信队列的配置与使用

情况 是否调用 BasicNackAsync 是否超时(TTL) 消息去向
消费者输入 “1” 调用 BasicAckAsync 不涉及 消息正常确认,从队列删除
消费者输入其他 调用 BasicNackAsync(requeue: false) 若没设置 TTL,消息被拒绝但不一定进 DLQ;若设置了 TTL,可能先超时 若 30 秒没处理,自动进入死信队列
消费者完全不操作 无 Ack/Nack 30 秒后超时 自动触发死信机制,进入 DLQ

生产者

 public async static Task DieLetterExchangeSend()
 {
     const string _queueName = "taobao.order.pay";
     const string _exchangeName = "taobao.order";
     const string _routingKey = "taobao.order.status";
     //创建连接工厂
     var factory = new ConnectionFactory()
     {
         UserName = "admin",//用户名
         Password = "admin123",//密码
         HostName = "192.168.49.151",//rabbitmq ip
     };

     using var connection = await factory.CreateConnectionAsync();
     using var channel = await connection.CreateChannelAsync();

     //await channel.QueueDeclareAsync(queue: "taobao.order.pay", durable: true, exclusive: false, autoDelete: false, arguments: null);

     //死信业务
     var args = new Dictionary<string, object>
     {
         // 死信交换机
         { "x-dead-letter-exchange", "taobao.order.dlx-exchange" },
         // 死信路由键(消息路由到死信队列的规则)
         { "x-dead-letter-routing-key", "taobao.order.order-dlq-routing-key" },
         // 可选:消息过期时间(毫秒)
         { "x-message-ttl", 60000 },
         // 可选:队列最大长度
         //{ "x-max-length", 1000 }
     };
     await channel.QueueDeclareAsync(queue: _queueName, durable: true, exclusive: false, autoDelete: false, arguments: args);

     //[+新增]:创建死信队列
     await channel.InitDieLetterExchangeAsync("taobao.order.dlx-exchange", "taobao.order.order-dlq", "taobao.order.order-dlq-routing-key");

     await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Direct);

     await channel.QueueBindAsync(
         queue: _queueName,
         exchange: _exchangeName,
         routingKey: _routingKey // 路由task_queue 键需与发送时的routingKey一致
     );

     for (int i = 0; i < 1; i++)
     {
         string message = $"乘客{i},136000000{i},已经到您了";
         var body = Encoding.UTF8.GetBytes(message);

         /**
             用于设置消息属性:
             persistent - 消息持久化
          */
         var properties = new BasicProperties
         {
             Persistent = true
         };

         /***
         用于发送消息:
             exchange: string.Empty - 使用默认交换机
             routingKey: "hello" - 路由键
             mandatory: true - 如果消息无法路由到队列,则返回给发送者
             basicProperties: properties - 消息属性
             body: body - 消息内容
          */
         await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: _routingKey, mandatory: false, basicProperties: properties, body: body);
         Console.WriteLine($" [x] Sent {message}");
     }
 }

消费者

public async static Task SubscribeRecevie()
{
    const string _queueName = "taobao.order.pay";
    const string _exchangeName = "taobao.order";
    const string _routingKey = "taobao.order.status";

    //1 创建连接工厂
    var factory = new ConnectionFactory()
    {
        UserName = "admin",//用户名
        Password = "admin123",//密码
        HostName = "192.168.49.151",//rabbitmq ip
    };

    //2 建立到RabbitMQ服务器的连接和通道(channel)
    using var connection = await factory.CreateConnectionAsync();
    using var channel = await connection.CreateChannelAsync();




    // 3. 声明队列确保其存在
    //await channel.QueueDeclareAsync(queue: _queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);


    await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Direct);

    //await channel.QueueBindAsync(queue: _queueName, exchange: _exchangeName, routingKey: _routingKey);



    /* 4. 设置QoS(服务质量)
     * 用于设置队列的消费者数量限制:
     * prefetchSize: 0 - 不限制消息大小
     * prefetchCount: 1 - 每次只处理一条消息
     * global: false - 只针对当前消费者
     */
    await channel.BasicQosAsync(prefetchSize: 0, prefetchCount: 1, global: false);

    Console.WriteLine(" [*] Waiting for messages.");
    /* 5. 创建消费者:
 * channel - 频道
 * autoAck - 自动确认(false为手动)
 * consumer - 消费者
 */
    var consumer = new AsyncEventingBasicConsumer(channel);
    // 6. 注册消息接收事件处理程序
    consumer.ReceivedAsync += async (model, ea) =>
    {
        // 7. 处理接收到的消息
        byte[] body = ea.Body.ToArray();
        var message = Encoding.UTF8.GetString(body);

        Console.WriteLine("请输入");
        var many = Console.ReadLine();

        var routingKey = ea.RoutingKey;
        Console.WriteLine($" [x] 01.Received '{routingKey}':'{message}'");

        if (many == "1")
        {
            Console.WriteLine("支付完成");
            await channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false);
        }
        else
        {
            Console.WriteLine("支付失败");
            /**
             * requeue - 是否重新入队
                 * true - 重新入队
                 * false - 丢弃
             */
            await channel.BasicNackAsync(deliveryTag: ea.DeliveryTag, multiple: false, requeue: false);
        }
    };

    //8. 启动消费者
    await channel.BasicConsumeAsync(_queueName, autoAck: false, consumer: consumer);

    Console.WriteLine(" Press [enter] to exit.");
}

RabbitMQ.Client 7.x 版本(当前用的7.20)BasicNackAsync 不起作用,我试了 6.X版本就可以。不知道是我的问题还是版本问题。

image

image

死信队列里的消息

image

临时队列

image

//临时队列
var queueDeclareResult = await channel.QueueDeclareAsync();
string queueName = queueDeclareResult.QueueName;
await channel.QueueBindAsync(queue: queueName, exchange: "publish/subscribe/Route", routingKey: "black");

当你调用 await channel.QueueDeclareAsync() 不带任何参数时,会创建一个特殊的队列:

  1. 服务器命名的队列: RabbitMQ 会自动生成一个唯一的队列名称

  2. 临时队列
    这种队列具有以下特征:
    • 排他性(exclusive)- 只对当前连接可见
    • 自动删除(autoDelete)- 连接断开时自动删除
    • 非持久化(non-durable)- 服务器重启后消失

生成的队列名称格式

这类自动生成的队列名称通常遵循特定格式,例如:

amq.gen-LHQZzvbg9e1_t5se2l_8wg

这种名称的特点:

  • amq.gen- 开头
  • 后跟一串随机字符
  • 每次创建都是唯一的
posted @ 2025-11-20 13:11  【唐】三三  阅读(23)  评论(0)    收藏  举报