第一節: Redis之String類型和Hash類型的介紹和案例應用


一. String類型基礎

1.類型介紹

  典型的Key-Value集合,如果要存實體,需要序列化成字符串,獲取的時候需要反序列化一下。

2. 指令Api說明

3.常用Api說明

(1).StringSet:寫入數據,如果數據已經存在,則覆蓋;可以一次性存入1個key-value,也可以一次性存入多個Key-value集合,並且可以設置其過期時間。

(2).StringGet:讀取數據,可以一次性讀取一個key的value,也可以一次性讀取多個key對應的value的集合。

(3).StringAppend:在原有值的基礎上進行拼接追加.

(4).StringLength:獲取值的長度

(5).StringIncrement:數值自增n,返回自增后的值

(6).StringDecrement:數值自減n,返回自減后的值

3.通用Api操作

(1).Execute("FLUSHDB"):刪除所有數據,類似SqlServer的truncate

(2).KeyDelete:根據key刪除數據,可以刪除單個key,也可以刪除多個key

(3).KeyExists:判斷key是否存在,也可以單個key或者多個key

(4).KeyRename:重命名key

(5).KeyExpire:設置對應key的的過期時間

常用string類型Api代碼:

 1             //1. 最簡單的key-value的添加,如果該key已存在,則執行的是附加操作
 2             //可以設置過期時間哦
 3             bool a1 = db.StringSet("101", "keen");
 4 
 5             //2. 根據key獲取值
 6             string data1 = db.StringGet("101");
 7 
 8             //3. 在原有的value上進行追加
 9             //在原有值的基礎上追加,返回值是最終字符串的長度,如果沒有這個key,則當做一個新的key進行添加
10             long data2 = db.StringAppend("101", "Marren");
11 
12             //4. 獲取值的長度
13             long data3 = db.StringLength("101");
14 
16             //5. 數值自增/減,返回自增、自減后的值
17             db.StringSet("102", 10);
18             //自增2,可以自增負值
19             var data4 = db.StringIncrement("102", 2);
20             //自減5
21             var data5 = db.StringDecrement("102", 5);
22 
24             //6. 插入實體和讀取實體 (需要序列化和反序列化)
25             //由於序列化的原因,肯定不如存到Hash里速度快
26             UserInfor userInfor = new UserInfor()
27             {
28                 userName = "ypf",
29                 userPwd = "123456",
30                 userAge = 15
31             };
32             db.StringSet("userInfor_101", JsonConvert.SerializeObject(userInfor));
33             UserInfor data6 = JsonConvert.DeserializeObject<UserInfor>(db.StringGet("userInfor_101"));
34 
35             //7. 一次性添加多個key-value集合
36             Dictionary<string, string> dic = new Dictionary<string, string>();
37             dic.Add("103", Guid.NewGuid().ToString("N"));
38             dic.Add("104", Guid.NewGuid().ToString("N"));
39             dic.Add("105", Guid.NewGuid().ToString("N"));
40             dic.Add("106", Guid.NewGuid().ToString("N"));
41             dic.Add("107", Guid.NewGuid().ToString("N"));
42             dic.Add("108", Guid.NewGuid().ToString("N"));
43             var keyValues = dic.Select(p => new KeyValuePair<RedisKey, RedisValue>(p.Key, p.Value)).ToArray();
44             bool data7 = db.StringSet(keyValues);
45 
46             //8.獲取多個key的 value值集合
47             string[] keys = { "101", "102", "103" };
48             RedisKey[] redisKeys = keys.Select(u => (RedisKey)u).ToArray();
49             //此處如果是別的復雜類型要借助JsonConvert進行轉換
50             List<string> data8 = db.StringGet(redisKeys).Select(u => u.ToString()).ToList();

通用Api代碼: 

 1             //1. 刪除所有數據
 2             db.Execute("FLUSHDB");
 3 
 4             //2. 刪除單個key
 5             bool d1 = db.KeyDelete("101");  //刪除成功,返回true
 6             bool d2 = db.KeyDelete("ffff"); //刪除不存在的數據,返回false
 7 
 8             //3. 刪除多個key
 9             string[] arry = { "102", "103", "104" };
10             RedisKey[] keys = arry.Select(u => (RedisKey)u).ToArray();
11             long d3 = db.KeyDelete(keys);     //返回的是刪除成功的個數  
12 
13             //4. 判斷key是否存在(不推薦使用,會有並發問題)
14             bool d4 = db.KeyExists("102");
15             bool d5 = db.KeyExists("105");
16 
17             //5. 重命名key
18             bool d6 = db.KeyRename("108", "10086");
19 
20             //6. 設置key的過期時間(1分鍾后自動銷毀)
21             bool d7 = db.KeyExpire("107", DateTime.Now.AddMinutes(1));

 

