1. 程式人生 > >【分散式鎖】03-使用Redisson實現RedLock原理

【分散式鎖】03-使用Redisson實現RedLock原理

前言

前面已經學習了Redission可重入鎖以及公平鎖的原理,接著看看Redission是如何來實現RedLock的。

RedLock原理

RedLock是基於redis實現的分散式鎖,它能夠保證以下特性:

  • 互斥性:在任何時候,只能有一個客戶端能夠持有鎖;避免死鎖:
  • 當客戶端拿到鎖後,即使發生了網路分割槽或者客戶端宕機,也不會發生死鎖;(利用key的存活時間)
  • 容錯性:只要多數節點的redis例項正常執行,就能夠對外提供服務,加鎖或者釋放鎖;

RedLock演算法思想,意思是不能只在一個redis例項上建立鎖,應該是在多個redis例項上建立鎖,n / 2 + 1,必須在大多數redis節點上都成功建立鎖,才能算這個整體的RedLock加鎖成功,避免說僅僅在一個redis例項上加鎖而帶來的問題。

這裡附上一個前幾天對RedLock解析比較透徹的文章:
https://mp.weixin.qq.com/s/gOYWLg3xYt4OhS46woN_Lg

Redisson實現原理

Redisson中有一個MultiLock的概念,可以將多個鎖合併為一個大鎖,對一個大鎖進行統一的申請加鎖以及釋放鎖

而Redisson中實現RedLock就是基於MultiLock 去做的,接下來就具體看看對應的實現吧

RedLock使用案例

