基于Redis的分布式鎖和Redlock算法

背景

在單進程的系統中,當存在多個線程可以同時改變某個變量(可變共享變量)時娘侍,就需要對變量或代碼塊做同步向族,使其在修改這種變量時能夠線性執(zhí)行消除并發(fā)修改變量。

而同步的本質是通過鎖來實現的驶冒。為了實現多個線程在一個時刻同一個代碼塊只能有一個線程可執(zhí)行,那么需要在某個地方做個標記韵卤,這個標記必須每個線程都能看到骗污,當標記不存在時可以設置該標記,其余后續(xù)線程發(fā)現已經有標記了則等待擁有標記的線程結束同步代碼塊取消標記后再去嘗試設置標記沈条。這個標記可以理解為鎖需忿。

不同地方實現鎖的方式也不一樣,只要能滿足所有線程都能看得到標記即可拍鲤。如 Java 中 synchronize 是在對象頭設置標記贴谎,Lock 接口的實現類基本上都只是某一個 volitile 修飾的 int 型變量其保證每個線程都能擁有對該 int 的可見性和原子修改汞扎,linux 內核中也是利用互斥量或信號量等內存數據做標記季稳。

除了利用內存數據做鎖其實任何互斥的都能做鎖(只考慮互斥情況),如流水表中流水號與時間結合做冪等校驗可以看作是一個不會釋放的鎖澈魄,或者使用某個文件是否存在作為鎖等景鼠。只需要滿足在對標記進行修改能保證原子性和內存可見性即可。

概念

1 什么是分布式?

分布式的 CAP 理論告訴我們:

任何一個分布式系統都無法同時滿足一致性(Consistency)铛漓、可用性(Availability)和分區(qū)容錯性(Partition tolerance)溯香,最多只能同時滿足兩項。

目前很多大型網站及應用都是分布式部署的浓恶,分布式場景中的數據一致性問題一直是一個比較重要的話題玫坛。基于 CAP理論包晰,很多系統在設計之初就要對這三者做出取舍湿镀。在互聯網領域的絕大多數的場景中,都需要犧牲強一致性來換取系統的高可用性伐憾,系統往往只需要保證最終一致性勉痴。

場景

分布式場景

此處主要指集群模式下,多個相同服務同時開啟.

在許多的場景中树肃,我們?yōu)榱吮WC數據的最終一致性蒸矛,需要很多的技術方案來支持,比如分布式事務胸嘴、分布式鎖等雏掠。很多時候我們需要保證一個方法在同一時間內只能被同一個線程執(zhí)行。在單機環(huán)境中劣像,通過 Java 提供的并發(fā) API 我們可以解決磁玉,但是在分布式環(huán)境下,就沒有那么簡單啦驾讲。

??● 分布式與單機情況下最大的不同在于其不是多線程而是多進程蚊伞。

??● 多線程由于可以共享堆內存,因此可以簡單的采取內存作為標記存儲位置吮铭。而進程之間甚至可能都不在同一臺物理機上时迫,因此需要將標記存儲在一個所有進程都能看到的地方。

什么是分布式鎖谓晌?

??● 當在分布式模型下掠拳,數據只有一份(或有限制),此時需要利用鎖的技術控制某一時刻修改數據的進程數纸肉。

??● 與單機模式下的鎖不僅需要保證進程可見溺欧,還需要考慮進程與鎖之間的網絡問題。(我覺得分布式情況下之所以問題變得復雜柏肪,主要就是需要考慮到網絡的延時和不可靠姐刁。。烦味。一個大坑)

??● 分布式鎖還是可以將標記存在內存聂使,只是該內存不是某個進程分配的內存而是公共內存如 Redis、Memcache。至于利用數據庫柏靶、文件等做鎖與單機的實現是一樣的弃理,只要保證標記能互斥就行。

2 我們需要怎樣的分布式鎖屎蜓?

