使用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";
}

 

posted @ 2024-11-19 17:25  百年俊少  阅读(5)  评论(0编辑  收藏  举报