先看下官方的程式碼使用:
(https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers#84-redlock)

 1RLock lock1 = redisson1.getLock("lock1");
2RLock lock2 = redisson2.getLock("lock2");
3RLock lock3 = redisson3.getLock("lock3");
4
5RLock redLock = anyRedisson.getRedLock(lock1, lock2, lock3);
6
7// traditional lock method
8redLock.lock();
9
10// or acquire lock and automatically unlock it after 10 seconds
11redLock.lock(10, TimeUnit.SECONDS);
12
13// or wait for lock aquisition up to 100 seconds 
14// and automatically unlock it after 10 seconds
15boolean res = redLock.tryLock(100, 10, TimeUnit.SECONDS);
16if (res) {
17   try {
18     ...
19   } finally {
20       redLock.unlock();
21   }
22}

這裡是分別對3個redis例項加鎖,然後獲取一個最後的加鎖結果。

RedissonRedLock實現原理

上面示例中使用redLock.lock()或者tryLock()最終都是執行RedissonRedLock中方法。

RedissonRedLock 繼承自RedissonMultiLock, 實現了其中的一些方法:

 1public class RedissonRedLock extends RedissonMultiLock {
2    public RedissonRedLock(RLock... locks) {
3        super(locks);
4    }
5
6    /**
7     * 鎖可以失敗的次數,鎖的數量-鎖成功客戶端最小的數量
8     */
9    @Override
10    protected int failedLocksLimit() {
11        return locks.size() - minLocksAmount(locks);
12    }
13
14    /**
15     * 鎖的數量 / 2 + 1,例如有3個客戶端加鎖,那麼最少需要2個客戶端加鎖成功
16     */
17    protected int minLocksAmount(final List<RLock> locks) {
18        return locks.size()/2 + 1;
19    }
20
21    /** 
22     * 計算多個客戶端一起加鎖的超時時間,每個客戶端的等待時間
23     * remainTime預設為4.5s
24     */
25    @Override
26    protected long calcLockWaitTime(long remainTime) {
27        return Math.max(remainTime / locks.size(), 1);
28    }
29
30    @Override
31    public void unlock() {
32        unlockInner(locks);
33    }
34
35}

看到locks.size()/2 + 1 ,例如我們有3個客戶端例項,那麼最少2個例項加鎖成功才算分散式鎖加鎖成功。

接著我們看下lock()的具體實現

RedissonMultiLock實現原理

  1public class RedissonMultiLock implements Lock {
2
3    final List<RLock> locks = new ArrayList<RLock>();
4
5    public RedissonMultiLock(RLock... locks) {
6        if (locks.length == 0) {
7            throw new IllegalArgumentException("Lock objects are not defined");
8        }
9        this.locks.addAll(Arrays.asList(locks));
10    }
11
12    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
13        long newLeaseTime = -1;
14        if (leaseTime != -1) {
15            // 如果等待時間設定了,那麼將等待時間 * 2
16            newLeaseTime = unit.toMillis(waitTime)*2;
17        }
18
19        // time為當前時間戳
20        long time = System.currentTimeMillis();
21        long remainTime = -1;
22        if (waitTime != -1) {
23            remainTime = unit.toMillis(waitTime);
24        }
25        // 計算鎖的等待時間,RedLock中:如果remainTime=-1,那麼lockWaitTime為1
26        long lockWaitTime = calcLockWaitTime(remainTime);
27
28        // RedLock中failedLocksLimit即為n/2 + 1
29        int failedLocksLimit = failedLocksLimit();
30        List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());
31        // 迴圈每個redis客戶端,去獲取鎖
32        for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
33            RLock lock = iterator.next();
34            boolean lockAcquired;
35            try {
36                // 呼叫tryLock方法去獲取鎖,如果獲取鎖成功,則lockAcquired=true
37                if (waitTime == -1 && leaseTime == -1) {
38                    lockAcquired = lock.tryLock();
39                } else {
40                    long awaitTime = Math.min(lockWaitTime, remainTime);
41                    lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
42                }
43            } catch (Exception e) {
44                lockAcquired = false;
45            }
46
47            // 如果獲取鎖成功,將鎖加入到list集合中
48            if (lockAcquired) {
49                acquiredLocks.add(lock);
50            } else {
51                // 如果獲取鎖失敗,判斷失敗次數是否等於失敗的限制次數
52                // 比如,3個redis客戶端,最多隻能失敗1次
53                // 這裡locks.size = 3, 3-x=1,說明只要成功了2次就可以直接break掉迴圈
54                if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
55                    break;
56                }
57
58                // 如果最大失敗次數等於0
59                if (failedLocksLimit == 0) {
60                    // 釋放所有的鎖,RedLock加鎖失敗
61                    unlockInner(acquiredLocks);
62                    if (waitTime == -1 && leaseTime == -1) {
63                        return false;
64                    }
65                    failedLocksLimit = failedLocksLimit();
66                    acquiredLocks.clear();
67                    // 重置迭代器 重試再次獲取鎖
68                    while (iterator.hasPrevious()) {
69                        iterator.previous();
70                    }
71                } else {
72                    // 失敗的限制次數減一
73                    // 比如3個redis例項,最大的限制次數是1,如果遍歷第一個redis例項,失敗了,那麼failedLocksLimit會減成0
74                    // 如果failedLocksLimit就會走上面的if邏輯,釋放所有的鎖,然後返回false
75                    failedLocksLimit--;
76                }
77            }
78
79            if (remainTime != -1) {
80                remainTime -= (System.currentTimeMillis() - time);
81                time = System.currentTimeMillis();
82                if (remainTime <= 0) {
83                    unlockInner(acquiredLocks);
84                    return false;
85                }
86            }
87        }
88
89        if (leaseTime != -1) {
90            List<RFuture<Boolean>> futures = new ArrayList<RFuture<Boolean>>(acquiredLocks.size());
91            for (RLock rLock : acquiredLocks) {
92                RFuture<Boolean> future = rLock.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
93                futures.add(future);
94            }
95
96            for (RFuture<Boolean> rFuture : futures) {
97                rFuture.syncUninterruptibly();
98            }
99        }
100
101        return true;
102    }
103}

核心程式碼都已經加了註釋,實現原理其實很簡單,基於RedLock思想,遍歷所有的Redis客戶端,然後依次加鎖,最後統計成功的次數來判斷是否加鎖成功。

申明

本文章首發自本人部落格:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!

感興趣的小夥伴可關注個人公眾號:壹枝花算不算浪漫

相關推薦

分散式03-使用Redisson實現RedLock原理

