RabbitMQ延时队列和死信队列

延时队列和死信队列

延时队列是RabbitMQ中的一种特殊队列,它可以在消息到达队列后延迟一段时间再被消费。

延时队列的实现原理是通过使用消息的过期时间和死信队列来实现。当消息被发送到延时队列时,可以为消息设置一个过期时间,这个过期时间决定了消息在延时队列中等待的时间。如果消息在过期时间内没有被消费者消费,则会被自动发送到一个预先指定的死信队列中。

在RabbitMQ中,延时队列的实现可以通过以下步骤来完成:

  1. 创建一个普通的队列作为延时队列,设置x-message-ttl参数为消息的过期时间。
  2. 创建一个死信队列,用于接收延时队列中过期的消息。
  3. 将延时队列设置为普通队列的死信交换机,并指定死信路由键。
  4. 将消费者绑定到死信队列,以消费延时队列中过期的消息。

使用场景

  1. 订单在十分钟内未支付则自动取消。
  2. 新创建的店铺,如果十天内都没有上传过商品,则自动发送消息提醒。
  3. 账单在一周内未支付,则自动结算。
  4. 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
  5. 还有很多场景就不一一例举了。

TTL设置

方式一:

创建队列时设置x-message-ttl的属性,所有被投递到该队列的消息最多都不会超过60s

var args = new Dictionary<string,object>();
args.Add("x-message-ttl",60000); //单位为毫秒
model.QueueDeclare("myqueue",false,false,false,args);

方式二:

为每条消息设置TTL,为每条消息设置过期时间。

IBasicProperties props = model.CreateBasicProperties();
props.ContentType = "text/plain";
props.DeliveryMode = 2;
props.Expiration = "60000"
model.BasicPublic(exchangeName,routingKey,props,messageBodyBytes);

代码实践

模拟支付业务

整个项目由三部分组成

  • Web API项目:用于发送订单请求,生产者。
  • 控制台项目一:用于处理订单支付,延时队列。
  • 控制台项目二:用于处理超时未支付的订单,死信队列。

Web API项目

订单类,就简单的写一个用于演示,真实业务肯定不是这样~

public class OrderDto
{
/// <summary>
/// 订单名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 订单状态
/// 0 未支付
/// 1 已支付
/// 2 超时
/// </summary>
public int Status { get; set; }
}

控制器

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService; public OrdersController(IOrderService orderService)
{
_orderService = orderService;
} [HttpPost]
public IActionResult CreateOrder([FromBody] OrderDto orderDto)
{
// 处理订单逻辑
_orderService.ProcessOrder(orderDto);
return Ok();
}
}

订单服务

public interface IOrderService
{
void ProcessOrder(OrderDto orderDto);
} public class OrderService : IOrderService
{
private readonly RabbitMQConnectionFactory _connectionFactory; public OrderService(RabbitMQConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
} public void ProcessOrder(OrderDto orderDto)
{
using (var channel = _connectionFactory.CreateChannel())
{
var properties = channel.CreateBasicProperties();
properties.Headers = new Dictionary<string, object>
{
{ "x-delay", 1000 * 20 } // 设置20秒延时
}; var message = JsonConvert.SerializeObject(orderDto);
var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("delayed_exchange", "routing_key", properties, body);
}
}
}

支付处理项目

ProcessPay类,用于接收订单消息

