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