一. 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 : 原創部落格請在轉載時保留原文連結或在文章開頭加上本人部落格位址,否則保留追究法律責任的權利。