第三節: List類型的介紹、生產者消費者模式、發布訂閱模式


一. List類型基礎

1.介紹

  它是一個雙向鏈表,支持左進、左出、右進、右出,所以它即可以充當隊列使用,也可以充當棧使用。

(1). 隊列:先進先出, 可以利用List左進右出,或者右進左出(ListLeftPush和ListRightPop配合 、 ListRightPush和ListLeftPop配合)

(2). 棧:先進后出,可以利用List左進左出,或者右進右出

2. 常用指令Api

 

3.常用Api

(1). ListLeftPush:從左側添加,返回集合總數

(2). ListRightPush:從右側添加,返回集合總數

(3). ListLeftPop:從左側取1個值,並刪除

(4). ListRightPop:從右側取1個值,並刪除

(5). ListInsertBefore:指定的key指定value之前(左邊)插入1個值

(6). ListInsertAfter:指定的key指定value之后(右邊)插入1個值

(7). ListGetByIndex:獲取key的指定索引對應的value值(從左往右算)

(8). ListRange:獲取key的所有value,數據類型得一致 (也可以獲取指定索引之間的value值,帶擴展)

(9). ListLength:獲取指定key的數據的個數

(10). ListRemove:刪除指定key對應的指定value值,返回刪除的個數

(11). ListRightPopLeftPush:從List1右側取一個值加到List2左側,返回的是右側取出來的這個值

 代碼分享:

 1             //1.從左側添加
 2             //單個,返回集合總數
 3             db.ListLeftPush("group1", "你好1");
 4             db.ListLeftPush("group1", "你好2");
 5             db.ListLeftPush("group1", "你好3");
 6             //多個
 7             string[] dList1 = { "你好4", "你好5" };
 8             RedisValue[] redisValue = dList1.Select(u => (RedisValue)u).ToArray();
 9             var d1 = db.ListLeftPush("group1", redisValue);
10 
11             //2.從右側添加
12             db.ListRightPush("group1", "你好6");
13 
14             //3.從左側取1個值,並刪除
15             //var v1=db.ListLeftPop("group1");
16 
17             //4.從右側取1個值並刪除
18             //var v2 = db.ListRightPop("group1");
19 
20             //5. 在List的指定的key指定value之前(左邊)插入1個值
21             db.ListInsertBefore("group1", "你好3", "ypf001");
22 
23             //6. 在List的指定的key指定value之后(右邊)插入1個值
24             db.ListInsertAfter("group1", "你好3", "ypf002");
25 
26             //7. 獲取key指定索引的值(從左往右算)
27             var d2 = db.ListGetByIndex("group1", 0);
28             var d3 = db.ListGetByIndex("group1", 2);
29 
30             //8. 獲取key的所有數據,數據類型得一致
31             var d4 = db.ListRange("group1").Select(u => (string)u).ToList();
32             //獲取key的前4條數據(從左往右)
33             var d44 = db.ListRange("group1", 0, 3).Select(u => (string)u).ToList();
34 
35             //9.獲取指定key的數據的個數
36             long d5 = db.ListLength("group1");
37 
38             //10. 刪除指定key對應的指定value值,返回刪除的個數
39             db.ListLeftPush("group1", "你好");
40             db.ListLeftPush("group1", "你好");
41             long d6 = db.ListRemove("group1", "你好");
42 
43             //11. 從List1右側取一個值加到List2左側,返回的是右側取出來的這個值
44             db.ListLeftPush("group2", "你好1");
45             db.ListLeftPush("group2", "你好2");
46             db.ListLeftPush("group2", "你好3");
47             db.ListLeftPush("group3", "哈哈1");
48             db.ListLeftPush("group3", "哈哈2");
49             db.ListLeftPush("group3", "哈哈3");
50             //從隊列group2右側取出來一個值放到group3中
51             var d7 = db.ListRightPopLeftPush("group2", "group3");

 

二. 案例分析

(一). 消息隊列 (生產者消費者模式)

PS:生產者消費模式:可以是多個生產者,多個消費者,但是生成的數據按順序進入隊列,但是每個數據只能被一個消費者消費。

1. 異步處理

  (1). 將同步業務:創建訂單→增加積分→發送短信,改為創建訂單后 存放到兩個消息隊列中(積分隊列和短信隊列),然后積分業務和短信業務分部去隊列中讀取,執行各自的業務

  (2). 注冊成功發郵件通知:很多場景注冊成功后要給用戶發一封郵件提示,但這封郵件實時性要求並不是很高,而且發郵件一般是調用第三方接口進行發送,有時候可能會很慢或者故障了, 針對這種情況,借助隊列采用生產者消費者模式非常適合。

2. 應用解耦

  將原先訂單系統和庫存系統的強依賴關系,改為中間引入消息隊列,這樣二者都依賴消息隊列做中介.

3. 流量削鋒

  秒殺服務,下單的用戶加到隊列中,然后開啟另外一個線程從隊列中讀取進行下單,下單成功/失敗 利用實時通訊技術通知客戶端 或者 客戶端主動刷新頁面進行查看結果,這里要結合實際架構(單體or集群)分析秒殺情況,不能一概而論,詳見后面秒殺章節。

