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