自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java 從零實現(xiàn)屬于你的 Redis 分布式鎖

開發(fā) 前端 新聞 分布式 Redis
我們想要解決分布式系統(tǒng)中的并發(fā)問題,就需要引入分布式鎖的概念。

[[347022]]

 

 java 從零實現(xiàn)屬于你的 redis 分布式鎖

redis分布式鎖

為什么需要分布式鎖

在 jdk 中為我們提供了加鎖的方式:

(1)synchronized 關(guān)鍵字

(2)volatile + CAS 實現(xiàn)的樂觀鎖

(3)ReadWriteLock 讀寫鎖

(4)ReenTrantLock 可重入鎖

等等,這些鎖為我們變成提供極大的便利性,保證在多線程的情況下,保證線程安全。

但是在分布式系統(tǒng)中,上面的鎖就統(tǒng)統(tǒng)沒用了。

我們想要解決分布式系統(tǒng)中的并發(fā)問題,就需要引入分布式鎖的概念。

java 代碼實現(xiàn)

創(chuàng)作動機

首先是對鎖實現(xiàn)原理的一個實現(xiàn),理論指導(dǎo)實踐,實踐完善理論。

晚上關(guān)于 redis 分布式鎖的文章一大堆,但是也都稂莠不齊。

redis 分布式鎖工具有時候中間件團隊不見得會提供,提供了也不見得經(jīng)常維護,不如自己實現(xiàn)一個,知道原理,也方便修改。

接口定義