前言 前面已經學習了Redission可重入鎖以及公平鎖的原理,接著看看Redission是如何來實現RedLock的。 RedLock原理 RedLock是基於redis實現的分散式鎖,它能夠保證以下特性: 互斥性:在任何時候,只能有一個客戶端能夠持有鎖;避免死鎖: 當客戶端拿到鎖後,即使發生了網路分割槽

分散式06-Zookeeper實現分散式:可重入原始碼分析

前言 前面已經講解了Redis的客戶端Redission是怎麼實現分散式鎖的,大多都深入到原始碼級別。 在分散式系統中,常見的分散式鎖實現方案還有Zookeeper,接下來會深入研究Zookeeper是如何來實現分散式鎖的。 Zookeeper初識 檔案系統 Zookeeper維護一個類似檔案系統的資料結構

分散式01-使用Redisson實現可重入分散式原理

前言 主流的分散式鎖一般有三種實現方式: 資料庫樂觀鎖 基於Redis的分散式鎖 基於ZooKeeper的分散式鎖 之前我在部落格上寫過關於mysql和redis實現分散式鎖的具體方案:https://www.cnblogs.com/wang-meng/p/10226618.html裡面主要是從實現原理出

分散式02-使用Redisson實現公平原理

前言 前面分析了Redisson可重入鎖的原理,主要是通過lua指令碼加鎖及設定過期時間來保證鎖執行的原子性,然後每個執行緒獲取鎖會將獲取鎖的次數+1,釋放鎖會將當前鎖次數-1,如果為0則表示釋放鎖成功。 可重入原理和JDK中的可重入鎖都是一致的。 Redisson公平鎖原理 JDK中也有公平鎖和非公平鎖,所

分散式04-使用Redisson實現ReadWriteLock原理

前言 關於讀寫鎖,大家應該都瞭解JDK中的ReadWriteLock, 當然Redisson也有讀寫鎖的實現。 所謂讀寫鎖,就是多個客戶端同時加讀鎖,是不會互斥的,多個客戶端可以同時加這個讀鎖,讀鎖和讀鎖是不互斥的 Redisson中使用RedissonReadWriteLock來實現讀寫鎖,它是RReadW

分散式05-使用Redisson中Semaphore和CountDownLatch原理

前言 前面已經寫了Redisson大多的內容,我們再看看Redisson官網共有哪些元件: image.png 剩下還有Semaphore和CountDownLatch兩塊,我們就趁熱打鐵,趕緊看看Redisson是如何實現的吧。 我們在JDK中都知道Semaphore和CountDownLatch兩兄弟,

原創redis庫存操作,分散式的四種實現方式[連載二]--基於Redisson實現分散式

一、redisson介紹 redisson實現了分散式和可擴充套件的java資料結構,支援的資料結構有:List, Set, Map, Queue, SortedSet, ConcureentMap, Lock, AtomicLong, CountDownLatch。並且是執行緒安全的,底層使用N

Redis實現分散式Redis實現分散式

前言 分散式鎖一般有三種實現方式:1. 資料庫樂觀鎖;2. 基於Redis的分散式鎖;3. 基於ZooKeeper的分散式鎖。本篇部落格將介紹第二種方式,基於Redis實現分散式鎖。雖然網上已經有各種介紹Redis分散式鎖實現的部落格,然而他們的實現卻有著各種各樣的問題,為

原創redis庫存操作,分散式的四種實現方式[連載一]--基於zookeeper實現分散式

一、背景 在電商系統中,庫存的概念一定是有的,例如配一些商品的庫存,做商品秒殺活動等,而由於庫存操作頻繁且要求原子性操作,所以絕大多數電商系統都用Redis來實現庫存的加減,最近公司專案做架構升級,以微服務的形式做分散式部署,對庫存的操作也單獨封裝為一個微服務,這樣在高併發情況下,加減庫存時,就會出現超賣等

連載redis庫存操作,分散式的四種實現方式[三]--基於Redis watch機制實現分散式

一、redis的事務介紹 1、 Redis保證一個事務中的所有命令要麼都執行,要麼都不執行。如果在傳送EXEC命令前客戶端斷線了,則Redis會清空事務佇列,事務中的所有命令都不會執行。而一旦客戶端傳送了EXEC命令,所有的命令就都會被執行,即使此後客戶端斷線也沒關係,因為Redis中已經記錄了所有要執行的