二. String類型案例

1. 普通的Key-Value緩存

  string類型最簡單的一個應用就是Key-value緩存,value可以是簡單string、int,也可以是序列化后的實體,可以替代Session進行存儲,和其它緩存一樣,也可以設置緩存的過期時間(常用的鍵設計:表名_id  ,如: UserInfor_001 )

2. 秒殺-超賣問題

(1).背景

  某商家拿出來100件iphone11在某天的0點以超低價開賣,勢必有很多人等着購買. 簡單分析一下業務邏輯:判斷庫存,庫存>0,繼續往后執行下單邏輯(比如:插入訂單表、發貨地址表記錄等等); 否則提示顧客“商品已經被搶光”.

  看到這個需求,可能新手會直接操控關系型數據庫,並沒有采取一下措施,這樣就會導致同一時間進來的顧客判斷庫存都>0, 購買成功的人數就>100了,也就是出現了超賣現象,對於商家而言, 我太難了!!

常用的解決方案:

A. 下單頁面加Lock鎖,會造成大量的客戶等待卡死等現象. (注:只能鎖住單進程,如果是分布式,多個IIS,需要引進分布式鎖)

B. 利用樂觀鎖, 會造成一種現象即使該用戶是前100個進來的也沒有買到,不合理,不適用

C. 把下單的用戶加到隊列中,然后開啟另外一個線程從隊列中讀取進行下單,下單業務執行完,再出隊,下單成功/失敗 利用實時通訊技術通知客戶端 或者 客戶端主動刷新頁面進行查看結果.

(此處需要區分是出隊后,接着出隊,還是出隊后執行完下單業務才能出下一個對呢, 還要注意如果秒殺服務是個集群,無法保證原隊列的順序,且同樣存在超買超賣問題)

(2).利用Redis單線程的原理解決 

  事先將該商品的庫存初始化到Redis中,然后利用StringDecrement自減1同時返回自減后的值,如果值>=0,表示有庫存然后執行后面的下單邏輯,這里利用Redis很大程度的給關系型數據庫減壓了(不用查詢sqlserver 判斷庫存了), 庫存不足,直接返回庫存不足。

(單體Redis好用,集群Redis不適用,如果用集群的話,可以考慮用lua腳本把查庫存和減庫存寫到一起,這樣就可以用於集群了)

代碼分享:

 1  public static void CaseDemo1(IDatabase db)
 2         {
 3             //刪除所有數據
 4             db.Execute("FLUSHDB");
 5             //事先初始化庫存
 6             db.StringSet("order_Num", 10);
 7 
 8             List<Task> taskList = new List<Task>();
 9             for (int i = 0; i < 50; i++)   //模擬多個用戶並發
10             {
11                 var task = Task.Run(() =>
12                  {
13                      try
14                      {
15                         //先自減,獲取自減后的值
16                         int order_Num = (int)db.StringDecrement("order_Num", 1);
17                          if (order_Num >= 0)
18                          {
19                              //下面執行訂單邏輯(這里不考慮業務出錯的情況)
20                              Task.Delay(2000);
21                              Console.WriteLine("下單成功了");
22                          }
23                          else
24                          {
25                              Console.WriteLine("商品已經被搶光了");
26                          }
27 
28                      }
29                      catch (Exception ex)
30                      {
31                          Console.WriteLine(ex.Message);
32                          throw;
33                      }
34                  });
35                 taskList.Add(task);
36             }
37             Task.WaitAll(taskList.ToArray());
38         }
View Code

PS:關於秒殺問題,詳見后面單獨的章節:

                    第六節:秒殺業務/超買超賣的幾種解決思路

3. 點擊量、點贊量、訪問量

(1). 背景

  要統計一個網站的訪問次數,一個ip一天只能點擊一次。

(2). 解決方案

  先判斷是否存在該ip,如果不存在,以ip為key,value隨意,存儲到string類型中,同時利用StringIncrement對訪問次數自增1。

 1         /// <summary>
 2         /// 訪問量案例
 3         /// </summary>
 4         /// <returns></returns>
 5         public IActionResult Index()
 6         {
 7             //獲取Ip,這里利用個隨機數模擬ip效果
 8             var ip = Guid.NewGuid().ToString("N");
 9             if (!_redis.KeyExists(ip))
10             {
11                 //把該ip存進去,並且設置有效期為1天
12                 _redis.StringSet(ip, "隨意值", TimeSpan.FromDays(1));
13                 //同時訪問次數自增1
14                 _redis.StringIncrement("fw_count", 1);
15             }
16             ViewBag.FwCount = _redis.StringGet("fw_count");
17             return View();
18         }

