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. 18.基于Consul的服务发现和ConsulManager管理

    192.168.10.14 prometheus.consul 192.168.10.100 各类服务 一.基于Consul的服务发现 Consul 是由 HashiCorp 开发的一个支持多数据中心 ...

  2. aspnetcore项目中kafka组件封装

    前段时间在项目中把用到kafka组件完全剥离开出来,项目需要可以直接集成进去.源代码如下: liuzhixin405/My.Project (github.com) 组件结构如下,代码太多不一一列举, ...

  3. 程序员天天 CURD,怎么才能成长,职业发展的思考 ?

    前言 关于程序员成长的话题,我前面写过一篇文章 - 程序员天天CURD,职业生涯怎么发展的思考. 现在回头看,对程序员这个职业发展的认识以及怎么发展还是有一些局限性.有一句话是这么说的:人的成长就是不 ...

  4. 基于改进MFCC特征和卷积递归神经网络的心音分类

    具体的软硬件实现点击http://mcu-ai.com/MCU-AI技术网页_MCU-AI人工智能 心音分类在心血管疾病的早期发现中起着至关重要的作用,特别是对于小型初级卫生保健诊所.尽管近年来心音分 ...

  5. StarCoder2-Instruct: 完全透明和可自我对齐的代码生成

    指令微调 是一种技术,它能让大语言模型 (LLMs) 更好地理解和遵循人类的指令.但是,在编程任务中,大多数模型的微调都是基于人类编写的指令 (这需要很高的成本) 或者是由大型专有 LLMs 生成的指 ...

  6. iceoryx源码阅读(三)——共享内存通信(一)

    目录 0 导引 1 整体通信结构 2 RelativePointer 2.1 原理 2.2 PointerRepository 2.3 构造函数 2.4 get函数 3 ShmSafeUnmanage ...

  7. IDEA 2020 Maven编译问题:Error:(3, 32) java: 程序包org.springframework.boot不存在。

    今天在编译Maven项目时,包已经加载好了,也进行了打包,途中均没有报错.package -- Install -- test 都没用问题,但是,一致性run,就会一直报错,如图 检查打包位置,包也在 ...

  8. GROK 表达式

    GROK 表达式 常用表达式 标识:USERNAME 或 USER 正则:[a-zA-Z0-9._-]+ 名称:用户名 描述:由数字.大小写及特殊字符(._-)组成的字符串 例子:1234.Bob.A ...

  9. 鸿蒙HarmonyOS实战-Stage模型(AbilityStage组件容器)

    前言 组件容器是一种用于管理和组织组件的工具或环境.它可以提供一些基本的功能,如组件的注册.创建.销毁和查找.组件容器通常会维护一个组件的依赖关系,并负责将这些依赖注入到组件中.它还可以提供一些其他的 ...

  10. wxPython==4.2.1 aui.AuiToolBar 如何去掉烦人的抓手?

    aui.AuiToolBar 如何去掉烦人的抓手? 最近在用wxPython做一些GUI小应用,发现工具栏总有几个点(抓手),很影响美观,如下: 目前官方没有提供隐藏抓手的功能,需要更改源码的auib ...