一、什麼是消息隊列?
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是爲了演示同步場景訂單服務消耗的時間
思考:從隊列中取出積分消息,去添加積分失敗了怎麼辦?也就是消費消息失敗了怎麼辦?