可以保證在分布式部署的應用集群中痘昌,同一個方法在同一時間只能被一臺機器上的一個線程執(zhí)行。

這把鎖要是一把可重入鎖(避免死鎖)

這把鎖最好是一把阻塞鎖(根據業(yè)務需求考慮要不要這條)

這把鎖最好是一把公平鎖(根據業(yè)務需求考慮要不要這條)

有高可用的獲取鎖和釋放鎖功能

獲取鎖和釋放鎖的性能要好

代碼實現

public interface IDistributedLock

? ? {

? ? ? ? ILockResult Lock(string resourceKey);

? ? ? ? ILockResult Lock(string resourceKey, TimeSpan expiryTime);

? ? ? ? ILockResult Lock(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime);

? ? ? ? ILockResult Lock(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime, CancellationToken cancellationToken);

? ? ? ? Task<ILockResult> LockAsync(string resourceKey);

? ? ? ? Task<ILockResult> LockAsync(string resourceKey, TimeSpan expiryTime);

? ? ? ? Task<ILockResult> LockAsync(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime);

? ? ? ? Task<ILockResult> LockAsync(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime, CancellationToken cancellationToken);

? ? }

? ? public interface ILockResult : IDisposable

? ? {

? ? ? ? string LockId { get; }

? ? ? ? bool IsAcquired { get; }

? ? ? ? int ExtendCount { get; }

? ? }


class EndPoint:RedLock.RedisLockEndPoint

? ? {

? ? ? ? private readonly string _connectionString;

? ? ? ? public EndPoint(string connectionString)

? ? ? ? {

? ? ? ? ? ? _connectionString = connectionString;

? ? ? ? ? ? //139.196.40.252,password=xstudio,defaultDatabase=9

? ? ? ? ? ? var connection = connectionString.Split(',');

? ? ? ? ? ? var dict = new Dictionary<string, string>();

? ? ? ? ? ? foreach (var item in connection)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? var keypar = item.Split('=');

? ? ? ? ? ? ? ? if (keypar.Length>1)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? dict[keypar[0]] = keypar[1];

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? this.EndPoint = new System.Net.DnsEndPoint(connection[0], 6379);

? ? ? ? ? ? if (dict.TryGetValue("password", out string password))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? this.Password = password;

? ? ? ? ? ? }

? ? ? ? ? ? if (dict.TryGetValue("defaultDatabase", out string defaultDatabase) && int.TryParse(defaultDatabase,out int database))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? RedisDatabase = database;

? ? ? ? ? ? }

? ? ? ? }

? ? }


[Export(typeof(IDistributedLock))]

? ? class InnerLock : IDistributedLock

