Redis鎖的簡單應用
Redis Cluster http://www.cnblogs.com/tdws/p/7710545.html
其實說多執行緒修改資料也不合適,畢竟redis服務端是單執行緒的,所有命令序列執行,只是在客戶端併發傳送命令的時候,導致序列的命令一些排列問題和網路時間差等造成資料不一致。本文雖然是數字的加減,但是為了說明鎖的情況,故意不是用原子命令incr。也並非分散式鎖的正確實現,沒有考慮一些重入性等,稍後會整理一篇分散式鎖的實踐。
Redis分散式鎖 http://www.cnblogs.com/tdws/p/5808528.html
ZK+curator 分散式鎖 http://www.cnblogs.com/tdws/p/5874686.html
先配上一個簡易的RedisHelper,一個set值,一個get值,一個設定併發鎖,以便在我後面的操作中,你能清楚我究竟做了什麼。
1 public class RedisHelper 2 { 3 public RedisClient client = new RedisClient("127.0.0.1", 6379); 4 public void Set<T>(string key, T val) 5 { 6 client.Set(key, val); 7 } 8 public T Get<T>(string key) 9 { 10 var result = client.Get<T>(key); 11 return result; 12 } 13 public IDisposable Acquire(string key) 14 { 15 return client.AcquireLock(key); 16 } 17 }
下面看一下併發程式碼,我只new了兩個Thread。兩個執行緒同時想訪問同一個key,分別訪問五萬次,在併發條件下,我們很難保證資料的準確性,請比較輸出結果。
1 static void Main(string[] args) 2 { 3 RedisHelper rds = new RedisHelper(); 4 rds.Set<int>("mykey1", 0); 5 Thread myThread1 = new Thread(AddVal); 6 Thread myThread2 = new Thread(AddVal); 7 myThread1.Start(); 8 myThread2.Start(); 9 Console.WriteLine("等待兩個執行緒結束"); 10 Console.ReadKey(); 11 } 12 13 public static void AddVal() 14 { 15 RedisHelper rds = new RedisHelper(); 16 for (int i = 0; i < 50000; i++) 17 { 18 19 int result = rds.Get<int>("mykey1"); 20 rds.Set<int>("mykey1", result + 1); 21 22 } 23 Console.WriteLine("執行緒結束,輸出" + rds.Get<int>("mykey1")); 24 }
是的,和我們單執行緒,跑兩個50000,會輸出100000。現在是兩個併發執行緒同時跑在由於併發造成的資料結果往往不是我們想要的。那麼如何解決這個問題呢,Redis已經為我們準備好了!
你可以看到我RedisHelper中有個方法是 public IDisposable Acquire(string key)。 也可以看到他返回的是IDisposable,證明我們需要手動釋放資源。方法內部的 AcquireLock正是關鍵之處,它像redis中索取一把鎖頭,被鎖住的資源,只能被單個執行緒訪問,不會被兩個執行緒同時get或者set,這兩個執行緒一定是交替著進行的,當然這裡的交替並不是指你一次我一次,也可能是你多次,我一次,下面看程式碼。
1 static void Main(string[] args) 2 { 3 RedisHelper rds = new RedisHelper(); 4 rds.Set<int>("mykey1", 0); 5 Thread myThread1 = new Thread(AddVal); 6 Thread myThread2 = new Thread(AddVal); 7 myThread1.Start(); 8 myThread2.Start(); 9 Console.WriteLine("等待兩個執行緒結束"); 10 Console.ReadKey(); 11 } 12 13 public static void AddVal() 14 { 15 RedisHelper rds = new RedisHelper(); 16 for (int i = 0; i < 50000; i++) 17 { 18 using (rds.Acquire("lock")) 19 { 20 int result = rds.Get<int>("mykey1"); 21 rds.Set<int>("mykey1", result + 1); 22 } 23 } 24 Console.WriteLine("執行緒結束,輸出" + rds.Get<int>("mykey1")); 25 }
可以看到我使用了using,呼叫我的Acquire方法獲取鎖。
輸出結果最後是100000,正是我們要的正確結果。前面的8W+是因為兩個執行緒之一先執行結束了。
還有,在正式使用的過程中,建議給我們的鎖,使用後刪除掉,並加上一個過期時間,使用expire。
以免程式執行期間意外退出,導致鎖一直存在,今後可能無法更新或者獲取此被鎖住的資料。
你也可以嘗試一下不設定expire,在程式剛開始執行時,關閉console,重新執行程式,並且在redis-cli的操作控制檯,get你鎖住的值,將會永遠獲取不到。
所有連線此redis例項的機器,同一時刻,只能有一個獲取指定name的鎖.
下面是StackExchange.Redis的寫法
1 var info = "name-"+Environment.MachineName; 2 //如果5秒不釋放鎖 自動釋放。避免死鎖 3 if (db.LockTake("name", info, TimeSpan.FromSeconds(5))) 4 { 5 try 6 { 7 8 } 9 catch (Exception ex) 10 { 11 12 } 13 finally 14 { 15 16 db.LockRelease("name", token); 17 } 18 }