文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

.NETCore基于RabbitMQ实现延时队列的两方法

2024-04-02 19:55

关注

前言

此文章用来记录自己学习延时队列过程的文章,并用.NET这两种方式实现了简单的Demo。

延时队列的应用场景 应用下单后,30分钟没有支付的话,则自动取消订单活动开始前30分钟,提醒参赛者参加活动。活动结束后,30分钟后提醒未进行评价的参赛人员进行评价…

上述的场景都可以使用延时队列进行对应的处理。

上面的场景虽说可以通过定时器也可以处理,但有点浪费资源, 而上述的场景时间是不定的,例如有两个活动需要提醒参赛者参加,一个是7点开始 ,另一个是8点开始,那么触发处理的一个是6点半,一个是7点半。

实现延时队列的两种方式

使用Rabbitmq实现延时队列可以让消息持久化,也支持分布式

 缺点
第一种第一种方式的缺陷以及解决方案
第二种这个插件的当前设计并不真正适合具有大量延迟消息(例如成百上千或数百万)的场景。详情信息

利用rabbitmq死信队列x-dead-letter-exchange和x-dead-letter-routing-key

实现需要创建两对交换机和队列,其中需要对其中一对的队列进行设置x-dead-letter-exchange和x-dead-letter-routing-key属性,属性指定转发到另一对的交换机,

随后实现流程图如下:

.NETCore实现方式

项目:.NET Core 控制台项目

install-package RabbitMQ.Client

生产者代码:

            ConnectionFactory connectionFactory = new ConnectionFactory
            {
                UserName = "guest",
                Password = "guest",
                HostName = "127.0.0.1"
            };

            //创建连接
            var connection = connectionFactory.CreateConnection();

            //创建通道
            var channl = connection.CreateModel();

           //指定队列的x-dead-letter-exchange和x-dead-letter-routing-key
            Dictionary<string, object> queueArgs = new Dictionary<string, object>()
            {
                { "x-dead-letter-exchange","exchange.business.test" },
                {"x-dead-letter-routing-key","businessRoutingkey" }
            };

            //延时的交换机和队列绑定
            channl.ExchangeDeclare("exchange.business.dlx", "direct", true, false, null);
            channl.QueueDeclare("queue.business.dlx", true, false, false, queueArgs);
            channl.QueueBind("queue.business.dlx", "exchange.business.dlx", "");

            //业务的交换机和队列绑定
            channl.ExchangeDeclare("exchange.business.test", "direct", true, false, null);
            channl.QueueDeclare("queue.business.test", true, false, false, null);
            channl.QueueBind("queue.business.test", "exchange.business.test", "businessRoutingkey", null);

            Console.WriteLine("生产者开始发送消息");
            while (true)
            {

                string message = Console.ReadLine();
                var body = Encoding.UTF8.GetBytes(message);
                var properties = channl.CreateBasicProperties();
                properties.Persistent = true;
                properties.Expiration = "5000";
                //发送一条延时5秒的消息
                channl.BasicPublish("exchange.business.dlx", "", properties, body);

            }

消费者

            ConnectionFactory connectionFactory = new ConnectionFactory
            {
                UserName = "guest",
                Password = "guest",
                HostName = "127.0.0.1"
            };

            //创建连接
            var connection = connectionFactory.CreateConnection();

            var channel = connection.CreateModel();

            EventingBasicConsumer consumer = new EventingBasicConsumer(channel);

            //给消费时添加一个委托
            consumer.Received += (obj, ea) =>
            {
                var message = Encoding.UTF8.GetString(ea.Body.ToArray());
                //打印消费的消息
                Console.WriteLine(message);
                channel.BasicAck(ea.DeliveryTag, false);
            };

            //消费queue.business.test队列的消息
            channel.BasicConsume("queue.business.test", false, consumer);

            Console.ReadKey();
            channel.Dispose();
            connection.Close();

实现效果:

rabbitmq通过安装插件的形式实现(推荐)

使用rabbitmq_delayed_message_exchange 插件提供的x-delayed-message类型的交换机

下载插件的地址:https://www.rabbitmq.com/community-plugins.html
选中rabbitmq_delayed_message_exchange插件

该插件使用只需要声明交换机的时候,指定x-delayed-message类型,然后添加x-delayed-type参数即可

.NET Core 实现