public class ProcessPay : IHostedService
{
private readonly ConnectionFactory _factory;
private IConnection _connection;
private IModel _channel; public ProcessPay()
{
_factory = new ConnectionFactory()
{
HostName = "ip",
Port = 5672,
UserName = "用户名",
Password = "密码"
};
} public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine(" Press [enter] to exit.");
_connection = _factory.CreateConnection();
_channel = _connection.CreateModel(); _channel.ExchangeDeclare("delayed_exchange", ExchangeType.Direct, true, false, null);
//关键代码,绑定死信交换机
var arguments = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "dead_letter_exchange" },
{ "x-dead-letter-routing-key", "dead_letter_routing_key" }
};
_channel.QueueDeclare("delayed_queue", true, false, false, arguments);
_channel.QueueBind("delayed_queue", "delayed_exchange", "routing_key"); var consumer = new EventingBasicConsumer(_channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body); // 处理支付逻辑
var orderDto = JsonConvert.DeserializeObject<OrderDto>(message);
Console.WriteLine($"订单信息:{orderDto.Name}");
Console.WriteLine("请输入价格(模拟支付):"); // 超时未支付
string? many = "";
// 支付处理
Console.WriteLine("请输入:");
// 超时未支付进行处理
Task.Factory.StartNew(() =>
{
many = Console.ReadLine();
Console.WriteLine($"many:{many}");
}).Wait(20 * 1000);
if (string.Equals(many, "100"))
{
orderDto.Status = 1;
Console.WriteLine("支付完成");
_channel.BasicAck(ea.DeliveryTag, true);
}
else
{
//重试几次依然失败
Console.WriteLine("等待一定时间内失效超时未支付的订单");
_channel.BasicNack(ea.DeliveryTag, false, false);
}
}; _channel.BasicConsume("delayed_queue", false, consumer); return Task.CompletedTask;
} public Task StopAsync(CancellationToken cancellationToken)
{
_channel?.Close();
_connection?.Close();
_channel?.Dispose();
_connection?.Dispose(); return Task.CompletedTask;
}
}

在Main方法中使用单例模式注册该服务,当然直接将代码写在Main方法也是没有问题的,只不过这种方式方便管理。

static void Main(string[] args)
{
var host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IHostedService,ProcessPay>();
})
.Build(); host.Run();
}

支付超时项目

创建一个死信队列服务,用于订阅死信队列中的订单消息,这里我就直接把代码写在Main方法中了

using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare("dead_letter_exchange", ExchangeType.Direct, true, false, null); channel.QueueDeclare("dead_letter_queue", true, false, false, null); channel.QueueBind("dead_letter_queue", "dead_letter_exchange", "dead_letter_routing_key"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body); // 处理超时未支付的订单逻辑
var orderDto = JsonConvert.DeserializeObject<OrderDto>(message);
orderDto.Status = 2;
Console.WriteLine($"订单信息:{orderDto.Name},{orderDto.Status}");
Console.WriteLine("超时未支付"); channel.BasicAck(ea.DeliveryTag, true);
}; channel.BasicConsume("dead_letter_queue", false, consumer); Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}

效果展示

代码看不出效果,直接上图。

首先是3个项目各自运行效果图

然后演示正常消费效果

接下来是超时未支付效果

结尾

这就是一个简单的延时队列和死信队列的代码,模拟了支付超时的场景,这里的数据都写死了的,真实运用的时候肯定是中数据库中获取,修改数据库实体的值。然后死信队列是用于处理在一定时间内未被处理的消息,死信交换机也只是一个普通的交换机,只不过他是用于处理超时的消息的交换机。

对于RabbitMQ的文章基本就结束了,可能还会有一篇RabbitMQ集群搭建的文章,但不是很想去写,最近太懒了~

有问题欢迎指出,活到老学到老~

RabbitMQ系列文章

参考资料

