SpringBoot integration实现分布式锁的示例详解

 更新时间:2023年12月11日 08:07:35   作者:zooooooooy  
常规项目都是采用Redission来实现分布式锁,进行分布式系统中资源竞争加锁操作,偶然发现SpringBoot中的integration也实现多种载体的分布式锁控制,下面我们就来看看具体实现方法吧

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

代码集成

引入

// 分布式锁
implementation 'org.springframework.boot:spring-boot-starter-integration'
implementation('org.springframework.integration:spring-integration-redis')

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

锁注册

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

@Bean
open fun redisLockRegistry(redisConnectionFactory: RedisConnectionFactory): RedisLockRegistry {

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

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

加锁

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

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

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

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

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

使用上非常简单。

细粒度加锁

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

@Scheduled(cron = "0 0 0/1 * * ?")
fun scheduleRemoveRedisLock() {

    redisLockRegistry.expireUnusedOlderThan(1000 * 60 * 60)
}

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

核心逻辑

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

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;
}

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

  • 超过等待时间
  • redis返回是否获取到锁

Redis锁逻辑判断

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来进行分布式锁的编写了。

到此这篇关于SpringBoot integration实现分布式锁的示例详解的文章就介绍到这了,更多相关SpringBoot integration分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Java的JDBC中Statement与PreparedStatement对象

    详解Java的JDBC中Statement与PreparedStatement对象

    这篇文章主要介绍了详解Java的JDBC中Statement与PreparedStatement对象,PreparedStatement一般来说比使用Statement效率更高,需要的朋友可以参考下
    2015-12-12
  • SpringBoot接口或方法进行失败重试的实现方式

    SpringBoot接口或方法进行失败重试的实现方式

    为了防止网络抖动,影响我们核心接口或方法的成功率,通常我们会对核心方法进行失败重试,如果我们自己通过for循环实现,会使代码显得比较臃肿,所以本文给大家介绍了SpringBoot接口或方法进行失败重试的实现方式,需要的朋友可以参考下
    2024-07-07
  • MyBatis拦截器动态替换表名的方法详解

    MyBatis拦截器动态替换表名的方法详解

    因为我们持久层框架更多地使用MyBatis,那我们就借助于MyBatis的拦截器来完成我们的功能,这篇文章主要给大家介绍了关于MyBatis拦截器动态替换表名的相关资料,需要的朋友可以参考下
    2022-04-04
  • Java程序快速合并多个Word(docx)文档

    Java程序快速合并多个Word(docx)文档

    这篇文章主要为大家介绍了如何使用Java程序快速合并多个Word(docx)文档实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Java语言基于无向有权图实现克鲁斯卡尔算法代码示例

    Java语言基于无向有权图实现克鲁斯卡尔算法代码示例

    这篇文章主要介绍了Java语言基于无向有权图实现克鲁斯卡尔算法代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • java开发中嵌套类的详解及实例

    java开发中嵌套类的详解及实例

    这篇文章主要介绍了 java开发中嵌套类的详解及实例的相关资料,一般把定义内部类的外围类成为包装类(enclosing class)或者外部类,需要的朋友可以参考下
    2017-07-07
  • 使用JAVA+Maven+TestNG框架实现超详细Appium测试安卓真机教程

    使用JAVA+Maven+TestNG框架实现超详细Appium测试安卓真机教程

    这篇文章主要介绍了使用JAVA+Maven+TestNG框架实现超详细Appium测试安卓真机教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • 解析Java设计模式编程中命令模式的使用

    解析Java设计模式编程中命令模式的使用

    这篇文章主要介绍了Java设计模式编程中命令模式的使用,在一些处理请求响应的场合经常可以用到命令模式的编程思路,需要的朋友可以参考下
    2016-02-02
  • Java中字符数组、String类、StringBuffer三者之间相互转换

    Java中字符数组、String类、StringBuffer三者之间相互转换

    这篇文章主要介绍了Java中字符数组、String类、StringBuffer三者之间相互转换,需要的朋友可以参考下
    2018-05-05
  • SpringBoot整合多个Mq服务做法详解

    SpringBoot整合多个Mq服务做法详解

    SpringBoot整合rabbitmq很容易,但是整合的目的是为了使用,那要使用rabbitmq就要对其有一定的了解,不然容易整成一团浆糊。因为说到底,SpringBoot只是在封装rabbitmq的API,让其更容易使用而已,废话不多说,让我们一起整它
    2023-02-02

最新评论