.NET 下 RabbitMQ 队列、死信队列、延时队列及小应用

前言

关于安装rabbitmq这里一笔掠过了。

正文

下面进入正题

1、新建aspnetcorewebapi空项目,NormalQueue,删除controllers文件夹已经无关的文件,这里为了偷懒不用console控制台

ini 复制代码
public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.

        builder.Services.AddControllers();
        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();
        builder.Services.AddHostedService<ConsumerService>();
        builder.Services.AddHostedService<DeadLetterExchangeConsuerService>();
        builder.Services.AddHostedService<DelayExchangeConsumerService>();
        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }
        app.MapGet("/normal/{message}", ([FromRoute] string message) =>
        {
            ConnectionFactory factory = new ConnectionFactory();
            factory.HostName = "localhost";
            factory.Port = 5672;
            using (IConnection connection = factory.CreateConnection())
            {
                using (IModel channel = connection.CreateModel())
                {
                    var queueName = "rbTest202301";
                    channel.QueueDeclare(queueName, true, false, false, null);

                    {
                        string sendMessage = string.Format("Message_{0}", message);
                        byte[] buffer = Encoding.UTF8.GetBytes(sendMessage);
                        IBasicProperties basicProperties = channel.CreateBasicProperties();
                        basicProperties.DeliveryMode = 2; //持久化  1=非持久化
                        channel.BasicPublish("", queueName, basicProperties, buffer);
                        Console.WriteLine("消息发送成功:" + sendMessage);
                    }
                }
            }
        });

        app.MapGet("/deadletterexchange/{message}",([FromRoute] string message) =>{
            DeadLetterExchange.Send(message);
        });

        app.MapGet("/delayexchange/{message}", ([FromRoute] string message) => {
            DelayExchange.SendMessage(message);
        });
        app.UseHttpsRedirection();

        app.UseAuthorization();


        app.MapControllers();

        app.Run();
    }
}

大概的介绍一下program文件:

这里有三个mini控制器,从这里发送对应的消息到rabbitmq

arduino 复制代码
"/normal/{message}"   普通队列,
"/deadletterexchange/{message}" 死信队列
"/deadletterexchange/{message}"   延时队列
ini 复制代码
builder.Services.AddHostedService<ConsumerService>();
builder.Services.AddHostedService<DeadLetterExchangeConsuerService>();
builder.Services.AddHostedService<DelayExchangeConsumerService>();

这里就是消费的服务,注册成HostedService。

ConsumerService代码如下:

ini 复制代码
public class ConsumerService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Console.WriteLine("normal Rabbitmq消费端开始工作!");
        while (!stoppingToken.IsCancellationRequested)
        {
            ConnectionFactory factory = new ConnectionFactory();
            factory.HostName = "localhost";
            factory.Port = 5672;
           
            IConnection connection = factory.CreateConnection();
            {
                IModel channel = connection.CreateModel();
                {
                    var queueName = "rbTest202301";
                    channel.QueueDeclare(queueName, true, false, false, null);
                    //输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息
                    channel.BasicQos(0, 1, false);
                    //在队列上定义一个消费者
                    var consumer = new EventingBasicConsumer(channel);
                    channel.BasicConsume(queueName, false, consumer);
                    consumer.Received += (ch, ea) =>
                    {
                        byte[] bytes = ea.Body.ToArray();
                        string str = Encoding.UTF8.GetString(bytes);
                        Console.WriteLine("队列消息:" + str.ToString());
                        //回复确认
                        channel.BasicAck(ea.DeliveryTag, false);
                    };
                }
            }
            await Task.Delay(5000);
        }
    }
}

DeadLetterExchangeConsuerService代码如下:

arduino 复制代码
public class DeadLetterExchangeConsuerService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Console.WriteLine("RabbitMQ消费端死信队列开始工作");
        while (!stoppingToken.IsCancellationRequested)
        {
            DeadLetterExchange.Consumer();
            await Task.Delay(5000);
        }
    }
}
csharp 复制代码
public class DeadLetterExchange
{
    public static string dlxExchange = "dlx.exchange";
    public static string dlxQueueName = "dlx.queue";
    static string exchange = "direct-exchange";
    static string queueName = "queue_Testdlx";
    static string dlxExchangeKey = "x-dead-letter-exchange";
    static string dlxQueueKey = "x-dead-letter-rounting-key";
    public static void Send(string message)
    {
        using (var connection = new ConnectionFactory() { HostName = "localhost", Port = 5672 }.CreateConnection())
        {
            using(var channel = connection.CreateModel())
            {
               
                channel.ExchangeDeclare(exchange, ExchangeType.Direct, true, false); //创建交换机
                channel.QueueDeclare(queueName, true, false, false,new Dictionary<string, object>
                {
                    { dlxExchangeKey,dlxExchange },
                    {dlxQueueKey,dlxQueueName }
                }); // 创建队列
                channel.QueueBind(queueName, exchange, queueName);
                

                var properties = channel.CreateBasicProperties();
                properties.Persistent= true;//持久化
                channel.BasicPublish(exchange,queueName,properties,Encoding.UTF8.GetBytes(message));
                Console.WriteLine($"向队列:{queueName}发送消息:{message}");
            }
        }
    }
    
