Redis分布式鎖三板斧,你真的會設計嗎?

2020-10-03 低調的乾貨君

Redis 是一個開源(BSD許可)的,內存中的數據結構存儲系統,它可以用作資料庫、緩存和消息中間件。 它支持多種類型的數據結構,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 等。

redis分布式鎖三板斧,獲取鎖、刪除鎖、鎖超時

redis分布式的常規實現


Redis是最常見的實現分布式鎖的方法之一,而很多人都了解使用了redis分布式鎖使用redis的

SET key value [EX seconds] [PX milliseconds] [NX|XX]

指令。

對於刪除操作,由於先判斷key是否存在,然後執行del操作,為避免刪除其他線程生成的鎖,因此需要執行使用lua腳本執行,保證原子性,腳本如下

-- lua刪除鎖:-- KEYS和ARGV分別是以集合方式傳入的參數 key為鎖名稱,argv為每個鎖的唯一表示if redis.call('get', KEYS[1]) == ARGV[1] then -- 執行刪除操作 return redis.call('del', KEYS[1]) else -- 不成功,返回0 return 0 end

超時時間怎麼設置

上面實現的過程中,我們對於設置key的超時時間的設置怎麼處理?一旦我們設置太短,業務代碼耗時過長,則會被超時釋放;如果設置的太長,萬一線程獲得鎖後線程異常或死亡,未能正常釋放鎖,容易導致業務積壓。

有人說我們可以預先計算業務耗時,設置一個最長的,其實這個看似很合理,但實際上,作為一款分布式鎖的基礎服務,本應與業務脫離,需要保證未來業務場景,同時簡化使用方式。

其實,redisson就針對這樣的問題提供了解決方案,那就是watch dog(看門狗),下面我們看下redisson的源碼。

watch dog即通過開啟一個線程進行key的續期操作。

先看redisson的demo使用,如下

// 初始化Config config = new Config();// 由於本地使用單機模式,其他模式也config.useSingleServer().setAddress("127.0.0.1:6379");RedissonClient redisson = Redisson.create(config);// 獲取鎖RLock lock = redisson.getLock("lockkey");lock.lock(30, TimeUnit.SECONDS);

那麼我們直接從redisson.getLock去往下跟蹤代碼,為了簡化,我們直接看到最後的watch dog的代碼(一步一步跟源碼,肯定會看到這段代碼)

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { if (leaseTime != -1L) { // 獲取鎖 return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } else { // watchdog RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e == null) { if (ttlRemaining == null) { this.scheduleExpirationRenewal(threadId); } } }); return ttlRemainingFuture; } }

此時我們看核心代碼tryLockInnerAsync

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { this.internalLockLeaseTime = unit.toMillis(leaseTime); return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, //如果鎖不存在,創建鎖,並返回空 "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; //如果鎖存在,且value匹配,說明是當前線程持有的鎖,對key增量加1(重入次數+1),返回空 if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; //申請鎖失敗,返回鎖的剩餘時間 return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)}); }

上面可以看到如果有鎖會返回nil,然後會看到最上面的代碼,會判斷,如果為null,會進行scheduleExpirationRenewal方法調用,如下

ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e == null) { if (ttlRemaining == null) { this.scheduleExpirationRenewal(threadId); } } });

scheduleExpirationRenewal方法內容如下:

