SpringBoot integration 实现分布式锁

常规项目都是采用Redission来实现分布式锁,进行分布式系统中资源竞争加锁操作。需要单独引入Jar包,偶然发现SpringBoot中的integration也实现多种载体的分布式锁控制。

代码集成

引入

arduino 复制代码
// 分布式锁
implementation 'org.springframework.boot:spring-boot-starter-integration'
implementation('org.springframework.integration:spring-integration-redis')

采用最常见的redis来作为分布式锁的底层载体。

锁注册

@Configuration配置类中,添加分布式锁注册信息。

kotlin 复制代码
@Bean
open fun redisLockRegistry(redisConnectionFactory: RedisConnectionFactory): RedisLockRegistry {

    return RedisLockRegistry(redisConnectionFactory, "fcDistroLock", 20000L)
}

有两个核心参数,第一个指定的是分布式锁的前缀,第二个是指定分布式锁的过期时间。过期时间建议不要指定到过长,防止拖慢整体的业务响应速度。

加锁

在使用之前需要知道加锁的三个核心方法。

lock 直接加锁,一直等待
tryLock(无参数) 尝试加锁,未获取到锁,直接返回失败
tryLock(long time, TimeUnit unit) 尝试加锁,等待一定时间后未获取到锁,直接返回失败

建议使用带参数的尝试加锁,设置一个合适的超时时间。建议使用模式如下

csharp 复制代码
Lock lock = ...;
if (lock.tryLock(time)) {
  try {
    // manipulate protected state
  } finally {
    lock.unlock();
  }
} else {
  // perform alternative actions
}}

有一个点需要注意,当加锁失败时,需要考虑补偿机制。例如用户余额扣减失败,需要重新进行推送;或者加锁失败,抛出异常回滚本地事务等。

使用上非常简单。

细粒度加锁

可以通过上图可以看到,我们加锁的对象是用户id,并不是所有用户。代表不同用户之间操作是不受分布式事务限制。这里同步会衍生另外一个问题,如果用户id特别多,就会占用非常多的资源。这里就需要定时手动清除加锁对象,或者加锁成功后直接清除。个人建议使用定时清除,有助于减少对象的创建,提高系统吞吐量。

kotlin 复制代码
@Scheduled(cron = "0 0 0/1 * * ?")
fun scheduleRemoveRedisLock() {

    redisLockRegistry.expireUnusedOlderThan(1000 * 60 * 60)
}

RedisLockRegistry其实已经提供清除的方法,我们只需要指定清除的有效期即可。项目中指定的是清除1个小时之前的加锁对象。

核心逻辑

打开tryLock的实现类RedisLock很容易发现,每个加锁id都对应1个RedisLock,1个RedisLock中包含1个ReentrantLock,用来进行本地资源互斥。

java 复制代码
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
   long now = System.currentTimeMillis();
   if (!this.localLock.tryLock(time, unit)) { // 获取本地互斥锁
      return false;
   }
   try {
      long expire = now + TimeUnit.MILLISECONDS.convert(time, unit);
      boolean acquired;
      while (!(acquired = obtainLock()) && System.currentTimeMillis() < expire) { //NOSONAR
         Thread.sleep(100); //NOSONAR 防止请求过快,进行100Ms的休眠
      }
      if (!acquired) {
         this.localLock.unlock();
      }
      return acquired;
   }
   catch (Exception e) {
      this.localLock.unlock();
      rethrowAsLockException(e);
   }
   return false;
}

两个条件跳出循环获取锁的过程。

  1. 超过等待时间
  2. redis返回是否获取到锁

Redis锁逻辑判断

swift 复制代码
private static final String OBTAIN_LOCK_SCRIPT =
      "local lockClientId = redis.call('GET', KEYS[1])\n" +
            "if lockClientId == ARGV[1] then\n" +
            "  redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
            "  return true\n" +
            "elseif not lockClientId then\n" +
            "  redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" +
            "  return true\n" +
            "end\n" +
            "return false";

利用Redis的原子性进行锁资源判断,通过是否相同应用id来支持重入锁。

整体使用

使用上非常简单,没有锁续期,没有读写锁,也没有考虑重入锁的计数问题。功能上还是比Redission差不少,在一些业务相对比较简单的场景可以尝试使用SpringBoot自带的分布式锁。如果需要面对更细粒度的控制,提高性能,更复杂的锁控制,就需要使用到Redission来进行分布式锁的编写了。

相关推荐
计算机毕设指导611 分钟前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
paopaokaka_luck29 分钟前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
捂月2 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
FIN技术铺4 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小码的头发丝、4 小时前
Spring Boot 注解
java·spring boot
午觉千万别睡过4 小时前
RuoYI分页不准确问题解决
spring boot
2301_811274315 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
编程重生之路6 小时前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端
politeboy6 小时前
k8s启动springboot容器的时候,显示找不到application.yml文件
java·spring boot·kubernetes
世间万物皆对象12 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试