    public static void Consumer()
    {
        var connection = new ConnectionFactory() { HostName = "localhost", Port = 5672 }.CreateConnection();
        var channel = connection.CreateModel();
        channel.ExchangeDeclare(dlxExchange, ExchangeType.Direct, true, false); //创建sixin交换机
        channel.QueueDeclare(dlxQueueName, true, false, false); // 创建sixin队列
        channel.QueueBind(dlxQueueName, dlxExchange, dlxQueueName); //绑定sixin队列sixin交换机

        channel.ExchangeDeclare(exchange, ExchangeType.Direct, true, false); //创建交换机
        channel.QueueDeclare(queueName, true, false, false, new Dictionary<string, object>
                {
                    { dlxExchangeKey,dlxExchange },
                    {dlxQueueKey,dlxQueueName }
                }); // 创建队列
        channel.QueueBind(queueName, exchange, queueName);

        var consumer = new EventingBasicConsumer(channel);
        channel.BasicQos(0, 1, false);
        consumer.Received += (model, ea) =>
        {
            var message = Encoding.UTF8.GetString(ea.Body.ToArray());
            Console.WriteLine($"队列{queueName}消费消息:{message},不做ack确认");
            channel.BasicNack(ea.DeliveryTag, false, requeue: false);
        };
        channel.BasicConsume(queueName, autoAck: false, consumer);
    }
}

DelayExchangeConsumerService代码如下:

arduino 复制代码
public class DelayExchangeConsumerService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Console.WriteLine("RabbitMQ消费端延迟队列开始工作");
        while (!stoppingToken.IsCancellationRequested)
        {
          
            DelayExchange.Consumer();
            await Task.Delay(5000);
        }
    }
}
php 复制代码
public class DelayExchange
{

    public static void SendMessage(string message)
    {
        //死信交换机
        string dlxexChange = "dlx.exchange";
        //死信队列
        string dlxQueueName = "dlx.queue";

        //消息交换机
        string exchange = "direct-exchange";
        //消息队列
        string queueName = "delay_queue";

        using (var connection = new ConnectionFactory() { HostName = "localhost", Port = 5672 }.CreateConnection())
        {
            using (var channel = connection.CreateModel())
            {
                ////创建死信交换机
                //channel.ExchangeDeclare(dlxexChange, type: ExchangeType.Direct, durable: true, autoDelete: false);
                ////创建死信队列
                //channel.QueueDeclare(dlxQueueName, durable: true, exclusive: false, autoDelete: false);
                ////死信队列绑定死信交换机
                //channel.QueueBind(dlxQueueName, dlxexChange, routingKey: dlxQueueName);

                // 创建消息交换机
                channel.ExchangeDeclare(exchange, type: ExchangeType.Direct, durable: true, autoDelete: false);
                //创建消息队列,并指定死信队列,和设置这个队列的消息过期时间为10s
                channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false, arguments:
                                    new Dictionary<string, object> {
                                         { "x-dead-letter-exchange",dlxexChange}, //设置当前队列的DLX(死信交换机)
                                         { "x-dead-letter-routing-key",dlxQueueName}, //设置DLX的路由key,DLX会根据该值去找到死信消息存放的队列
                                         { "x-message-ttl",10000} //设置队列的消息过期时间
                                    });
                //消息队列绑定消息交换机
                channel.QueueBind(queueName, exchange, routingKey: queueName);

                var properties = channel.CreateBasicProperties();
                properties.Persistent = true;
                //properties.Expiration = "5000";发布消息,延时5s
                //发布消息
                channel.BasicPublish(exchange: exchange,
                                     routingKey: queueName,
                                     basicProperties: properties,
                                     body: Encoding.UTF8.GetBytes(message));
                Console.WriteLine($"{DateTime.Now},向队列:{queueName}发送消息:{message}");
            }
        }
    }

    public static void Consumer()
    {
        //死信交换机
        string dlxexChange = "dlx.exchange";
        //死信队列
        string dlxQueueName = "dlx.queue";
        var connection = new ConnectionFactory() { HostName = "localhost", Port = 5672 }.CreateConnection();
        {
            //创建信道
            var channel = connection.CreateModel();
            {
                //创建死信交换机
                channel.ExchangeDeclare(dlxexChange, type: ExchangeType.Direct, durable: true, autoDelete: false);
                //创建死信队列
                channel.QueueDeclare(dlxQueueName, durable: true, exclusive: false, autoDelete: false);
                //死信队列绑定死信交换机
                channel.QueueBind(dlxQueueName, dlxexChange, routingKey: dlxQueueName);

                var consumer = new EventingBasicConsumer(channel);
                channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: true);
                consumer.Received += (model, ea) =>
                {
                    //处理业务
                    var message = Encoding.UTF8.GetString(ea.Body.ToArray());
                    Console.WriteLine($"{DateTime.Now},队列{dlxQueueName}消费消息:{message}");
                    channel.BasicAck(ea.DeliveryTag, false);
                };
                channel.BasicConsume(dlxQueueName, autoAck: false, consumer);
            }
        }
    }
}