4. 即時通訊

  考慮到同時很多人發送,前端頁面的渲染會有點吃不消,這里可以采用 群id 當做隊列的key,發送的消息當做value,存入隊列中,然后開啟一個新的線程從里面讀取, 可以一下獲取20條,獲取的同時並刪除,如果隊列為空,則休息幾秒中,再次獲取。

針對群聊的代碼分享

  以群聊為例,利用ListLeftPush方法,以“群id”當做key,以發送人id、發送內容、時間組合當做value,從左側存儲到隊列;然后利用Core中的BackService類開啟后台線程利用ListRightPop 進行讀取,同時要在ConfigureService中進行注冊。

代碼分享:

 1        /// <summary>
 2         /// 測試群聊頁面
 3         /// (PS:不斷刷新即可)
 4         /// </summary>
 5         /// <returns></returns>
 6         public IActionResult Index()
 7         {
 8             string userId = Guid.NewGuid().ToString("N");
 9             string msg = "哈哈" + new Random().Next(1, 10000);
10             SendMessage(userId, msg);
11             return View();
12         }
13 
14         /// <summary>
15         ///群聊發送消息接口 
16         /// </summary>
17         /// <param name="userId">用戶id</param>
18         /// <param name="msg">發送的內容</param>
19         /// <returns></returns>
20         public string SendMessage(string userId, string msg)
21         {
22             try
23             {            
24                 string groupName = "classParty";  //群名
25                 string sendContent = $"{userId}_{msg}_{DateTime.Now}"; //內容
26                 //存入隊列
27                 _redis.ListLeftPush(groupName, sendContent);
28                 return "ok";
29             }
30             catch (Exception ex)
31             {
32                 return "error";
33             }
34         }

后台服務及注冊:

 1  public class SendService : BackgroundService
 2     {
 3         private readonly IDatabase _redis;
 4         public SendService(RedisHelp redisHelp)
 5         {
 6             _redis = redisHelp.GetDatabase();
 7         }
 8         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 9         {
10             while (!stoppingToken.IsCancellationRequested)
11             {
12                 try
13                 {
14                     //實際情況,這里有幾個群,開幾個線程執行     
15                     List<string> msgList = new List<string>();
16                     Stopwatch stopwatch = new Stopwatch();
17                     stopwatch.Start();
18                     //要么沒有更多待發消息立即發給客戶端,要么累積滿1秒鍾待發消息后發送給客戶端
19                     while (true)
20                     {                        
21                         string msg = _redis.ListRightPop("classParty");
22                         if (!string.IsNullOrEmpty(msg)) 
23                         {
24                             msgList.Add(msg);
25                         }
26                         else
27                         {
28                             await Task.Delay(500);
29                         }
30                         //滿一段時間的消息向客戶端發送
31                         if (stopwatch.Elapsed>TimeSpan.FromSeconds(1))
32                         {
33                             stopwatch.Stop();
34                             if (msgList.Any())
35                             {
36                                 //將這一批消息發送給客戶端                             
37                                 //需要重新滯空msgList
38                             }
39                         }
40                     }                 
41                 }
42                 catch (Exception)
43                 {
44                     throw;
45                 }
46             }
47         }
48     }
1  public void ConfigureServices(IServiceCollection services)
2   {
3             //注冊后台服務
4             services.AddHostedService<SendService>();
5    }

(二). 解決查詢緩慢問題

  比如發帖網站,會有非常多的帖子,而且數量每日俱增,首頁顯示的是最新發布的10條帖子,顯示的是:發帖人名稱 和 發帖標題,如果從數據庫中查詢可能會非常慢,這時候可以把發帖人名稱和

發帖標題(包括帖子id),存到Redis隊列中,這個時候利用 棧 的特性,ListGetByIndex:獲取key指定索引的值(從左往右算), 獲取前10條數據,用於顯示,查看詳情的時候,再根據帖子的id到數據庫中查。

 

三. 發布訂閱模式

 1. 說明

  發布者發布一條消息,所有的訂閱者都能收到。

2. 案例背景

  以微博為例(或者微信的訂閱號),博主A,博主B都關注了博主C、博主D,在博主A(或B)的版面,應該顯示的是博主C和博主D發布的最新博文(最新發布的在最上面),換句話說博主C或者博主D每發一篇博文,都要推送給關注他們的博主A和博主B。

3. 技術分析

(1). 數據結構的設計:一個博主對應一個List鏈表,用來存儲該博主應該顯示的博文消息。 以博主的用戶id作為key,博文消息的id作為value。

(2). 博主C每發一條博文,就需要向關注他的粉絲(A和B)對應的鏈表中分別存儲一下 該博文消息的id。

(3). 博主D每發一條博文,就需要向關注他的粉絲(A和B)對應的鏈表中分別存儲一下 該博文消息的id。

(4).  博主A就可以到自己對應的鏈表中利用的特性,獲取最新的n條博文消息id。

PS: 拿到博文消息id了,剩下的就容易了,根據id去關系型數據中查內容就很快了,或者也可以將標題或者內容的前100字也存儲到Redis中,便於頁面顯示(這樣value的格式就是:博文id-博文標題-博文內容前100字)。

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 本人才疏學淺,用郭德綱的話說“我是一個小學生”,如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM