大牛解析——基於 Redis 的分布式鎖

}else {

//throw new RuntimeException("instance is error") ;

return false ;

}

if (UNLOCK_MSG.equals(result)){

return true ;

}else {

return false ;

}

}

這裡使用了一個 lua 腳本來判斷 value 是否相等,相等才執行 del 命令。

使用 lua 也可以保證這裡兩個操作的原子性。

因此上文提到的四個基本特性也能滿足了:

  • 使用 Redis 可以保證性能。
  • 阻塞鎖與非阻塞鎖見上文。
  • 利用超時機制解決了死鎖。
  • Redis 支持集群部署提高了可用性。

使用

我自己有擼了一個完整的實現,並且已經用於了生產,有興趣的朋友可以開箱使用:

maven 依賴:

1

2

3

4

5

top.crossoverjie.opensource

distributed-redis-lock

1.0.0

配置 bean :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@Configuration

public class RedisLockConfig {

@Bean

public RedisLock build(){

RedisLock redisLock = new RedisLock() ;

HostAndPort hostAndPort = new HostAndPort("127.0.0.1",7000) ;

JedisCluster jedisCluster = new JedisCluster(hostAndPort) ;

// Jedis 或 JedisCluster 都可以

redisLock.setJedisCluster(jedisCluster) ;

return redisLock ;

}

}

使用:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

@Autowired

private RedisLock redisLock ;

public void use() {

String key = "key";

String request = UUID.randomUUID().toString();

try {

boolean locktest = redisLock.tryLock(key, request);

if (!locktest) {

System.out.println("locked error");

return;

}

//do something

} finally {

redisLock.unlock(key,request) ;

}

}

使用很簡單。這裡主要是想利用 Spring 來幫我們管理 RedisLock 這個單例的 bean,所以在釋放鎖的時候需要手動(因為整個上下文只有一個 RedisLock 實例)的傳入 key 以及 request(api 看起來不是特別優雅)。

也可以在每次使用鎖的時候 new 一個 RedisLock 傳入 key 以及 request,這樣倒是在解鎖時很方便。但是需要自行管理 RedisLock 的實例。各有優劣吧。

項目源碼在:

https://github.com/crossoverJie/distributed-lock-redis

歡迎討論。

單測

在做這個項目的時候讓我不得不想提一下單測

因為這個應用是強依賴於第三方組件的(Redis),但是在單測中我們需要排除掉這種依賴。比如其他夥伴 fork 了該項目想在本地跑一遍單測,結果運行不起來:

  1. 有可能是 Redis 的 ip、端口和單測裡的不一致。
  2. Redis 自身可能也有問題。
  3. 也有可能是該同學的環境中並沒有 Redis。

所以最好是要把這些外部不穩定的因素排除掉,單測只測我們寫好的代碼。

於是就可以引入單測利器 Mock 了。

它的想法很簡答,就是要把你所依賴的外部資源統統屏蔽掉。如:數據庫、外部接口、外部文件等等。

使用方式也挺簡單,可以參考該項目的單測:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@Test

public void tryLock() throws Exception {

String key = "test";

String request = UUID.randomUUID().toString();

Mockito.when(jedisCluster.set(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),

Mockito.anyString(), Mockito.anyLong())).thenReturn("OK");

boolean locktest = redisLock.tryLock(key, request);

System.out.println("locktest=" + locktest);

Assert.assertTrue(locktest);

//check

Mockito.verify(jedisCluster).set(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),

Mockito.anyString(), Mockito.anyLong());

}

這裡只是簡單演示下,可以的話下次仔細分析分析。

它的原理其實也挺簡單,debug 的話可以很直接的看出來:

大牛解析——基於 Redis 的分佈式鎖

這裡我們所依賴的 JedisCluster 其實是一個 cglib 代理對象。所以也不難想到它是如何工作的。

比如這裡我們需要用到 JedisCluster 的 set 函數並需要它的返回值。

Mock 就將該對象代理了,並在實際執行 set 方法後給你返回了一個你自定義的值。

這樣我們就可以隨心所欲的測試了,完全把外部依賴所屏蔽了

總結

至此一個基於 Redis 的分佈式鎖完成,但是依然有些問題。

  • 如在 key 超時之後業務並沒有執行完畢但卻自動釋放鎖了,這樣就會導致併發問題。
  • 就算 Redis 是集群部署的,如果每個節點都只是 master 沒有 slave,那麼 master 宕機時該節點上的所有 key 在那一時刻都相當於是釋放鎖了,這樣也會出現併發問題。就算是有 slave 節點,但如果在數據同步到 salve 之前 master 宕機也是會出現上面的問題。


分享到:


相關文章: