Redis分布式鎖


畢業后一直做.Net工作,我喜歡C#更優美簡潔的語法(雖然有些關鍵字或者類的命名有點隱晦)。當然Java也不能丟掉,Java的很多開源技術更能讓我拓展視野,在分布式方面也更容易上手。空余時間正在將自己的一個個人項目用java重寫,設計為一個分布式的項目,其中有減庫存的操作。要做到全局同步,分布式鎖正好用於解決此問題,在分布式環境下,多線程共享臨界資源的場景下,分布式鎖是一種非常重要的組件。Redis的單線程,setnx命令也是得天獨厚。

我定義了一個接口,希望在將來做Redisson RedLock算法實現和ZK的分布式鎖實現。下面lock是阻塞鎖,實現應包含重試機制,trylock是非阻塞鎖。

 1 public interface DistributedLocker {
 2 
 3     boolean lock(String key) throws InterruptedException;
 4 
 5     boolean lock(String key,int expireSecond,int waitSecond) throws InterruptedException;
 6 
 7     boolean tryLock(String key);
 8 
 9     boolean tryLock(String key,int expireSecond);
10 
11     boolean releaseLock(String key);
12 }
 1 public class RedisLocker implements DistributedLocker {
 2 
 3     private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
 4     private JedisClient jedisClient;
 5 
 6     public void setClient(JedisClient client) {
 7         this.jedisClient = client;
 8     }
 9 
10     private final int defaultExpireSeconds = 5;
11 
12     private final int defaultWaitSeconds = 100;
13 
14     @Override
15     public boolean lock(String key) throws InterruptedException {
16 
17         return lock(key, defaultExpireSeconds, defaultWaitSeconds);
18     }
19 
20     @Override
21     public boolean lock(String key, int expireSecond, int waitSecond) throws InterruptedException {
22         int maxDelayMillis = waitSecond * 1000;
23         boolean isLockSucceed=false;
24         while (maxDelayMillis>0){
25             int delayTime = (int) (Math.random() * 20);
26             maxDelayMillis-=delayTime;
27             Thread.sleep(delayTime);
28             System.out.println("wait "+delayTime);
29             long startTime = System.currentTimeMillis();
30             isLockSucceed= tryLock(key,expireSecond);
31             long endTime = System.currentTimeMillis();
32             System.out.println("network"+(endTime-startTime));
33             maxDelayMillis=maxDelayMillis-delayTime-(int)(endTime-startTime);
34             System.out.println("剩余"+maxDelayMillis);
35             if (isLockSucceed){
36                 System.out.println("lock ok 剩余"+maxDelayMillis);
37                 break;
38             }
39         }
40 
41         return isLockSucceed;
42     }
43 
44     @Override
45     public boolean tryLock(String key) {
46         return tryLock(key, defaultExpireSeconds);
47     }
48 
49     @Override
50     public boolean tryLock(String key, int expireSeconds) {
51 
52         String lockToken = threadLocal.get();
53         if (StringUtils.isBlank(lockToken)) {
54             System.out.println("token為空" + lockToken);
55             lockToken = UUID.randomUUID().toString();
56             threadLocal.set(lockToken);
57         }
58 
59         boolean isLockSucceed = jedisClient.setNX(key, lockToken);
60         if (isLockSucceed) {    //如果加鎖成功
61             jedisClient.expire(key, expireSeconds);
62         } else {//如果加鎖失敗  判斷是否應該重入
63             String tokenFromRedis = jedisClient.get(key);
64             System.out.println("tokenFromRedis:" + tokenFromRedis);
65             if (lockToken.equals(tokenFromRedis)) {
66                 isLockSucceed = true;//可重入
67                 System.out.println("重入成功");
68             }
69         }
70         System.out.println("獲取鎖結果" + isLockSucceed + " token為" + lockToken);
71         return isLockSucceed;
72     }
73 
74     @Override
75     public boolean releaseLock(String key) {
76         return jedisClient.del(key);
77     }
78 }
可靠性分析:
1. key是一定要設置過期時間的,setnx原生命令不包含expire選項,需要使用key的命令。非原子操作遇到expire命令不成功也許是個災難。原生的命令好像支持直接帶expire 在jedis中不確定是不是版本問題 需要再確認。
2. 釋放鎖,也就是del key的時候,命令可能會執行失敗,導致其他線程長期拿不到鎖。
3. 鎖丟失,為了解決單點問題,可能引入主從加哨兵(master&&slave&&sentinel),或者集群(redis cluster)。拿主從來說,如果master setnx命令執行成功,在數據未同步給slave的瞬間, master掛掉,從升主。這時 一個setnx的key,可能會被兩個線程set成功,也就是兩個線程都拿到了鎖。
4. 臨界時間,如果一個線程使用鎖后,准備del 鎖,這時key過期了,其他線程立即創建key 持有鎖,現在del命令到達redis並刪除了剛創建的key,就很慘了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM