一. Geo類型
1. 類型說明
Geo 是 Redis 3.2 版本後新增的資料類型,用來儲存興趣點(POI,point of interest)的坐标資訊。可以實作計算兩 POI 之間的距離、擷取一個點周邊指定距離的 POI。
2. 常用Api
(1).GeoAdd:添加POI點
(2).GeoDistance:擷取兩點之間的最短距離
(3).GeoPosition:擷取某個點的坐标
(4).GeoRadius:擷取某個點(不一定是POI)周邊xx米以外的點
(5).GeoRemove:删除某個點
代碼分享:
1 //1. 添加所有商店的地理位置
2 db.GeoAdd("ShopsGeo", new GeoEntry(116.34039, 39.94218, "name1"));
3 db.GeoAdd("ShopsGeo", new GeoEntry(116.340934, 39.942221, "name2"));
4 db.GeoAdd("ShopsGeo", new GeoEntry(116.341082, 39.941025, "name3"));
5 db.GeoAdd("ShopsGeo", new GeoEntry(116.340848, 39.937758, "name4"));
6 db.GeoAdd("ShopsGeo", new GeoEntry(116.342982, 39.937325, "name5"));
7 db.GeoAdd("ShopsGeo", new GeoEntry(116.340866, 39.936827, "name6"));
8
9 //2. 計算商店name1和name2之間的距離(機關m)
10 double? dist = db.GeoDistance("ShopsGeo", "name1", "name5", GeoUnit.Meters);
11
12 //3. 擷取name1商店的坐标
13 GeoPosition? pos = db.GeoPosition("ShopsGeo", "name1");
14
15 //4. 擷取一個 name2 周邊的200内的點:
16 GeoRadiusResult[] results = db.GeoRadius("ShopsGeo", "name2", 200, GeoUnit.Meters);
17 foreach (GeoRadiusResult result in results)
18 {
19 Console.WriteLine("Id=" + result.Member + ",位置" + result.Position + ",距離" + result.Distance);
20 }
21
22 //5. 擷取一個坐标(116.34092, 39.94223)(這個坐标不一定是 POI)周邊的 POI:
23 GeoRadiusResult[] results2 = db.GeoRadius("ShopsGeo", 116.34092, 39.94223, 200, GeoUnit.Meters);
24 foreach (GeoRadiusResult result in results2)
25 {
26 Console.WriteLine("Id=" + result.Member + ",位置" + result.Position + ",距離" + result.Distance);
27 }
28
29 //6. 删除
30 bool d1 = db.GeoRemove("ShopsGeo", "name2");
3. 案例
地圖上點相關的操作,方圓xx米内有多少個商店,某兩個商店間的距離
二. Redis批量操作和事務
1. 批量操作
Batch會把所需要執行的指令打包成一條請求發到Redis,然後一起等待傳回結果。這樣批量操作的速度就大大提升。 利用CreateBatch和Execute方法,僅支援異步方法哦。
1 public static void BatchDemo(IDatabase db)
2 {
3 IBatch batch = db.CreateBatch();
4 batch.StringSetAsync("keen1", "111");
5 batch.StringSetAsync("keen2", "222");
6 batch.Execute();
7
8 }
2. 事務
Redis的操作都是原子性單線程的,如果一次性操作很多,基本上每個方法都支援批量操作,但是如果操作的資料類型不同,可以使用Redis的事務進行包裹。 CreateTransaction和Execute方法
1 public static void TransDemo(IDatabase db)
2 {
3 var trans = db.CreateTransaction();
4 trans.StringSetAsync("keen1", "111");
5 trans.StringSetAsync("keen2", 222);
6 bool result = trans.Execute();
7 }
3. 差別
批量操作假設裡面有一個出錯,不會整體復原,而事務要麼都成功,要麼都失敗。 redis的事務不會復原,事務也不是原子性操作的。
三. Redis分布式鎖
1. 背景
在傳統的單體項目中,即部署到單個IIS上,針對并發問題,比如進銷存中的出庫和入庫問題,多個人同時操作,屬于一個IIS程序中多個線程并發操作的問題,這個時候可以引入線程鎖lock/Monitor等,輕松解決這類問題。但是随着業務量的逐漸增大,比如"秒殺業務",肯定是叢集,這個時候線程鎖已經沒用了,必須引入分布式鎖。
常見的分布式鎖有:資料庫、zookeeper、redis。
2. 技術分析
秒殺業務叢集同時通路DB,很容易出現超買超賣問題,如下圖:

分析:
解決方案:
在秒殺服務叢集和DB之間引入Redis(或者Redis叢集),無論是Redis單體還是叢集,分布式鎖都是上一個解鎖了下一個才繼續加鎖,引入Redis叢集的目的是防止Redis崩潰,而不是加快速度,這樣保證了最終到DB上上的操作是按順序依次進行的,進而解決了超賣問題。如下圖:
3. 代碼實戰
StackExchange.Redis中加鎖和解鎖的api分别是:LockTake和LockRelease。 bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); 三個參數的含義分别是:
(1). 鎖名
(2). 誰加的的鎖
(3). 逾時時間,過期自動釋放,防止死鎖。
代碼如下:(加鎖的時候要循環擷取鎖,直到擷取為止)
1 /// <summary>
2 /// 分布式鎖 業務
3 /// </summary>
4 /// <param name="db"></param>
5 public static void DfsLockDemo(IDatabase db)
6 {
7
8 Lock(db);
9 try
10 {
11 Console.WriteLine("業務執行中......");
12 Thread.Sleep(8000);
13 Console.WriteLine("業務執行完畢");
14 }
15 catch (Exception)
16 {
17 //釋放鎖
18 UnLock(db);
19 }
20 finally
21 {
22 //釋放鎖
23 UnLock(db);
24 }
25 }
26
27 /// <summary>
28 /// 加鎖
29 /// </summary>
30 /// <param name="db"></param>
31 public static void Lock(IDatabase db)
32 {
33 RedisValue token = Environment.MachineName;
34 while (true)
35 {
36 bool flag = db.LockTake("myLock", token, TimeSpan.FromSeconds(10)); //10秒後自動釋放
37 if (flag)
38 {
39 //表示擷取成功,跳出while
40 break;
41 }
42 else
43 {
44 Console.WriteLine("擷取失敗,繼續擷取");
45 Thread.Sleep(200);
46
47 }
48 }
49 }
50 /// <summary>
51 /// 解鎖
52 /// </summary>
53 /// <param name="db"></param>
54 public static void UnLock(IDatabase db)
55 {
56 RedisValue token = Environment.MachineName;
57 db.LockRelease("myLock", token);
58 }
模拟兩個項目運作效果:
秒殺案例其它思路或者詳細解決方案見: 第六節:秒殺業務/超買超賣的幾種解決思路
!
- 作 者 : Yaopengfei(姚鵬飛)
- 部落格位址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 本人才疏學淺,用郭德綱的話說“我是一個國小生”,如有錯誤,歡迎讨論,請勿謾罵^_^。
- 聲 明2 : 原創部落格請在轉載時保留原文連結或在文章開頭加上本人部落格位址,否則保留追究法律責任的權利。