咱們在開發不少業務場景會使用到鎖,例如庫存控制,抽獎等。通常咱們會使用內存鎖的方式來保證線性的執行。但如今大多站點都會使用分佈式部署,那多臺服務器間的就必須使用同一個目標來判斷鎖。分佈式與單機狀況下最大的不一樣在於其不是多線程而是多進程。redis
[分佈式站點使用內存鎖方式以下圖]
假設有3個用戶同時購買一件商品,商品庫存只剩下1,若是3個用戶同時購買,負載均衡把3個用戶分別指向站點一、二、3,那結果將會是3個用戶都購買成功。下面咱們使用分佈式鎖解決這個問題。數據庫
[分佈式站點使用分佈式鎖以下圖]
單臺服務器因爲能夠共享堆內存,所以能夠簡單的採起內存做爲標記存儲位置。而多服務器之間都不在同一臺物理機上,所以須要將標記存儲在一個全部進程都能看到的地方。緩存
想一想咱們實現分佈式鎖要知足哪些條件?
一、在分佈式系統環境下,一個鎖在同一時間只能被一個服務器獲取;(這是全部分佈式鎖的基礎)
二、高性能的獲取鎖和釋放鎖;(鎖用完了,要及時釋放,以供別人繼續使用)
三、具有鎖失效機制,防止死鎖;(防止由於某些意外,鎖沒有獲得釋放,那別人將永遠沒法使用)
四、具有非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗。(知足等待鎖的同時,也要知足非阻塞鎖特性,便於多樣性的業務場景使用)服務器
分佈式鎖有不少的實現方式:數據庫排他鎖、Zookeeper、緩存(redis、memcached)等,本文選用Redis實現。緣由以下:
一、Redis有很高的性能;
二、Redis命令對此支持較好,實現起來比較方便;
Redis官網上對C#的使用推薦有ServiceStack.Redis和StackExchange.Redis
因爲StackExchange.Redis雖然有提供LockTake方法,很方便的使用鎖,可是隻支持.Net4.5以上。公司不少站點都是3.5和4.0的,因此選用ServiceStack.Redis,本身封裝一下。多線程
一、SETNX
SETNX key val:當且僅當key不存在時,set一個key爲val的字符串,返回1;若key存在,則什麼都不作,返回0。
二、expire
expire key timeout:爲key設置一個超時時間,單位爲second,超過這個時間鎖會自動釋放,避免死鎖。
三、delete
delete key:刪除key
在使用Redis實現分佈式鎖的時候,主要就會使用到這三個命令。負載均衡
一、鎖方法分佈式
/// <summary> /// 分佈式鎖 /// </summary> /// <param name="key">鎖key</param> /// <param name="lockExpirySeconds">鎖自動超時時間(秒)</param> /// <param name="waitLockMs">等待鎖時間(秒)</param> /// <returns></returns> public static bool Lock(string key, int lockExpirySeconds = 10, double waitLockSeconds = 0) { //間隔等待50毫秒 int waitIntervalMs = 50; RedisClient client = (RedisClient)RedisClientManager.GetClient(RedisProviderName.RedisCommon, 0); string lockKey = "LockForSetNX:" + key; DateTime begin = DateTime.Now; using (client) { while (true) { //循環獲取取鎖 if (client.SetNX(lockKey, new byte[] { 1 }) == 1) { //設置鎖的過時時間 client.Expire(lockKey, lockExpirySeconds); return true; } //不等待鎖則返回 if (waitLockSeconds == 0) break; //超過等待時間,則再也不等待 if ((DateTime.Now - begin).TotalSeconds >= waitLockSeconds) break; Thread.Sleep(waitIntervalMs); } return false; } }
二、釋放鎖ide
/// <summary> /// 刪除鎖 執行完代碼之後調用釋放鎖 /// </summary> /// <param name="key"></param> public static void DelLock(string key) { RedisClient client = (RedisClient)RedisClientManager.GetClient(RedisProviderName.RedisCommon, 0); string lockKey = "LockForSetNX:" + key; using (client) { client.Del(lockKey); } }
三、業務應用代碼memcached
try { //取鎖,設置key10秒後失效,最大等待鎖5秒 if (RedisHelper.Lock("LockKey", 10, 5)) { //取到鎖,執行具體業務 //例如商品購買,庫存-1 } } finally { //釋放鎖 DelLock("LockKey"); }
【若是文章對你有所幫助,請在評論區留言點贊,以資鼓勵。】性能