如何優雅的實現分佈式鎖

image.png

概述

提到分佈式鎖你們都會想到以下兩種:java

  • 基於Redisson組件,使用redlock算法實現
  • 基於Apache Curator,利用Zookeeper的臨時順序節點模型實現

今天咱們來講說第三種,使用 Spring Integration 實現,也是我我的比較推薦的一種。git

Spring Integration在基於Spring的應用程序中實現輕量級消息傳遞,並支持經過聲明適配器與外部系統集成。 Spring Integration的主要目標是提供一個簡單的模型來構建企業集成解決方案,同時保持關注點的分離,這對於生成可維護,可測試的代碼相當重要。咱們熟知的 Spring Cloud Stream的底層就是Spring Integration。github

官方地址:github.com/spring-proj…redis

Spring Integration提供的全局鎖目前爲以下存儲提供了實現:算法

  • Gemfire
  • JDBC
  • Redis
  • Zookeeper

它們使用相同的API抽象,這意味着,不論使用哪一種存儲,你的編碼體驗是同樣的。試想一下你目前是基於zookeeper實現的分佈式鎖,哪天你想換成redis的實現,咱們只須要修改相關依賴和配置就能夠了,無需修改代碼。下面是你使用 Spring Integration 實現分佈式鎖時須要關注的方法:spring

方法名 描述
lock() Acquires the lock. 加鎖,若是已經被其餘線程鎖住或者當前線程不能獲取鎖則阻塞
lockInterruptibly() Acquires the lock unless the current thread is interrupted. 加鎖,除非當前線程被打斷。
tryLock() Acquires the lock only if it is free at the time of invocation. 嘗試加鎖,若是已經有其餘鎖鎖住,獲取當前線程不能加鎖,則返回false,加鎖失敗;加鎖成功則返回true
tryLock(long time, TimeUnit unit) Acquires the lock if it is free within the given waiting time and the current thread has not been interrupted. 嘗試在指定時間內加鎖,若是已經有其餘鎖鎖住,獲取當前線程不能加鎖,則返回false,加鎖失敗;加鎖成功則返回true
unlock() Releases the lock. 解鎖

實戰

話很少說,咱們看看使用 Spring Integration 如何基於redis和zookeeper快速實現分佈式鎖,至於Gemfire 和 Jdbc的實現你們自行實踐。bash

基於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 端點,一個正常秩序業務邏輯,另一個實例訪問出現以下錯誤
    image.png
    說明第二個實例沒有拿到鎖,證實了分佈式鎖的存在。

注意,若是使用新版Springboot進行集成時須要使用Redis4版本,不然會出現下面的異常告警,主要是unlock() 釋放鎖時使用了UNLINK命令,這個須要Redis4版本才能支持。app

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 端點,一個正常秩序業務邏輯,另一個實例訪問出現以下錯誤
    image.png
    說明第二個實例沒有拿到鎖,證實了分佈式鎖的存在。
相關文章
相關標籤/搜索