本文版權歸博客園和做者本人吳雙共同全部 。轉載爬蟲請註明地址,博客園蝸牛 http://www.cnblogs.com/tdws/p/5712835.htmlhtml
蝸牛Redis系列文章目錄http://www.cnblogs.com/tdws/tag/NoSql/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值,一個設置併發鎖,以便在我後面的操做中,你能清楚我究竟作了什麼。ide
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,分別訪問五萬次,在併發條件下,咱們很難保證數據的準確性,請比較輸出結果。ui
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已經爲咱們準備好了!spa
你能夠看到我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 }