天天看點

如何優雅的實作分布式鎖

概述

提到分布式鎖大家都會想到如下兩種:

  • 基于

    Redisson

    元件,使用redlock算法實作
  • Apache Curator

    ,利用Zookeeper的臨時順序節點模型實作

今天我們來說說第三種,使用

Spring Integration

實作,也是我個人比較推薦的一種。

Spring Integration

在基于Spring的應用程式中實作輕量級消息傳遞,并支援通過聲明擴充卡與外部系統內建。 Spring Integration的主要目标是提供一個簡單的模型來建構企業內建解決方案,同時保持關注點的分離,這對于生成可維護,可測試的代碼至關重要。我們熟知的

Spring Cloud Stream的底層就是Spring Integration。

官方位址:

https://github.com/spring-projects/spring-integration

Spring Integration提供的全局鎖目前為如下存儲提供了實作:

  • Gemfire
  • JDBC
  • Redis
  • Zookeeper

它們使用相同的API抽象,這意味着,不論使用哪種存儲,你的編碼體驗是一樣的。試想一下你目前是基于zookeeper實作的分布式鎖,哪天你想換成redis的實作,我們隻需要修改相關依賴和配置就可以了,無需修改代碼。下面是你使用

Spring Integration

實作分布式鎖時需要關注的方法:

方法名 描述

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的實作大家自行實踐。

基于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

如何優雅的實作分布式鎖