protected RFuture<Boolean> renewExpirationAsync(long threadId) { return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, // 如果有鎖則續期 30s(源碼中初始化 this.lockWatchdogTimeout = 30000L) "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)}); }

此時我們終於看到了續期操作,了解了watch dog的原理。

相關焦點

  • 阿里a面試題剖析 使用 Redis 如何設計分布式鎖?
    面試原題一般實現分布式鎖都有哪些方式?使用 redis 如何設計分布式鎖?使用 zk 來設計分布式鎖可以嗎?這兩種分布式鎖的實現方式哪種效率比較高?分布式鎖官方叫做 RedLock 算法,是 redis 官方支持的分布式鎖算法。
  • 聊聊Redis分布式鎖
    一、基於Redis分布式鎖方案1、Redis天然適合做分布式鎖,因為它是單進程單線程的,你所有的請求到Redis裡面,它都會進行串行化處理;2、實現方式Redis Setnx (SET if Not exists)命令在指定的key不存在時,為key設置指定的值① SETNX KEY_NAME VALUE Expire_Time --這是它獲取鎖的一個命令格式
  • java 從零實現屬於你的 redis 分布式鎖
    但是在分布式系統中,上面的鎖就統統沒用了。我們想要解決分布式系統中的並發問題,就需要引入分布式鎖的概念。晚上關於 redis 分布式鎖的文章一大堆,但是也都稂莠不齊。redis 分布式鎖工具有時候中間件團隊不見得會提供,提供了也不見得經常維護,不如自己實現一個,知道原理,也方便修改。
  • 基於 Redis 實現的分布式鎖
    30毫秒 * * @param key 分布式鎖,redis key * @param expireTime 分布式鎖過期時間,單位秒 * @return * @author mingfei.zhang */ boolean lock(String key, long expireTime); /**
  • Go和Redis實現分布式鎖
    在分布式系統中,發並操作會導致數據不一致的情況,接下要解決的幾個問題:單個應用中使用鎖:(單進程多線程)分布式鎖控制分布式系統之間同步訪問資源的一種方式(相同的應用部署多套,並發請求,會執行到相同代碼塊)。設計一個分布式鎖需要具備哪些條件?
  • 基於 Redis 的高容錯性分布式鎖架構設計方案 -01
    假設某個節點在執行完SETNX命令後宕機了,那麼鎖資源將無法釋放,這會導致其它節點永遠無法加鎖而hang住。為了解決這個問題,我們往往會選擇在程序中執行完SETNX命令之後再緊跟EXPIRE或PEXPIRE命令來避免產生死鎖;當然,在程序不出現任何異常,或者網絡不發生抖動的時候,原則上是可行的,但作為一名架構師或技術專家,在設計方案時,永遠需要把最壞的情況考慮進去。
  • 利用Redis實現分布式鎖
    為什麼需要分布式鎖?在傳統單體應用單機部署的情況下,可以使用Java並發相關的鎖,如ReentrantLcok或synchronized進行互斥控制。但是,隨著業務發展的需要,原單體單機部署的系統,漸漸的被部署在多機器多JVM上同時提供服務,這使得原單機部署情況下的並發控制鎖策略失效了,為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分布式鎖要解決的問題。
  • 分布式鎖解決方案-Redis
    ## 為什麼要學習分布式鎖解決方案為了解決分布式架構帶來的數據準確性問題!我們用synchronized或者 ReentrantLock(瑞恩吹特) 能解決問題嗎?真實生產環境我們採用集群的方式去訪問秒殺商品(nginx為我們做了負載均衡)。
  • C# Redis分布式鎖 - 單節點
    為什麼要用分布式鎖?先上一張截圖,這是在瀏覽別人的博客時看到的.但是進程鎖有一個前提,那就是需要多個進程在同一個系統中,如果多個進程不在同一個系統,那就只能使用分布式鎖來控制了.分布式鎖是控制分布式系統中不同系統之間訪問共享資源的一種鎖實現.它和線程鎖,進程鎖的作用都是一樣,只是範圍不一樣.
  • Spring Cloud基於Redis實現的分布式鎖
    分布式鎖實現方式:1、基於資料庫實現分布式鎖 2、基於緩存(redis,memcached,tair)實現分布式鎖3、基於Zookeeper實現分布式鎖 為什麼不使用資料庫?資料庫是單點?搞兩個資料庫,數據之前雙向同步。
  • 手寫Redis分布式鎖
    對Zookeeper分布式鎖有興趣的可以看看我寫的這篇文章。分布式鎖實現Redis分布式鎖底層分析過期時間保證鎖在異常情況下也能解鎖。採用Lua腳本操作Redis,使操作具有原子性。後臺進程心跳檢測,如果當前時間持有鎖並且鎖還未失效,延長鎖的失效時間。如果當前線程沒有獲取到鎖,會一直自旋,直到獲取到鎖為止。
  • 基於Redis實現分布式鎖之前,這些坑你一定得知道
    開頭基於Redis的分布式鎖對大家來說並不陌生,可是你的分布式鎖有失敗的時候嗎?在失敗的時候可曾懷疑過你在用的分布式鎖真的靠譜嗎?以下是結合自己的踩坑經驗總結的一些經驗之談。你真的需要分布式鎖嗎?client2從C、D、E獲取鎖成功,client2也獲取鎖成功,那麼在同一時刻client1和client2同時獲取鎖,redlock被玩壞了。怎麼解決呢?最容易想到的方案是打開持久化。持久化可以做到持久化每一條redis命令,但這對性能影響會很大,一般不會採用,如果不採用這種方式,在節點掛的時候肯定會損失小部分的數據,可能我們的鎖就在其中。
  • 分布式鎖沒那麼難,手把手教你實現 Redis 分布鎖!|保姆級教程
    那就先寫下最近在鼓搗一個東西,使用 Redis 實現可重入分布鎖。看到這裡,有的朋友可能會提出來使用 redisson 不香嗎,為什麼還要自己實現?哎,redisson 真的很香,但是現有項目中沒辦法使用,只好自己手擼一個可重入的分布式鎖了。
  • 我猜你還沒明白如何利用好Redis使用實現分布式鎖?
    因此,這裡的問題是:Java 提供的原生鎖機制在多機部署場景下失效了,這是因為兩臺機器加的鎖不是同一個鎖(兩個鎖在不同的 JVM 裡面)。那麼,我們只要保證兩臺機器加的鎖是同一個鎖,問題不就解決了嗎?此時,就該分布式鎖隆重登場了。
  • 一起來學習分布式鎖
    那麼,我們只要保證兩臺機器加的鎖是同一個鎖,問題不就解決了嗎?會存在單點問題,只要 redis 故障了。redisson 中有一個 watchdog 的概念,翻譯過來就是看門狗,它會在你獲取鎖之後,每隔 10 秒幫你把 key 的超時時間設為 30s這樣的話,就算一直持有鎖也不會出現 key 過期了,其他線程獲取到鎖的問題了。
  • 使用Redis構建分布式鎖
    這個時候Redis Server會給加了樂觀鎖的客戶端連接發送消息,訴你該數據已經修改過了,這個時候本客戶端連接可以選擇循環重試或者直接退出。這個聽起來是不是像CAS的操作呢?我的感覺是像極了。 一切看起來很美好,不是嗎?
  • 實現一個Redis分布式鎖
    在我們日常開發中,難免會遇到要加鎖的情景。這一波操作明顯不符合原子性,如果代碼塊不加鎖,很容易因為並發導致超賣問題。咱們的系統如果是單體架構,那我們使用本地鎖就可以解決問題。如果是分布式架構,就需要使用分布式鎖。
  • 手撕redis鎖,就這麼簡單
    因為mysql操作是需要IO的,IO的速度比內存速度慢,因此mysql如果在那種場景下使用的話是會存在系統瓶頸的。所以本篇就和小夥伴們分享基於內存操作的比較常用的分布式鎖——redis分布式鎖。如果出現了異常,過了鎖的有效期,鎖會自動釋放,釋放鎖主要採用了redis的delete命令,釋放鎖之前會校驗當前redis存儲的隨機數,只有當前的隨機數和存儲的隨機數一致的時候才允許釋放。
  • redission 分布式鎖
    所以在設計系統時,往往需要權衡,在CAP中作選擇。當然,這個理論也並不一定完美,不同系統對CAP的要求級別不一樣,選擇需要考慮方方面面。在微服務系統中,一個請求存在多級跨服務調用,往往需要犧牲強一致性老保證系統高可用,比如通過分布式事務,異步消息等手段完成。但還是有的場景,需要阻塞所有節點的所有線程,對共享資源的訪問。比如並發時「超賣」和「餘額減為負數」等情況。
  • SpringBoot2.0實戰(22)整合Redis之實現分布式鎖
    >分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式,在分布式系統中,如果不同的應用之間共享一個或一組資源,那麼訪問這些資源的時候,往往需要互斥來防止彼此幹擾來保證一致性,在這種情況下,便需要使用到分布式鎖。