延时队列实际应用场景可能比较复杂,比如每条消息的过期时间不一样,收到的消息的顺序有可能会乱掉。

这些不做深究,自行百度。

关于死信队列常见应用场景之一下单,支付,支付超时的各种场景,下面通过一个简单的例子模拟一下

同样的新建一个空的webapi项目DeadLetterQueue,

program代码如下:

ini 复制代码
public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.

        builder.Services.AddControllers();
        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();
        builder.Services.AddHostedService<ConsumerService>();
        builder.Services.AddHostedService<DeadConsumerService>();
        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }
        app.MapGet("/normal/{message}", ([FromRoute] string message) =>
        {
            ConnectionFactory factory = new ConnectionFactory();
            factory.HostName = "localhost";
            factory.Port = 5672;
            using (IConnection connection = factory.CreateConnection())
            {
                using (IModel channel = connection.CreateModel())
                {
                    var queueName = "rbTest2023010";
                  
                    //channel.ExchangeDeclare("exchange.dlx", ExchangeType.Direct, true);
                    //channel.QueueDeclare("queue.dlx", true, false, false, null);
                    channel.ExchangeDeclare("exchange.normal", ExchangeType.Fanout, true);
                    channel.QueueDeclare(queueName, true, false, false,
                        new Dictionary<string, object>
                    {
                        { "x-message-ttl" ,10000},
                        {"x-dead-letter-exchange","exchange.dlx" },
                        {"x-dead-letter-routing-key","routingkey" }
                    }
                        );
                   
                    channel.QueueBind(queueName, "exchange.normal", "");
                    {
                        string sendMessage = string.Format("Message_{0}", message);
                        byte[] buffer = Encoding.UTF8.GetBytes(sendMessage);
                        IBasicProperties basicProperties = channel.CreateBasicProperties();
                        basicProperties.DeliveryMode = 2; //持久化  1=非持久化
                        channel.BasicPublish("exchange.normal", queueName, basicProperties, buffer);
                        Console.WriteLine($"{DateTime.Now}消息发送成功:{sendMessage}" );
                    }
                }
            }
        });
        app.UseHttpsRedirection();

        app.UseAuthorization();


        app.MapControllers();

        app.Run();
    }
}

下单后消费代码ConsumerService如下

csharp 复制代码
using Microsoft.AspNetCore.Connections;
using Microsoft.Extensions.Hosting;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;

namespace App
{
    public class ConsumerService : BackgroundService
    {
        private readonly IModel channel;
        private readonly IConnection connection;
        public ConsumerService()
        {
            ConnectionFactory factory = new ConnectionFactory();
            factory.HostName = "localhost";
            factory.Port = 5672;
            factory.UserName = "admin";
            factory.Password = "admin";
            connection = factory.CreateConnection();
            channel = connection.CreateModel();
            var queueName = "rbTest2023010";
            channel.ExchangeDeclare("exchange.normal", ExchangeType.Fanout, true);
            channel.QueueDeclare(queueName, true, false, false, new Dictionary<string, object>
                        {
                            { "x-message-ttl" ,10000},
                            {"x-dead-letter-exchange","exchange.dlx" },
                            {"x-dead-letter-routing-key","routingkey" }
                        });

            channel.QueueBind(queueName, "exchange.normal", "");
            //输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息
            channel.BasicQos(0, 1, false);
            //在队列上定义一个消费者
            var consumer = new EventingBasicConsumer(channel);
            channel.BasicConsume(queueName, false, consumer);
            consumer.Received += (ch, ea) =>
            {
                byte[] bytes = ea.Body.ToArray();
                string str = Encoding.UTF8.GetString(bytes);
                Console.WriteLine($"{DateTime.Now}来自死信队列获取的消息: {str.ToString()}");
                //回复确认
                if (str.Contains("跳过")) //假设超时不处理,留给后面deadconsumerservice处理
                {
                    Console.WriteLine($"{DateTime.Now}来自死信队列获取的消息: {str.ToString()},该消息被拒绝");
                    channel.BasicNack(ea.DeliveryTag, false, false);
                }
                else  //正常消息处理
                {
                    Console.WriteLine($"{DateTime.Now}来自死信队列获取的消息: {str.ToString()},该消息被接受");
                    channel.BasicAck(ea.DeliveryTag, false);
                }
            };

        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("normal Rabbitmq消费端开始工作!");
            while (!stoppingToken.IsCancellationRequested)
            {
                await Task.Delay(5000);
            }
        }
        public override void Dispose()
        {
            // 在服务结束时关闭连接和通道
            channel.Close();
            connection.Close();
            base.Dispose();
        }
    }
}