.NET中使用RabbitMQ延时队列和死信队列的更多相关文章

  1. 《RabbitMQ》什么是死信队列

    一 什么是死信队列 当一条消息在队列中出现以下三种情况的时候,该消息就会变成一条死信. 消息被拒绝(basic.reject / basic.nack),并且requeue = false 消息TTL ...

  2. RabbitMQ延迟消息:死信队列 | 延迟插件 | 二合一用法+踩坑手记+最佳使用心得

    前言 前段时间写过一篇: # RabbitMQ:消息丢失 | 消息重复 | 消息积压的原因+解决方案+网上学不到的使用心得 很多人加了我好友,说很喜欢这篇文章,也问了我一些问题. 因为最近工作比较忙, ...

  3. RocketMQ之八:重试队列,死信队列,消息轨迹

    问题思考 死信队列的应用场景? 死信队列中的数据是如何产生的? 如何查看死信队列中的数据? 死信队列的读写权限? 死信队列如何消费? 重试队列和死信队列的配置 消息轨迹 1.应用场景 一般应用在当正常 ...

  4. rabbitmq实现延时队列(死信队列)

    基于队列和基于消息的TTL TTL是time to live 的简称,顾名思义指的是消息的存活时间.rabbitMq可以从两种维度设置消息过期时间,分别是队列和消息本身. 队列消息过期时间-Per-Q ...

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

    关于安装rabbitmq这里一笔掠过了. 下面进入正题: 1.新建aspnetcorewebapi空项目,NormalQueue,删除controllers文件夹已经无关的文件,这里为了偷懒不用con ...

  6. rabbitMq 学习笔记(二) 备份交换器,过期时间,死信队列,死信队列

    备份交换器 备份交换器,英文名称为 Altemate Exchange,简称庙,或者更直白地称之为"备胎交换器". 生产者在发送消息的时候如果不设置 mandatory 参数, 那 ...

  7. Kafka 实现延迟队列、死信队列、重试队列

    更多内容,访问 IT-BLOG Kafka中实现延迟队列 在发送延时消息的时候并不是先投递到要发送的真实主题(real_topic)中,而是先投递到一些 Kafka 内部的主题(delay_topic ...

  8. rabbitmq死信队列和延时队列的使用

    死信队列&死信交换器:DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信之后,如果这个消息所在的队列存在x-dead-letter-exchange ...

  9. 面试官:RabbitMQ过期时间设置、死信队列、延时队列怎么设计?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 RabbitMQ我们经常的使用, ...

  10. RabbitMQ使用 prefetch_count优化队列的消费,使用死信队列和延迟队列实现消息的定时重试,golang版本

    RabbitMQ 的优化 channel prefetch Count 死信队列 什么是死信队列 使用场景 代码实现 延迟队列 什么是延迟队列 使用场景 实现延迟队列的方式 Queue TTL Mes ...

随机推荐

  1. github只下载某个文件或文件夹(使用GitZip插件)

    安装GitZip插件 (此安装过程需要梯子(不懂"梯子",百度一下就明白)) 1. 打开插件管理页面 方法一:打开Chrome浏览器(Edge浏览器同理),在Chrom地址栏输入c ...

  2. SpringBoot序列化、反序列化空字符串为null的三种方式

    一.需求:接收前端传入的""空字符串参数,有时候我们需要把它转为null SpringBoot项目 方式:①Jackson(推荐).②切面+反射.③注解+切面+反射 后两种方式,未 ...

  3. 累计预扣法个税,怎么算?(附excel)

    累计预扣法个税计算 依法纳税是每个公民的义务,但看着每个月递增的个税,你可能会发出疑问,这到底是怎么算的?这就要引出2019年1月1日实施新实施的个税法,累计预扣法.即自2019年1月1日起,居民个人 ...

  4. OLAP开源引擎对比之历史概述

    前言 OLAP概念诞生于1993年,工具则出现在更早以前,有史可查的第一款OLAP工具是1975年问世的Express,后来走进千家万户的Excel也可归为此类,所以虽然很多数据人可能没听过OLAP, ...

  5. 一、Doris演进史

    Apache Doris -- 为分析而生 Doris发展历程: Doris发展比较重要的关键节点与事件 #2008 - Doris1 :「筑巢引凤」的重要基石 早年,百度最主要的收入来源是广告.广告 ...

  6. 后宫酒店HaremHotel游戏Python游戏作弊方法游戏金币增加方法

    额,这个游戏嘛,内容就不说了, 顺便送给大家下载地址:https://store.zhaimaojun.cn/GameDetail/280 这个游戏刚开始起步其实比较困难,因为没钱啊,动不动就要花钱, ...

  7. C#TMS系统学习(ShippingNotice页面)

    C#TMS系统代码-业务页面ShippingNotice学习 学一个业务页面,ok,领导开完会就被裁掉了,很突然啊,他收拾东西的时候我还以为他要旅游提前请假了,还在寻思为什么回家连自己买的几箱饮料都要 ...

  8. C语言:如何打印星星

    //打印星星                  //k是两个上下部分都一样的空格      /*i        j               k                    k      ...

  9. 『手撕Vue-CLI』处理不同指令

    前言 在上一篇『手撕Vue-CLI』添加自定义指令中,已经实现了自定义指令的添加,但是指令还是比较简单的,只是简单的打印一句话,那么在实际运用场景中,可能会有更多的需求,比如可能需要在指令中传递参数, ...

  10. sqlerver 报错5120 无法为该请求检索数据 系统找不到指定路径

    背景: 数据库mdf文件所在盘符F盘被删除了,也就是文件不存在了,sqlserver管理器打开就报错5120,并且正常路径的数据库也不显示出来. 要让正常的数据库显示出来,就需要删除掉已经没有的数据库 ...