如何优雅的实现分布式锁


概述

提到分布式锁大家都会想到如下两种:
  • 基于Redisson组件,使用redlock算法实现
  • 基于Apache Curator,利用Zookeeper的临时顺序节点模型实现


今天我们来说说第三种,使用 Spring Integration 实现,也是我个人比较推荐的一种。

Spring Integration在基于Spring的应用程序中实现轻量级消息传递,并支持通过声明适配器与外部系统集成。 Spring Integration的主要目标是提供一个简单的模型来构建企业集成解决方案,同时保持关注点的分离,这对于生成可维护,可测试的代码至关重要。我们熟知的
Spring Cloud Stream的底层就是Spring Integration。

官方地址:https://github.com/spring-proj ... ation

Spring Integration提供的全局锁目前为如下存储提供了实现:
  • Gemfire
  • JDBC
  • Redis
  • ZooKeeper


它们使用相同的API抽象,这意味着,不论使用哪种存储,你的编码体验是一样的。试想一下你目前是基于ZooKeeper实现的分布式锁,哪天你想换成Redis的实现,我们只需要修改相关依赖和配置就可以了,无需修改代码。下面是你使用 Spring Integration实现分布式锁时需要关注的方法:
1.png

实战

话不多说,我们看看使用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端点,一个正常秩序业务逻辑,另外一个实例访问出现如下错误:
2.png

说明第二个实例没有拿到锁,证明了分布式锁的存在。

注意,如果使用新版Spring Boot进行集成时需要使用Redis 4版本,否则会出现下面的异常告警,主要是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端点,一个正常秩序业务逻辑,另外一个实例访问出现如下错误:
3.png

说明第二个实例没有拿到锁,证明了分布式锁的存在。

原文链接:https://mp.weixin.qq.com/s/yM1y8w1tHuT1m_aYIbehug,作者:JAVA日知录

0 个评论

要回复文章请先登录注册