4.【RabbitMQ實戰】- 發佈確認

生產者將信道設置成 confirm 模式,一旦信道進入 confirm 模式,所有在該信道上面發佈的消
息都將會被指派一個唯一的 ID
(從 1 開始),一旦消息被投遞到所有匹配的隊列之後,broker 就會
發送一個確認給生產者(包含消息的唯一 ID),這就使得生產者知道消息已經正確到達目的隊列了,
如果消息和隊列是可持久化的,那麼確認消息會在將消息寫入磁盤之後發出,broker 回傳給生產
者的確認消息中 delivery-tag 域包含了確認消息的序列號,此外 broker 也可以設置basic.ack 的
multiple 域,表示到這個序列號之前的所有消息都已經得到了處理。
confirm 模式最大的好處在於他是異步的,一旦發佈一條消息,生產者應用程序就可以在等信道
返回確認的同時繼續發送下一條消息,當消息最終得到確認之後,生產者應用便可以通過回調方
法來處理該確認消息,如果 RabbitMQ 因爲自身內部錯誤導致消息丟失,就會發送一條 nack 消息,
生產者應用程序同樣可以在回調方法中處理該 nack 消息

  • 設置隊列持久化
  • 設置消息持久化
  • 發佈確認(RabbitMQ 告訴生產者已經確定持久化了)

只有設置了上述三個條件才能保證消息一定不會丟失

image.png

發佈確認三種模式

單個確認

這是一種簡單的確認方式,它是一種同步確認發佈的方式,也就是發佈一個消息之後只有它被確認發佈,後續的消息才能繼續布,waitForConfirmsOrDie(long)這個方法只有在消息被確認的時候才返回,如果在指定時間範圍內這個消息沒有被確認那麼它將拋出異常。 這種確認方式有一個最大的缺點就是:發佈速度特別的慢,因爲如果沒有確認發佈的消息就會 阻塞所有後續消息的發佈,這種方式最多提供每秒不超過數百條發佈消息的吞吐量。當然對於某些應用程序來說這可能已經足夠了

image.png

批量確認

上面那種方式非常慢,與單個等待確認消息相比,先發布一批消息然後一起確認可以極大地提高吞吐量,當然這種方式的缺點就是:當發生故障導致發佈出現問題時,不知道是哪個消息出現問題了,我們必須將整個批處理保存在內存中,以記錄重要的信息而後重新發布消息。當然這種方案仍然是同步的,也一樣阻塞消息的發佈。

image.png

異步發佈確認

image.png

處理異步未確認的消息

最好的解決的解決方案就是把未確認的消息放到一個基於內存的能被髮佈線程訪問的隊列, 比如說用 ConcurrentQueue 這個隊列在 confirm callbacks 與發佈線程之間進行消息的傳遞。

發佈確認代碼

using rabbitmq.common;

using RabbitMQ.Client.Events;

using System;
using System.Collections.Concurrent;
using System.Text;

namespace PublishConfirm.Producer
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("發佈確認");
            //SinglePublishConfirm();  //單個發佈確認耗時:00:00:00.6743354
            //MulitPublishConfirm(); //批量發佈確認耗時:00:00:00.4526167
            PublishConfirmAsync(); // 異步發佈確認:00:00:00.3534272
        }

        /// <summary>
        /// 單個發佈確認
        /// </summary>
        public static void SinglePublishConfirm()
        {
            var begin = DateTime.Now;
            using var channel = RabbitmqUntils.GetChannel();
            string queueName = Guid.NewGuid().ToString();
            channel.QueueDeclare(queue:queueName,durable:false,exclusive:false,autoDelete:false,null);
            // 開啓發布確認
            channel.ConfirmSelect();
            var begintime = DateTime.Now;
            for (int i = 0; i < 100; i++)
            {                                                   
                var body = Encoding.UTF8.GetBytes($"{i}");
                channel.BasicPublish(exchange: "", queueName, false, null, body);
                if (channel.WaitForConfirms())// 等待所有消息確認,如果所有的消息都被服務端成功接收返回true,只要有一條沒有被成功接收就返回false
                {
                    Console.WriteLine($"消息發送成功:{i}");
                }
                else
                {
                    //服務端返回 false 或超時時間內未返回,生產者可以消息重發 需添加補救措施
                }

            }
            var end = DateTime.Now;
            Console.WriteLine($"單個發佈確認耗時:{end - begin}");
        }

        /// <summary>
        /// 批量發佈確認
        /// </summary>
        public static void MulitPublishConfirm()
        {
            var begin = DateTime.Now;
            using var channel = RabbitmqUntils.GetChannel();
            string queueName = Guid.NewGuid().ToString();
            channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, null);
            // 開啓發布確認
            channel.ConfirmSelect();
            var begintime = DateTime.Now;
            for (int i = 0; i < 1000; i++)
            {
                var body = Encoding.UTF8.GetBytes($"{i}");
                channel.BasicPublish(exchange: "", queueName, false, null, body);

                // 批量確認:100條確認異常
                if (i%100 == 0)
                {

                    if (channel.WaitForConfirms())// 等待所有消息確認,如果所有的消息都被服務端成功接收返回true,只要有一條沒有被成功接收就返回false
                    {
                        Console.WriteLine($"消息發送成功:{i}");
                    }
                    else
                    {
                        //服務端返回 false 或超時時間內未返回,生產者可以消息重發 需添加補救措施
                    }
                }
               

            }
            var end = DateTime.Now;
            Console.WriteLine($"批量發佈確認耗時:{end - begin}");
        }

        /// <summary>
        /// 異步發佈確認
        /// </summary>
        public static void PublishConfirmAsync()
        {
            var begin = DateTime.Now;
            using var channel = RabbitmqUntils.GetChannel();
            string queueName = Guid.NewGuid().ToString();
            channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, null);

            // 開啓發布確認
            channel.ConfirmSelect();
            ConcurrentDictionary<ulong, string> confirmDic = new ConcurrentDictionary<ulong, string>();
            var begintime = DateTime.Now;
            for (int i = 1; i <= 1000; i++)
            {
                string msg = $"msg_{i}";
                var body = Encoding.UTF8.GetBytes(msg);
                confirmDic.TryAdd(channel.NextPublishSeqNo, msg);
                channel.BasicPublish(exchange: "", queueName, false, null, body);
            }

            // 監聽確認的消息
            channel.BasicAcks += (sender, e) =>
            {
                Console.WriteLine($"監聽確認的消息的序列號:{e.DeliveryTag}");
                confirmDic.Remove(e.DeliveryTag, out string body);
                Console.WriteLine($"刪除確認的消息:{body}");
            };

            // 監聽未確認的消息
            channel.BasicNacks += (sender, e) =>
            {
                Console.WriteLine($"監聽未確認的消息序列號:{e.DeliveryTag}");
                confirmDic.TryGetValue(e.DeliveryTag, out string body);
                Console.WriteLine($"監聽未確認的消息:{body}");

            };
            var end = DateTime.Now;
            Console.WriteLine($"異步發佈確認:{end - begin}");
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章