分佈式系統--封裝Redis消息隊列--消息隊列下的異步場景

一、什麼是消息隊列?
1、消息就是數據。
2、隊列有隊尾和隊頭,隊列有入隊和出隊,隊列先進先出。
3、生產者
存數據入口
4、消費者
取數據入口

二、推模型--發佈訂閱模型--阻塞

主動把消息推給訂閱者。
數據實時要求高,用推。

三、拉模型--生產者消費者模型--非阻塞
消費者自己去拉取數據。
數據實時要求不高,用拉。

四、它有哪些優勢?爲什麼使用它?
可以解決一些分佈式場景,如:異步場景,應用解耦,流量削峯,今天講講解決異步場景。

五、同步場景

客戶端發起一個請求,創建訂單,創建完訂單需要增加積分,然後發送短信,假設創建訂單花費1s,增加積分花費1s,發送短信花費1s,實則花費了3s。

缺陷:耗時時間太長。

六、異步場景

如果在訂單服務開啓1個異步線程去處理髮送短信服務,這樣做會有下面的缺陷。

缺陷:1個客戶端請求就是1個線程,在訂單服務端開啓的異步線程一多,就會導致訂單服務端的線程數減少,間接會導致訂單服務併發量降低。

七、消息隊列下的異步場景

 積分服務和短信服務訂閱消息隊列,消息隊列推送消息到積分服務,短信服務,這樣創建訂完單響應給客戶端只需要花費1s,但是又不會影響訂單服務的併發量。

 

八、封裝Redis消息隊列,解決消息隊列下的異步場景

1、封裝Redis消息隊列

namespace MyRedisUnitty
{
    /// <summary>
    /// 封裝Redis消息隊列
    /// </summary>
    public class RedisMsgQueueHelper : IDisposable
    {
        /// <summary>
        /// Redis客戶端
        /// </summary>
        public RedisClient redisClient { get; }

        public RedisMsgQueueHelper(string redisHost)
        {
            redisClient = new RedisClient(redisHost);
        }

        /// <summary>
        /// 入隊
        /// </summary>
        /// <param name="qKeys">入隊key</param>
        /// <param name="qMsg">入隊消息</param>
        /// <returns></returns>
        public long EnQueue(string qKey, string qMsg)
        {
            //1、編碼字符串
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(qMsg);

            //2、Redis消息隊列入隊
            long count = redisClient.LPush(qKey, bytes);

            return count;
        }

        /// <summary>
        /// 出隊(非阻塞) === 拉
        /// </summary>
        /// <param name="qKey">出隊key</param>
        /// <returns></returns>
        public string DeQueue(string qKey)
        {
            //1、redis消息出隊
            byte[] bytes = redisClient.RPop(qKey);
            string qMsg = null;
            //2、字節轉string
            if (bytes == null)
            {
                Console.WriteLine($"{qKey}隊列中數據爲空");
            }
            else
            {
                qMsg = System.Text.Encoding.UTF8.GetString(bytes);
            }

            return qMsg;
        }

        /// <summary>
        /// 出隊(阻塞) === 推
        /// </summary>
        /// <param name="qKey">出隊key</param>
        /// <param name="timespan">阻塞超時時間</param>
        /// <returns></returns>
        public string DeQueueBlock(string qKey, TimeSpan? timespan)
        {
            // 1、Redis消息出隊
            string qMsg = redisClient.BlockingPopItemFromList(qKey, timespan);

            return qMsg;
        }

        /// <summary>
        /// 獲取隊列數量
        /// </summary>
        /// <param name="qKey">隊列key</param>
        /// <returns></returns>
        public long GetQueueCount(string qKey)
        {
            return redisClient.GetListCount(qKey);
        }

        /// <summary>
        /// 關閉Redis
        /// </summary>
        public void Dispose()
        {
            redisClient.Dispose();
        }
    }
}

2、訂單服務發送積分消息和短信消息