? ? {

? ? ? ? private static Lazy<RedLock.RedisLockFactory> _factory;

? ? ? ? static InnerLock()

? ? ? ? {

? ? ? ? ? ? _factory = new Lazy<RedisLockFactory>(() => new RedisLockFactory(new EndPoint(ConfigurationManager.AppSettings["Redis"])), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);

? ? ? ? }

? ? ? ? public ILockResult Lock(string resourceKey)

? ? ? ? {

? ? ? ? ? ? return new LockResult(_factory.Value.Create(resourceKey, TimeSpan.FromDays(1)));

? ? ? ? }

? ? ? ? public ILockResult Lock(string resourceKey, TimeSpan expiryTime)

? ? ? ? {

? ? ? ? ? ? return new LockResult(_factory.Value.Create(resourceKey, expiryTime));

? ? ? ? }

? ? ? ? public ILockResult Lock(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime)

? ? ? ? {

? ? ? ? ? ? return new LockResult(_factory.Value.Create(resourceKey, expiryTime, waitTime, retryTime));

? ? ? ? }

? ? ? ? public ILockResult Lock(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime, CancellationToken cancellationToken)

? ? ? ? {

? ? ? ? ? ? return new LockResult(_factory.Value.Create(resourceKey, expiryTime, waitTime, retryTime, cancellationToken));

? ? ? ? }

? ? ? ? public async Task<ILockResult> LockAsync(string resourceKey)

? ? ? ? {

? ? ? ? ? ? var result = await _factory.Value.CreateAsync(resourceKey, TimeSpan.FromDays(1));

? ? ? ? ? ? return new LockResult(result);

? ? ? ? }

? ? ? ? public async Task<ILockResult> LockAsync(string resourceKey, TimeSpan expiryTime)

? ? ? ? {

? ? ? ? ? ? var result = await _factory.Value.CreateAsync(resourceKey, expiryTime);

? ? ? ? ? ? return new LockResult(result);

? ? ? ? }

? ? ? ? public async Task<ILockResult> LockAsync(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime)

? ? ? ? {

? ? ? ? ? ? var result = await _factory.Value.CreateAsync(resourceKey, expiryTime, waitTime, retryTime);

? ? ? ? ? ? return new LockResult(result);

? ? ? ? }

? ? ? ? public async Task<ILockResult> LockAsync(string resourceKey, TimeSpan expiryTime, TimeSpan waitTime, TimeSpan retryTime, CancellationToken cancellationToken)

? ? ? ? {

? ? ? ? ? ? var result = await _factory.Value.CreateAsync(resourceKey, expiryTime, waitTime, retryTime, cancellationToken);

? ? ? ? ? ? return new LockResult(result);

? ? ? ? }

? ? }

? ? class LockResult : ILockResult

? ? {

? ? ? ? private IRedisLock _lock;

? ? ? ? public LockResult(IRedisLock redisLock)

? ? ? ? {

? ? ? ? ? ? _lock = redisLock;

? ? ? ? }

? ? ? ? public string LockId => _lock.LockId;

? ? ? ? public bool IsAcquired => _lock.IsAcquired;

? ? ? ? public int ExtendCount => _lock.ExtendCount;

? ? ? ? public void Dispose()

? ? ? ? {

? ? ? ? ? ? _lock.Dispose();

? ? ? ? }

? ? }

開源地址

https://github.com/samcook/RedLock.net

https://github.com/StackExchange/StackExchange.Redis/

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末炬转,一起剝皮案震驚了整個濱河市控汉,隨后出現的幾起案子,更是在濱河造成了極大的恐慌返吻,老刑警劉巖姑子,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異测僵,居然都是意外死亡街佑,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門捍靠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沐旨,“玉大人,你說我怎么就攤上這事榨婆〈判” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵良风,是天一觀的道長谊迄。 經常有香客問我,道長烟央,這世上最難降的妖魔是什么统诺? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮疑俭,結果婚禮上粮呢,老公的妹妹穿的比我還像新娘。我一直安慰自己钞艇,他們只是感情好啄寡,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哩照,像睡著了一般挺物。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葡秒,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天姻乓,我揣著相機與錄音嵌溢,去河邊找鬼眯牧。 笑死蹋岩,一個胖子當著我的面吹牛,可吹牛的內容都是我干的学少。 我是一名探鬼主播剪个,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼版确!你這毒婦竟也來了扣囊?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绒疗,失蹤者是張志新(化名)和其女友劉穎侵歇,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體吓蘑,經...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡惕虑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了磨镶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溃蔫。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖琳猫,靈堂內的尸體忽然破棺而出伟叛,到底是詐尸還是另有隱情,我是刑警寧澤脐嫂,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布统刮,位于F島的核電站,受9級特大地震影響账千,放射性物質發(fā)生泄漏网沾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一蕊爵、第九天 我趴在偏房一處隱蔽的房頂上張望辉哥。 院中可真熱鬧,春花似錦攒射、人聲如沸醋旦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饲齐。三九已至,卻和暖如春咧最,著一層夾襖步出監(jiān)牢的瞬間捂人,已是汗流浹背御雕。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留滥搭,地道東北人酸纲。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像瑟匆,于是被迫代替她去往敵國和親闽坡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容