概述
提到分布式鎖大家都會想到如下兩種:
- 基于
元件,使用redlock算法實作Redisson
-
,利用Zookeeper的臨時順序節點模型實作Apache Curator
今天我們來說說第三種,使用
Spring Integration
實作,也是我個人比較推薦的一種。
Spring Integration
在基于Spring的應用程式中實作輕量級消息傳遞,并支援通過聲明擴充卡與外部系統內建。 Spring Integration的主要目标是提供一個簡單的模型來建構企業內建解決方案,同時保持關注點的分離,這對于生成可維護,可測試的代碼至關重要。我們熟知的
Spring Cloud Stream的底層就是Spring Integration。
官方位址:
https://github.com/spring-projects/spring-integrationSpring Integration提供的全局鎖目前為如下存儲提供了實作:
- Gemfire
- JDBC
- Redis
- Zookeeper
它們使用相同的API抽象,這意味着,不論使用哪種存儲,你的編碼體驗是一樣的。試想一下你目前是基于zookeeper實作的分布式鎖,哪天你想換成redis的實作,我們隻需要修改相關依賴和配置就可以了,無需修改代碼。下面是你使用
Spring Integration
實作分布式鎖時需要關注的方法:
方法名 | 描述 |
| 加鎖,如果已經被其他線程鎖住或者目前線程不能擷取鎖則阻塞 |
| 加鎖,除非目前線程被打斷。 |
| 嘗試加鎖,如果已經有其他鎖鎖住,擷取目前線程不能加鎖,則傳回false,加鎖失敗;加鎖成功則傳回true |
| 嘗試在指定時間内加鎖,如果已經有其他鎖鎖住,擷取目前線程不能加鎖,則傳回false,加鎖失敗;加鎖成功則傳回true |
| 解鎖 |
實戰
話不多說,我們看看使用
Spring Integration
如何基于redis和zookeeper快速實作分布式鎖,至于Gemfire 和 Jdbc的實作大家自行實踐。
基于Redis實作
- 引入相關元件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在application.yml中添加redis的配置
spring:
redis:
host: 172.31.0.149
port: 7111
- 建立配置類,注入
RedisLockRegistry
@Configuration
public class RedisLockConfiguration {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory){
return new RedisLockRegistry(redisConnectionFactory, "redis-lock");
}
}
- 編寫測試代碼
@RestController
@RequestMapping("lock")
@Log4j2
public class DistributedLockController {
@Autowired
private RedisLockRegistry redisLockRegistry;
@GetMapping("/redis")
public void test1() {
Lock lock = redisLockRegistry.obtain("redis");
try{
//嘗試在指定時間内加鎖,如果已經有其他鎖鎖住,擷取目前線程不能加鎖,則傳回false,加鎖失敗;加鎖成功則傳回true
if(lock.tryLock(3, TimeUnit.SECONDS)){
log.info("lock is ready");
TimeUnit.SECONDS.sleep(5);
}
} catch (InterruptedException e) {
log.error("obtain lock error",e);
} finally {
lock.unlock();
}
}
}
-
測試
啟動多個執行個體,分别通路
端點,一個正常秩序業務邏輯,另外一個執行個體通路出現如下錯誤/lock/redis
說明第二個執行個體沒有拿到鎖,證明了分布式鎖的存在。
注意,如果使用新版Springboot進行內建時需要使用Redis4版本,否則會出現下面的異常告警,主要是 unlock()
釋放鎖時使用了UNLINK指令,這個需要Redis4版本才能支援。
2020-05-14 11:30:24,781 WARN RedisLockRegistry:339 - The UNLINK command has failed (not supported on the Redis server?); falling back to the regular DELETE command
org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'UNLINK'
基于Zookeeper實作
- 引入元件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-zookeeper</artifactId>
</dependency>
- 在application.yml中添加zookeeper的配置
zookeeper:
host: 172.31.0.43:2181
-
ZookeeperLockRegistry
@Configuration
public class ZookeeperLockConfiguration {
@Value("${zookeeper.host}")
private String zkUrl;
@Bean
public CuratorFrameworkFactoryBean curatorFrameworkFactoryBean(){
return new CuratorFrameworkFactoryBean(zkUrl);
}
@Bean
public ZookeeperLockRegistry zookeeperLockRegistry(CuratorFramework curatorFramework){
return new ZookeeperLockRegistry(curatorFramework,"/zookeeper-lock");
}
}
@RestController
@RequestMapping("lock")
@Log4j2
public class DistributedLockController {
@Autowired
private ZookeeperLockRegistry zookeeperLockRegistry;
@GetMapping("/zookeeper")
public void test2() {
Lock lock = zookeeperLockRegistry.obtain("zookeeper");
try{
//嘗試在指定時間内加鎖,如果已經有其他鎖鎖住,擷取目前線程不能加鎖,則傳回false,加鎖失敗;加鎖成功則傳回true
if(lock.tryLock(3, TimeUnit.SECONDS)){
log.info("lock is ready");
TimeUnit.SECONDS.sleep(5);
}
} catch (InterruptedException e) {
log.error("obtain lock error",e);
} finally {
lock.unlock();
}
}
}
-
/lock/zookeeper