namespace MyReidsMsgQueue.Async
{
    class OrderService
    {
        public string CreateOrder()
        {
            //統計時間
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            //1、訂單號生成
            string orderNo = GetOrderGenrator();

            //1.1、模擬存儲到數據庫,需要花費1s時間
            Thread.Sleep(1000);
            Console.WriteLine($"訂單:{orderNo}保存成功");

            //2、添加積分
            //Console.WriteLine($"*******************開始調用積分服務*******************");
            //PointsService pointsService = new PointsService();
            //pointsService.AddPoints(orderNo);
            //Console.WriteLine($"*******************積分服務調用完成*******************");

            ////3、發送短信
            //Console.WriteLine($"*******************開始調用短信服務*******************");
            //SmsService smsService = new SmsService();
            //smsService.SendSms(orderNo);
            //Console.WriteLine($"*******************短信服務調用完成*******************");

            //Redis優化
            using (var msgQueue = new RedisMsgQueueHelper("localhost:6379"))
            {
                // 1、發送積分消息
                msgQueue.EnQueue("My_Points", orderNo);

                // 2、發送短信消息
                msgQueue.EnQueue("My_Sms", orderNo);
            }

            stopwatch.Stop();
            Console.WriteLine($"訂單完成耗時:{stopwatch.ElapsedMilliseconds}ms");

            return orderNo;
        }

        /// <summary>
        /// 訂單號生成器
        /// </summary>
        /// <returns></returns>
        private string GetOrderGenrator()
        {
            Random ran = new Random();
            return "O-" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ran.Next(1000, 9999).ToString();
        }
    }
}

3、消費積分消息

namespace MyRedisPoints
{
    class Program
    {
        static void Main(string[] args)
        {
            //Redis優化
            using (var msgQueue = new RedisMsgQueueHelper("localhost:6379"))
            {
                Console.WriteLine("積分消息......");
                //1、獲取積分消息--反覆消費
                while (true)
                {
                    string msgPoints = msgQueue.DeQueueBlock("My_Points", TimeSpan.FromSeconds(60));

                    if (msgPoints != null)
                    {
                        //2、添加積分
                        PointsService pointsService = new PointsService();
                        pointsService.AddPoints(msgPoints);
                    }
                }
            }
        }
    }
}
amespace MyRedisPoints.Async
{
    public class PointsService
    {
        public void AddPoints(string orderNo)
        {
            //1、模擬積分添加到數據庫,需要花費1s時間
            Thread.Sleep(1000);
           
            Console.WriteLine($"增加積分:orderNo:{orderNo}成功");
        }
    }
}

4、消費短信消息

namespace MyRedisSms
{
    class Program
    {
        static void Main(string[] args)
        {
            //Redis優化
            using (var msgQueue = new RedisMsgQueueHelper("localhost:6379"))
            {
                Console.WriteLine("短信消息......");
                //1、獲取短信消息--反覆消費
                while (true)
                {
                    string msgSms = msgQueue.DeQueueBlock("My_Sms", TimeSpan.FromSeconds(60));

                    if (msgSms != null)
                    {
                        //2、發送短信
                        SmsService smsService = new SmsService();
                        smsService.SendSms(msgSms);
                    }
                }
            }
        }
    }
}
namespace MyRedisSms.Async
{
    public class SmsService
    {
        public void SendSms(string orderNo)
        {
            //1、模擬調用第三方短信接口發送短信,需要花費1s時間
            Thread.Sleep(1000);

            Console.WriteLine($"發送短信:orderNo:{orderNo}成功");
        }
    }
}

十、客戶端調用訂單服務

namespace MyReidsMsgQueue
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 異步處理
            {
                OrderService orderService = new OrderService();
                orderService.CreateOrder();
            }
            #endregion

            Console.ReadKey();
        }
    }
}

十一、運行效果

十二、項目結構

MyReidsMsgQueue裏的PointsService.cs和SmsService.cs是爲了演示同步場景訂單服務消耗的時間

 思考:從隊列中取出積分消息,去添加積分失敗了怎麼辦?也就是消費消息失敗了怎麼辦?

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章