為了便于和 JDK 復(fù)用,我們讓接口繼承自 jdk 的 Lock 接口。

  1. package com.github.houbb.lock.api.core; 
  2.  
  3. import java.util.concurrent.TimeUnit; 
  4. import java.util.concurrent.locks.Lock; 
  5.  
  6. /** 
  7.  * 鎖定義 
  8.  * @author binbin.hou 
  9.  * @since 0.0.1 
  10.  */ 
  11. public interface ILock extends Lock { 
  12.  
  13.     /** 
  14.      * 嘗試加鎖 
  15.      * @param time 時間 
  16.      * @param unit 當(dāng)為 
  17.      * @param key key 
  18.      * @return 返回 
  19.      * @throws InterruptedException 異常 
  20.      * @since 0.0.1 
  21.      */ 
  22.     boolean tryLock(long time, TimeUnit unit, 
  23.                     String key) throws InterruptedException; 
  24.  
  25.     /** 
  26.      * 嘗試加鎖 
  27.      * @param key key 
  28.      * @return 返回 
  29.      * @since 0.0.1 
  30.      */ 
  31.     boolean tryLock(String key); 
  32.  
  33.     /** 
  34.      * 解鎖 
  35.      * @param key key 
  36.      * @since 0.0.1 
  37.      */ 
  38.     void unlock(String key); 
  39.  

方法我們只添加了三個比較常用的核心方法,作為第一個版本,簡單點。

后續(xù)陸續(xù)添加即可。

抽象實現(xiàn)

為了便于后期添加更多的所實現(xiàn),這里首先實現(xiàn)了一個公用的抽象父類。

  1. package com.github.houbb.lock.redis.core; 
  2.  
  3. import com.github.houbb.lock.api.core.ILock; 
  4. import com.github.houbb.lock.redis.constant.LockRedisConst; 
  5. import com.github.houbb.wait.api.IWait; 
  6.  
  7. import java.util.concurrent.TimeUnit; 
  8. import java.util.concurrent.locks.Condition; 
  9.  
  10. /** 
  11.  * 抽象實現(xiàn) 
  12.  * @author binbin.hou 
  13.  * @since 0.0.1 
  14.  */ 
  15. public abstract class AbstractLockRedis implements ILock { 
  16.  
  17.     /** 
  18.      * 鎖等待 
  19.      * @since 0.0.1 
  20.      */ 
  21.     private final IWait wait; 
  22.  
  23.     protected AbstractLockRedis(IWait wait) { 
  24.         this.wait = wait; 
  25.     } 
  26.  
  27.     @Override 
  28.     public void lock() { 
  29.         throw new UnsupportedOperationException(); 
  30.     } 
  31.  
  32.     @Override 
  33.     public void lockInterruptibly() throws InterruptedException { 
  34.         throw new UnsupportedOperationException(); 
  35.     } 
  36.  
  37.     @Override 
  38.     public boolean tryLock() { 
  39.         return tryLock(LockRedisConst.DEFAULT_KEY); 
  40.     } 
  41.  
  42.     @Override 
  43.     public void unlock() { 
  44.         unlock(LockRedisConst.DEFAULT_KEY); 
  45.     } 
  46.  
  47.     @Override 
  48.     public boolean tryLock(long time, TimeUnit unit, String key) throws InterruptedException { 
  49.         long startTimeMills = System.currentTimeMillis(); 
  50.  
  51.         // 一次獲取,直接成功 
  52.         boolean result = this.tryLock(key); 
  53.         if(result) { 
  54.             return true
  55.         } 
  56.  
  57.         // 時間判斷 
  58.         if(time <= 0) { 
  59.             return false
  60.         } 
  61.         long durationMills = unit.toMillis(time); 
  62.         long endMills = startTimeMills + durationMills; 
  63.  
  64.         // 循環(huán)等待 
  65.         while (System.currentTimeMillis() < endMills) { 
  66.             result = tryLock(key); 
  67.             if(result) { 
  68.                 return true
  69.             } 
  70.  
  71.             // 等待 10ms 
  72.             wait.wait(TimeUnit.MILLISECONDS, 10); 
  73.         } 
  74.         return false
  75.     } 
  76.  
  77.     @Override 
  78.     public synchronized boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 
  79.         return tryLock(time, unit, LockRedisConst.DEFAULT_KEY); 
  80.     } 
  81.  
  82.     @Override 
  83.     public Condition newCondition() { 
  84.         throw new UnsupportedOperationException(); 
  85.     } 
  86.  

最核心的實際上是 public boolean tryLock(long time, TimeUnit unit, String key) throws InterruptedException 方法。

這個方法會調(diào)用 this.tryLock(key) 獲取鎖,如果成功,直接返回;如果不成功,則循環(huán)等待。

這里設(shè)置了超時時間,如果超時,則直接返回 true。

redis 鎖實現(xiàn)

我們實現(xiàn)的 redis 分布鎖,繼承自上面的抽象類。

  1. package com.github.houbb.lock.redis.core; 
  2.  
  3. import com.github.houbb.heaven.util.lang.StringUtil; 
  4. import com.github.houbb.id.api.Id; 
  5. import com.github.houbb.id.core.util.IdThreadLocalHelper; 
  6. import com.github.houbb.lock.redis.constant.LockRedisConst; 
  7. import com.github.houbb.lock.redis.exception.LockRedisException; 
  8. import com.github.houbb.lock.redis.support.operator.IOperator; 
  9. import com.github.houbb.wait.api.IWait; 
  10.  
  11. /** 
  12.  * 這里是基于 redis 實現(xiàn) 
  13.  * 
  14.  * 實際上也可以基于 zk/數(shù)據(jù)庫等實現(xiàn)。 
  15.  * 
  16.  * @author binbin.hou 
  17.  * @since 0.0.1 
  18.  */ 
  19. public class LockRedis extends AbstractLockRedis { 
  20.  
  21.     /** 
  22.      * redis 操作實現(xiàn) 
  23.      * @since 0.0.1 
  24.      */ 
  25.     private final IOperator redisOperator; 
  26.  
  27.     /** 
  28.      * 主鍵標(biāo)識 
  29.      * @since 0.0.1 
  30.      */ 
  31.     private final Id id; 
  32.  
  33.     public LockRedis(IWait wait, IOperator redisOperator, Id id) { 
  34.         super(wait); 
  35.         this.redisOperator = redisOperator; 
  36.         this.id = id; 
  37.     } 
  38.  
  39.     @Override 
  40.     public boolean tryLock(String key) { 
  41.         final String requestId = id.id(); 
  42.         IdThreadLocalHelper.put(requestId); 
  43.  
  44.         return redisOperator.lock(key, requestId, LockRedisConst.DEFAULT_EXPIRE_MILLS); 
  45.     } 
  46.  
  47.     @Override 
  48.     public void unlock(String key) { 
  49.         final String requestId = IdThreadLocalHelper.get(); 
  50.         if(StringUtil.isEmpty(requestId)) { 
  51.             String threadName = Thread.currentThread().getName(); 
  52.             throw new LockRedisException("Thread " + threadName +" not contains requestId"); 
  53.         } 
  54.  
  55.         boolean unlock = redisOperator.unlock(key, requestId); 
  56.         if(!unlock) { 
  57.             throw new LockRedisException("Unlock key " + key + " result is failed!"); 
  58.         } 
  59.     } 

這里就是 redis 鎖的核心實現(xiàn)了,如果不太理解,建議回顧一下原理篇:

redis 分布式鎖原理詳解

加鎖

加鎖部分,這里會生成一個 id 標(biāo)識,用于區(qū)分當(dāng)前操作者。

為了安全也設(shè)置了默認(rèn)的超時時間。

當(dāng)然這里是為了簡化調(diào)用者的使用成本,開發(fā)在使用的時候只需要關(guān)心自己要加鎖的 key 即可。

當(dāng)然,甚至連加鎖的 key 都可以進一步抽象掉,比如封裝 @DistributedLock 放在方法上,即可實現(xiàn)分布式鎖。這個后續(xù)有時間可以拓展,原理也不難。

解鎖

解鎖的時候,就會獲取當(dāng)前進程的持有標(biāo)識。

憑借當(dāng)前線程持有的 id 標(biāo)識,去解鎖。

IOperator

我們對 redis 的操作進行了抽象,為什么抽象呢?

因為 redis 服務(wù)種類實際很多,可以是 redis 單點,集群,主從,哨兵。

連接的客戶端也可以很多,jedis,spring redisTemplate, codis, redisson 等等。

這里為了后期拓展方便,就對操作進行了抽象。

接口

定義接口如下:

  1. package com.github.houbb.lock.redis.support.operator; 
  2.  
  3. /** 
  4.  * Redis 客戶端 
  5.  * @author binbin.hou 
  6.  * @since 0.0.1 
  7.  */ 
  8. public interface IOperator { 
  9.  
  10.     /** 
  11.      * 嘗試獲取分布式鎖 
  12.      * 
  13.      * @param lockKey    鎖 
  14.      * @param requestId  請求標(biāo)識 
  15.      * @param expireTimeMills 超期時間 
  16.      * @return 是否獲取成功 
  17.      * @since 0.0.1 
  18.      */ 
  19.     boolean lock(String lockKey, String requestId, int expireTimeMills); 
  20.  
  21.     /** 
  22.      * 解鎖 
  23.      * @param lockKey 鎖 key 
  24.      * @param requestId 請求標(biāo)識 
  25.      * @return 結(jié)果 
  26.      * @since 0.0.1 
  27.      */ 
  28.     boolean unlock(String lockKey, String requestId); 
  29.  

jedis 實現(xiàn)

我們實現(xiàn)一個 jedis 單點版本的:

  1. package com.github.houbb.lock.redis.support.operator.impl; 
  2.  
  3. import com.github.houbb.lock.redis.constant.LockRedisConst; 
  4. import com.github.houbb.lock.redis.support.operator.IOperator; 
  5. import redis.clients.jedis.Jedis; 
  6.  
  7. import java.util.Collections; 
  8.  
  9. /** 
  10.  * Redis 客戶端 
  11.  * @author binbin.hou 
  12.  * @since 0.0.1 
  13.  */ 
  14. public class JedisOperator implements IOperator { 
  15.  
  16.     /** 
  17.      * jedis 客戶端 
  18.      * @since 0.0.1 
  19.      */ 
  20.     private final Jedis jedis; 
  21.  
  22.     public JedisOperator(Jedis jedis) { 
  23.         this.jedis = jedis; 
  24.     } 
  25.  
  26.     /** 
  27.      * 嘗試獲取分布式鎖 
  28.      * 
  29.      * expireTimeMills 保證當(dāng)前進程掛掉,也能釋放鎖 
  30.      * 
  31.      * requestId 保證解鎖的是當(dāng)前進程(鎖的持有者) 
  32.      * 
  33.      * @param lockKey         鎖 
  34.      * @param requestId       請求標(biāo)識 
  35.      * @param expireTimeMills 超期時間 
  36.      * @return 是否獲取成功 
  37.      * @since 0.0.1 
  38.      */ 
  39.     @Override 
  40.     public boolean lock(String lockKey, String requestId, int expireTimeMills) { 
  41.         String result = jedis.set(lockKey, requestId, LockRedisConst.SET_IF_NOT_EXIST, LockRedisConst.SET_WITH_EXPIRE_TIME, expireTimeMills); 
  42.         return LockRedisConst.LOCK_SUCCESS.equals(result); 
  43.     } 
  44.  
  45.     /** 
  46.      * 解鎖 
  47.      * 
  48.      * (1)使用 requestId,保證為當(dāng)前鎖的持有者 
  49.      * (2)使用 lua 腳本,保證執(zhí)行的原子性。 
  50.      * 
  51.      * @param lockKey   鎖 key 
  52.      * @param requestId 請求標(biāo)識 
  53.      * @return 結(jié)果 
  54.      * @since 0.0.1 
  55.      */ 
  56.     @Override 
  57.     public boolean unlock(String lockKey, String requestId) { 
  58.         String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
  59.         Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); 
  60.         return LockRedisConst.RELEASE_SUCCESS.equals(result); 
  61.     } 
  62.  

這里時最核心的部分。

別看簡單幾行代碼,需要注意的點還是很多的。

加鎖

加鎖時附帶 requestId,用來標(biāo)識自己為鎖的持有者。

SETNX 當(dāng) key 不存在時才進行加鎖。

設(shè)置加鎖的過期時間,避免因異常等原因未釋放鎖,導(dǎo)致鎖的長時間占用。

解鎖

使用 lua 腳本,保證操作的原子性。

為了證明為鎖的持有者,傳入 requestId。

測試驗證

maven 引入

  1. <dependency> 
  2.     <groupId>com.github.houbb</groupId> 
  3.     <artifactId>lock-core</artifactId> 
  4.     <version>0.0.1</version> 
  5. </dependency> 

測試代碼

  1. Jedis jedis = new Jedis("127.0.0.1"6379); 
  2. IOperator operator = new JedisOperator(jedis); 
  3.  
  4. // 獲取鎖 
  5. ILock lock = LockRedisBs.newInstance().operator(operator).lock(); 
  6.  
  7. try { 
  8.     boolean lockResult = lock.tryLock(); 
  9.     System.out.println(lockResult); 
  10.     // 業(yè)務(wù)處理 
  11. catch (Exception e) { 
  12.     e.printStackTrace(); 
  13. finally { 
  14.     lock.unlock(); 

小結(jié)

到這里,一個簡單版本的 redis 分布式鎖就實現(xiàn)完成了。

當(dāng)然還有很多可以改進的地方:

(1)比如引入遞增的 sequence,避免分布式鎖中的 GC 導(dǎo)致的問題

(2)對于更多 redis 服務(wù)端+客戶端的支持

(3)對于注解式 redis 分布式鎖的支持

責(zé)任編輯:張燕妮 來源: 今日頭條
相關(guān)推薦

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

Redis數(shù)據(jù)分布式鎖

2021-11-26 06:43:19

Java分布式

2019-06-19 15:40:06

分布式鎖RedisJava

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2023-03-01 08:07:51

2024-04-01 05:10:00

Redis數(shù)據(jù)庫分布式鎖

2024-10-07 10:07:31

2023-10-11 09:37:54

Redis分布式系統(tǒng)

2019-12-25 14:35:33

分布式架構(gòu)系統(tǒng)

2020-07-30 09:35:09

Redis分布式鎖數(shù)據(jù)庫

2020-07-15 16:50:57

Spring BootRedisJava

2024-11-28 15:11:28

2022-05-18 10:38:51

Redis分布式鎖數(shù)據(jù)

2023-01-13 07:39:07

2022-06-16 08:01:24

redis分布式鎖

2021-10-09 11:34:59

MySQL分布式鎖庫存

2022-03-04 09:54:04

Redis分布式鎖腳本

2021-02-28 07:49:28

Zookeeper分布式

2017-01-16 14:13:37

分布式數(shù)據(jù)庫
點贊
收藏

51CTO技術(shù)棧公眾號