基於redisson分散式的簡單註解實現

Redisson依賴: <!--redisson--><dependency>     <groupId>org.redisson</groupId>     <artifactId>redisson</ar

分散式快取——-基於redis分散式快取的實現

一:Redis 是什麼? Redis是基於記憶體、可持久化的日誌型、Key-Value資料庫 高效能儲存系統,並提供多種語言的API. 二:出現背景 資料結構(Data Structure)需求

redis併發讀寫,使用Redisson實現分散式

今天為大家帶來一篇有關Redisson實現分散式鎖的文章,好了,不多說了,直接進入主題。1. 可重入鎖(Reentrant Lock)Redisson的分散式可重入鎖RLock Java物件實現了java.util.concurrent.locks.Lock介面,同時還支援自

hibernate框架使用hibernate實現悲觀和樂觀

四種隔離機制不要忘記:(1,2,4,8) 1.read-uncommitted:能夠去讀那些沒有提交的資料(允許髒讀的存在) 2.read-committed:不會出現髒讀,因為只有另一個事務提交才會讀取來 結果,但仍然會出現不可重複讀和幻讀現象。 4.repeatable

分散式架構(10)---基於Redis元件的特性,實現一個分散式限流

分散式---基於Redis進行介面IP限流 場景 為了防止我們的介面被人惡意訪問,比如有人通過JMeter工具頻繁訪問我們的介面,導致介面響應變慢甚至崩潰,所以我們需要對一些特定的介面進行IP限流,即一定時間內同一IP訪問的次數是有限的。 實現原理 用Redis作為限流元件的核心的原理,將使用者的IP地址當

Redisson實現分散式(3)—專案落地實現

Redisson實現分散式鎖(3)—專案落地實現 有關Redisson實現分散式鎖前面寫了兩篇部落格作為該專案落地的鋪墊。 1、Redisson實現分散式鎖(1)---原理 2、Redisson實現分散式鎖(2)—RedissonLock 這篇講下通過Redisson實現分散式鎖的專案實現,我會把專案放到Gi

分散式的演化常用的種類以及解決方案

### 前言 上一篇分散式鎖的文章中,通過超市存放物品的例子和大家簡單分享了一下Java鎖。本篇文章我們就來深入探討一下Java鎖的種類,以及不同的鎖使用的場景,當然本篇只介紹我們常用的鎖。我們分為兩大類,分別是樂觀鎖和悲觀鎖,公平鎖和非公平鎖。 ### 樂觀鎖和悲觀鎖 #### 樂觀鎖 老貓相信,

分散式的演化電商“超賣”場景實戰

### 前言 從本篇開始,老貓會通過電商中的業務場景和大家分享鎖在實際應用場景下的演化過程。從Java單體鎖到分散式環境下鎖的實踐。 ### 超賣的第一種現象案例 其實在電商業務場景中,會有一個這樣讓人忌諱的現象,那就是“超賣”,那麼什麼是超賣呢?舉個例子,某商品的庫存數量只有10件,最終卻賣出了15件

分散式的演化“超賣場景”,MySQL分散式

### 前言 之前的文章中通過電商場景中秒殺的例子和大家分享了單體架構中鎖的使用方式,但是現在很多應用系統都是相當龐大的,很多應用系統都是微服務的架構體系,那麼在這種跨jvm的場景下,我們又該如何去解決併發。 ### 單體應用鎖的侷限性 在進入實戰之前簡單和大家粗略聊一下網際網路系統中的架構演進。 !

分散式的演化終章!手擼ZK分散式!

### 前言 這應該是分散式鎖演化的最後一個章節了,相信很多小夥伴們看完這個章節之後在應對高併發的情況下,如何保證執行緒安全心裡肯定也會有譜了。在實際的專案中也可以參考一下老貓的github上的例子,當然程式碼沒有經過特意的封裝,需要小夥伴們自己再好好封裝一下。那麼接下來,就和大家分享一下基於zookeep