通过模拟发送的消息加入跳过两个字会拒收这条消息,这样就会跳到设置的exchange.dlx交换机队列去,如果没有跳过那么这条消息就正常处理掉,消费确认。

超时不处理后我们通过新的消费服务DelayConsumerService来处理这异常的消费,比如回复库存,订单状态改为取消等等

csharp 复制代码
using RabbitMQ.Client.Events;
using RabbitMQ.Client;
using System.Text;

namespace App
{
    public class DelayConsumerService:BackgroundService
    {
        private readonly IModel channel;
        private readonly IConnection connection;
        public DelayConsumerService()
        {
            ConnectionFactory factory = new ConnectionFactory();
            factory.HostName = "localhost";
            factory.Port = 5672;
            factory.UserName = "admin";
            factory.Password = "admin";
            connection = factory.CreateConnection();
            channel = connection.CreateModel();
            var queueName = "queue.dlx";
            channel.ExchangeDeclare("exchange.dlx", ExchangeType.Direct, true);
            //channel.QueueDeclare("queue.dlx", true, false, false, null);

            channel.QueueDeclare(queueName, true, false, false, null);

            channel.QueueBind(queueName, "exchange.dlx", "routingkey");  //可能是新版问题吧,不绑定routingkey消费不了。
            //输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息
            channel.BasicQos(0, 1, false);
            //在队列上定义一个消费者
            var consumer = new EventingBasicConsumer(channel);
            channel.BasicConsume("queue.dlx", false, consumer);
            consumer.Received += (ch, ea) =>
            {
                byte[] bytes = ea.Body.ToArray();
                string str = Encoding.UTF8.GetString(bytes);
                Console.WriteLine($"{DateTime.Now}超时未处理的消息: {str.ToString()}");
                //回复确认
                {
                    channel.BasicAck(ea.DeliveryTag, false);
                }
            };
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("delay Rabbitmq消费端开始工作!");
            while (!stoppingToken.IsCancellationRequested)
            {
                await Task.Delay(5000);
            }
        }

        public override void Dispose()
        {
            // 在服务结束时关闭连接和通道
            channel.Close();
            connection.Close();
            base.Dispose();
        }
    }
}

上面第一个例子的循环代码也需要放到构造函数去,否则rabbitmq会不停的新增消费者。

运行结果:

关于rabbitmq的死信队列和延时队列的介绍什么的这里不去贴baidu了,应用demo就这么多了,代码这里github.com/liuzhixin40...

小面分享一个完整一点的例子。

github.com/liuzhixin40...

感觉自己还是不合适写这些玩意儿,没有那么细心和耐心,有这时间真不如写写demo。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:星仔007

出处:cnblogs.com/morec/p/17020566.html

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!

相关推荐
AI人H哥会Java2 小时前
【Spring】Spring的模块架构与生态圈—Spring MVC与Spring WebFlux
java·开发语言·后端·spring·架构
毕设资源大全2 小时前
基于SpringBoot+html+vue实现的林业产品推荐系统【源码+文档+数据库文件+包部署成功+答疑解惑问到会为止】
java·数据库·vue.js·spring boot·后端·mysql·html
Watermelon_Mr2 小时前
Spring(三)-SpringWeb-概述、特点、搭建、运行流程、组件、接受请求、获取请求数据、特殊处理、拦截器
java·后端·spring
唐墨1233 小时前
golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
开发语言·后端·golang
凡人的AI工具箱3 小时前
每天40分玩转Django:Django测试
数据库·人工智能·后端·python·django·sqlite
qyq13 小时前
Django框架与ORM框架
后端·python·django
无奈ieq4 小时前
Scala快速入门+示例
开发语言·后端·scala
阿Q说代码5 小时前
合合信息:视觉内容安全技术的前沿进展与应用
后端·安全
向宇it5 小时前
【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)
java·开发语言·unity·c#·游戏引擎·里氏替换原则
hshpy6 小时前
To use only local configuration in your Spring Boot application
java·spring boot·后端