總結: 

  String除了key-value當緩存外,主要是利用其原子性,圍繞自增自減並返回當前值(計數器作用)來使用,比如:單個網站的點擊量(訪問量、收藏量),單個商品的秒殺等等。如果是某個類別下多個物品的計數,同時要獲取物品的計數排名,則利用SortedSet來實現,比如:某個班級每個小孩的投票數並排序、某個欄目下每篇文章的閱讀數並排序 等等。

 PS:String和SortedSet具有計數器功能,String是針對單個,Sorted是針對某個類別下的多個或每一個,並且實現排序功能。 Hash類型也能實現某個類別下多個物品的計數,但它不具有排序功能。

 

三. Hash類型基礎

1.類型說明

  一個key,對應一個Key-Value集合, hashid -{key:value;key:value;key:value;}, 相當於value又是一個“鍵值對集合” 或者值是另外一個 Dictionary。

2. 常用指令Api

3.常用Api說明

(1).HashSet:存儲單個 hashid-key-value

(2).HashGet:單個value的獲取; 獲取1個hashid對應的所有key集合; 獲取1個hashid對應所有的key和value集合.

(3).HashDelete:刪除1個hashid-key; 刪除1個hashid-多個key

(4).HashIncrement:自增,返回自增后的值

(5).HashDecrement:自減,返回自減后的值

(6).HashExists:判斷 hashid-key 是否存在,返回true和value

 代碼分享:

 1             //1.添加
 2             db.HashSet("UserInfor_001", "name", "ypf");
 3             db.HashSet("UserInfor_001", "age", "27");
 4             db.HashSet("UserInfor_001", "sex1", "男1");
 5             db.HashSet("UserInfor_001", "sex2", "男2");
 6             db.HashSet("UserInfor_001", "sex3", "男3");
 7             db.HashSet("UserInfor_001", "sex4", "男4");
 8             db.HashSet("UserInfor_001", "name", "Marren");  //會覆蓋上面的值
 9             //沒找到一下把實體添加進去的方法
10             UserInfor userInfor = new UserInfor()
11             {
12                 userName = "ypf",
13                 userPwd = "123456",
14                 userAge = 15
15             };
16 
17             //2.獲取
18             //2.1 單個value的獲取
19             string age = db.HashGet("UserInfor_001", "age");
20             string name = db.HashGet("UserInfor_001", "name");
21             //2.2 獲取1個hashid對應所有的key的集合(前提必須是同數據類型的)
22             List<string> keyList = db.HashKeys("UserInfor_001").Select(u => (string)u).ToList();
23             //2.3 獲取hashid對應的所有key和value,必須保證該hashid對應的所有數據類型一致
24             Dictionary<string, string> dic = new Dictionary<string, string>();
25             foreach (var item in db.HashGetAll("UserInfor_001"))
26             {
27                 dic.Add(item.Name, item.Value);
28             }
29             //沒法一下獲取一個實體
30 
31             //3. 刪除
32             //單個key
33             bool d1 = db.HashDelete("UserInfor_001", "name");
34             //多個key
35             string[] dataKeyArry = { "sex1", "sex2", "sex3" };
36             RedisValue[] redisValueArry = dataKeyArry.Select(u => (RedisValue)u).ToArray();
37             long deleteNum = db.HashDelete("UserInfor_001", redisValueArry);
38 
39             //4. 自增,自減, 返回自增或自減后的值
40             db.HashSet("UserInfor_002", "age", 20);
41             long d2 = db.HashIncrement("UserInfor_002", "age", 2); //自增2,返回值為22
42             long d3 = db.HashDecrement("UserInfor_002", "age", 3); //自減3,返回值為19
43 
44             //5. 判斷數據是否存在
45             bool d4 = db.HashExists("UserInfor_002", "age");
46             bool d5 = db.HashExists("UserInfor_002", "age2");

 4. 優缺點

 

四. Hash類型案例

  Hash類型用於存儲某個類別下多個物品的存儲,也可以實現物品的計數器功能,但是和SortedSet相比,它不具有排序功能。

1. 購物車

 分析:

  以用戶id作為hashid,商品id作為key,商品數量作為value,利用自增和自減功能來實現增加商品數量和減少商品數量能。也可以刪除商品,獲取商品總數,獲取購物車中所有商品。

2. 存儲群聊消息。

  存儲群聊消息,比如:群名為hashid, 用戶id當做key,內容作為value。 這樣存儲可以,但是取數據的時候必須一下全部取出來,不能根據時間取前n條。

 

 

 

 

 

!

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


免責聲明!

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



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