生产者

            ConnectionFactory connectionFactory = new ConnectionFactory()
            {
                UserName = "guest",
                Password = "guest",
                HostName = "127.0.0.1"
            };

            var connection = connectionFactory.CreateConnection();

            var channel = connection.CreateModel();

            Dictionary<string, object> exchangeArgs = new Dictionary<string, object>()
            {
                {"x-delayed-type","direct" }
            };

            //指定x-delayed-message 类型的交换机,并且添加x-delayed-type属性
            channel.ExchangeDeclare("plug.delay.exchange", "x-delayed-message", true, false, exchangeArgs);

            channel.QueueDeclare("plug.delay.queue", true, false, false, null);

            channel.QueueBind("plug.delay.queue", "plug.delay.exchange", "plugdelay");

            var properties = channel.CreateBasicProperties();
            Console.WriteLine("生产者开始发送消息");
            Dictionary<string, object> headers = new Dictionary<string, object>()
            {
                {"x-delay","5000" }
            };
            properties.Persistent = true;
            properties.Headers = headers;
            while (true)
            {

                string message = Console.ReadLine();
                var body = Encoding.UTF8.GetBytes(message);
                channel.BasicPublish("plug.delay.exchange", "plugdelay", properties, body);

            }

消费者:

            ConnectionFactory connectionFactory = new ConnectionFactory
            {
                UserName = "guest",
                Password = "guest",
                HostName = "127.0.0.1"
            };

            //创建连接
            var connection = connectionFactory.CreateConnection();

            var channel = connection.CreateModel();

            EventingBasicConsumer consumer = new EventingBasicConsumer(channel);

            consumer.Received += (obj, ea) =>
            {
                var message = Encoding.UTF8.GetString(ea.Body.ToArray());
                Console.WriteLine(message);
                channel.BasicAck(ea.DeliveryTag, false);
            };

            channel.BasicConsume("plug.delay.queue", false, consumer);

            Console.ReadKey();
            channel.Dispose();
            connection.Close();

实现效果:

第一种方式的缺陷以及解决方案

如果存在A、B消息进入了队列中,A在前,B在后,如果B消息的过期时间比A的过期时间要早,消费的时候,并不会先消费B,再消费A,而是B会等A先消费,即使A要晚过期

举例

生产者代码修改成如下:

            ConnectionFactory connectionFactory = new ConnectionFactory
            {
                UserName = "guest",
                Password = "guest",
                HostName = "127.0.0.1"
            };

            //创建连接
            var connection = connectionFactory.CreateConnection();

            //创建通道
            var channl = connection.CreateModel();

            Dictionary<string, object> queueArgs = new Dictionary<string, object>()
            {
                { "x-dead-letter-exchange","exchange.business.test" },
                {"x-dead-letter-routing-key","businessRoutingkey" }
            };

            //延时的交换机和队列绑定
            channl.ExchangeDeclare("exchange.business.dlx", "direct", true, false, null);
            channl.QueueDeclare("queue.business.dlx", true, false, false, queueArgs);
            channl.QueueBind("queue.business.dlx", "exchange.business.dlx", "");

            //业务的交换机和队列绑定
            channl.ExchangeDeclare("exchange.business.test", "direct", true, false, null);
            channl.QueueDeclare("queue.business.test", true, false, false, null);
            channl.QueueBind("queue.business.test", "exchange.business.test", "businessRoutingkey", null);

            string message1 = "Hello Word!1";
            string message2 = "Hello Word!2";
            var body1 = Encoding.UTF8.GetBytes(message1);
            var body2 = Encoding.UTF8.GetBytes(message2);
            var properties = channl.CreateBasicProperties();
            properties.Persistent = true;
            //先发送过期时间5秒的消息
            properties.Expiration = "5000";
            channl.BasicPublish("exchange.business.dlx", "", properties, body2);

            //再发送过期时间3秒的消息
            properties.Expiration = "3000";
            channl.BasicPublish("exchange.business.dlx", "", properties, body1);

结果:

这里先发了延时20秒的A消息,然后又发了延时10秒的B消息,但是最终结果并不是先消费了B消息,而是等A消息过期后,立刻再去消费B。

这个会影响什么业务呢?好比两个C、D活动,C活动开始时间是7点,D活动开始时间是5点,那么D活动提醒需要等到C活动提醒后,才会立刻提醒,这明显不符合我们的业务需求。

解决方案 每个活动都是单独的创建自己的交换机和队列使用第二种实现方式,即使用插件的形式。

第一种不太现实,因为如果活动多的话,则会创建很多的队列,而且只会使用一次。

业务上还是推荐使用插件的实现方式。

第二种方式的效果

github地址:

https://github.com/MDZZ3/RabbitmqDelay

到此这篇关于.NETCore基于RabbitMQ实现延时队列的两方法的文章就介绍到这了,更多相关.NETCore RabbitMQ 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