使用場景
在做Web項目的時候,有很多特殊的場景要使用到鎖
比如說搶紅包,資源分配,訂單支付等場景
就拿搶紅包來說,如果一個紅包有5份,同時100個人搶如果沒有用到鎖的話
100個人同時並發都搶成功,那就出大事了
怎么實現鎖
class Test { //定義一個私有成員變量,用於Lock private static object lockobj = new object(); void DoSomething() { lock (lockobj) { //需要鎖定的代碼塊 } } }
這樣我們就可以很好的控制並發的情況,從而不出現問題
但是在項目還小的時候,可能只運行在一台服務器,一個進程的情況下
這種代碼不會出現問題
但是在部署在多台服務器,每個服務器開多個進程的情況下
.net自帶的lock鎖只能保證同一個進程在並發情況在不出現問題
而多服務器,多進程情況下。lock鎖就不能滿足我們的要求了
怎么實現分布式鎖
實現思路為
當我們在執行代碼前,先去設置一個分布式鎖
其實就是給Redis設置一個Key,但是要這個Key不存再的情況下才可以設置成功
如果設置成功,表示當前進程拿到鎖,可以執行后續代碼
如果設置失敗,表示其它進程已經鎖定,那么我們就要讓當前進程休眠一下,然后再去重試設置鎖
直到設置鎖成功,才表示當前進程鎖定,才可以執行自定義代碼
在執行自寶義代碼后,釋放鎖,這樣其它進程就可以拿到鎖了
我們在設置鎖的時候,為了防此自定義代碼報錯,而出現死鎖的情況
所以我們在設置鎖的時候可以設置鎖的一個過期時間,這樣就算自定義代碼出錯,沒有釋放鎖的情況下
其它進程也可以在一定時間內拿到鎖,當然可以try,catch把自定義代碼包起來
代碼實現
//TODO:拿鎖 //定義一個鎖的Key var lockKey = CacheKeyFormat.AssignLockKeyFormat; var isLocked = false;//是否已經鎖定,默認否 do { //使用do-while先去給Redis的lockKey隨便設置一個值 //但是設置的條件是,如果當前lockKey存再(表示其它進程已經鎖定了)就返回false //如果lockKey不存再(表示當前沒有其它進程鎖定),就反回true,並且設置過期時間為600毫秒(如果進行沒有釋放,報錯死鎖的情況) isLocked = redisDb.StringSet(lockKey, id, TimeSpan.FromMilliseconds(600), StackExchange.Redis.When.NotExists); //如果isLocked反回false表示被其它進程鎖定,那么當前進程休眠200毫秒后,再去設置鎖 //重復此動作直到當前進程拿到鎖為止 if (!isLocked) System.Threading.Thread.Sleep(200); } while (!isLocked); //TODO:執行其它動作,to do something //在執行完自定義代碼后,釋放鎖 redisDb.KeyDelete(CacheKeyFormat.AssignLockKeyFormat);
這樣的話,不管項目部署多少服務器,開多少個進程
我們都能保證在這個情況下,這個執行動作是一個一個執行,不會存再並發不可控的情況