使用RabbitMq原生实现延迟队列
可以看 这篇文章:RabbitMQ从零到集群高可用.NetCore(.NET5) - 死信队列,延时队列 - 包子wxl - 博客园
这个文章中的
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: true);
有问题,当有多条时,第一条不确认就阻塞了后边的消息,所以要去掉
使用RabbitMQ的TTL(Time-To-Live)特性和死信交换机(Dead Letter Exchange, DLX)来实现延迟投递是一种间接的方法。在这个方法中,消息首先被发送到一个具有TTL设置的队列。当消息在队列中存活的时间超过TTL时,它会被丢弃到配置的死信交换机,然后你可以在该交换机上配置路由规则将消息转发到目标队列
发消息的代码:
var factory = new ConnectionFactory { HostName = RabbitMqAccount.Host, Port = RabbitMqAccount.Port, UserName = RabbitMqAccount.UserName, Password = RabbitMqAccount.Password }; var connection = factory.CreateConnection(); // 创建连接对象 var channel = connection.CreateModel(); // 声明正常交换机 要先启服务端,再启客户端 channel.ExchangeDeclare(ExchangeNameStr.normalExchange, "direct"); foreach (var item in users) { string msg = JsonConvert.SerializeObject(item); Random rng = new Random(Guid.NewGuid().GetHashCode()); int time = rng.Next(1 * 60, 60 * 60) * 1000; SendDelayedMessage(channel, msg,time); } void SendDelayedMessage(IModel channel, string message, int delayMilliseconds) { var properties = channel.CreateBasicProperties(); properties.Expiration = delayMilliseconds.ToString(); // 发送消息到正常队列 channel.BasicPublish(ExchangeNameStr.normalExchange, ExchangeNameStr.routingKey, false, properties, Encoding.UTF8.GetBytes(message)); logComm.Debug($"[x] Sent {message} with delay:" + properties.Expiration); }
消费者的代码:
var factory = new ConnectionFactory { HostName = RabbitMqAccount.Host, Port = RabbitMqAccount.Port, UserName = RabbitMqAccount.UserName, Password = RabbitMqAccount.Password }; var connection = factory.CreateConnection(); // 创建连接对象 var channel = connection.CreateModel(); // 声明死信交换机 channel.ExchangeDeclare(ExchangeNameStr.dlxExchange, "direct"); // 声明死信队列 var dlxArgs = new Dictionary<string, object> { {"x-dead-letter-exchange", ""}, // 消息从死信队列消费后不再进入其它队列 }; channel.QueueDeclare(ExchangeNameStr.dlxQueue, true, false, false, dlxArgs); channel.QueueBind(ExchangeNameStr.dlxQueue, ExchangeNameStr.dlxExchange, ExchangeNameStr.routingKey); // 声明正常交换机 channel.ExchangeDeclare(ExchangeNameStr.normalExchange, "direct"); // 声明正常队列 var normalArgs = new Dictionary<string, object> { {"x-dead-letter-exchange", ExchangeNameStr.dlxExchange} // 死信交换机 }; channel.QueueDeclare(ExchangeNameStr.normalQueue, true, false, false, normalArgs); channel.QueueBind(ExchangeNameStr.normalQueue, ExchangeNameStr.normalExchange, ExchangeNameStr.routingKey); ConsumeDelayedMessages(channel); void ConsumeDelayedMessages(IModel channel) { var consumer = new EventingBasicConsumer(channel); consumer.Received += async (model, ea) => { var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); User user = JsonConvert.DeserializeObject<User>(message); if (user != null) { logComm.Debug($"[x] Received {user.ToSelfString()} from DLX."); } }; channel.BasicConsume(ExchangeNameStr.dlxQueue, true, consumer); }
正常交互机里的消息不消费,过期后进入死信交换机,消费端只消费死信交换机队里里的消息。
public class ExchangeNameStr { public static readonly string normalExchange = "SJSD-DelayedExchange"; public static readonly string normalQueue = "SJSD_Delayed_Queue"; public static readonly string dlxExchange = "SJSD-DLXExchange"; public static readonly string dlxQueue = "SJSD_Dlx_Queue"; public static readonly string routingKey = "ActivityData"; } /// <summary> /// RabbitMq的地址和账号 /// </summary> public class RabbitMqAccount { public static readonly string Host = "127.0.0.1"; public static readonly int Port = 5672; public static readonly string UserName = "UserName"; public static readonly string Password = "Password"; }