一个注解解决分布式锁和接口幂等性,springboot 实战 。强到离大谱

这篇具有很好参考价值的文章主要介绍了一个注解解决分布式锁和接口幂等性,springboot 实战 。强到离大谱。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

如今基本上都是分布式、多节点时代,我们业务代码中避免不了需要使用分布式锁。admin4j-lock
为我们提供分布式锁解决方案。支持redissonzookeeper分布式锁

功能

  • 支持redisson分布式锁和zookeeper 分布式锁

  • 支持可重入锁

  • 支持读写锁

  • 支持红锁 redLock

  • 支持一个注解解决分布式锁问题

  • 支持一个注解解决接口幂等性问题

  • 支持编程式使用分布式锁

  • 锁名称支持 el表达式

使用方式

引入POM, 默认使用redisson分布式锁

<dependency>
    <groupId>com.admin4j</groupId>
    <artifactId>lock-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>

最新版查看 https://central.sonatype.com/artifact/com.admin4j/lock-spring-boot-starter/

注解方式使用

 	@DistributedLock(value = "'testDLock:'+#id", user = true)
    public R testDLock(String name, Integer id) throws InterruptedException {

        Thread.sleep(30000);
        return R.ok();
    }

DistributedLock 注解参数详解

  • prefix:锁key的前缀
  • lockModel:指定锁模式 REENTRANT(可重入锁),FAIR(公平锁) ,REDLOCK(红锁),READ(读锁), WRITE(写锁)
  • key、value: 锁名称,支持el 表达式
  • keyGenerator: 所名称生成器。Spring注入基础DLockKeyGenerator实现类即可
  • tryLock:是否尝试获取锁。成功获取则进入锁;获取失败则抛出异常。 true 获取不到锁,会立即返回,不会阻塞。false(默认)
    获取不到锁,会阻塞当前线程
  • tenant: 是否开启租户(默认false)。开启租户会在可能后面拼接上租户。 需要实现 ILoginUserInfoService 接口告诉当前登录用户的租户信息
  • user:是否开启用户(默认false)开启用户模式 需要实现 ILoginUserInfoService 接口告诉当前登录用户的唯一ID标识
  • executor: 分布式锁执行器。指定使用 redisson 还是 zookeeper 分布式锁

编程式使用

//使用工具类
 DistributedLockUtil.tryLockWithError("DistributedLock:" + id, () -> {
            System.out.println("i get the lock   = " + name);
           //doSomething
        });

使用zookeeper 分布式锁

        <dependency>
            <groupId>com.admin4j</groupId>
            <artifactId>lock-spring-boot-starter</artifactId>
            <version>0.2.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>lock-redisson-spring-boot-starter</artifactId>
                    <groupId>com.admin4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.admin4j</groupId>
            <artifactId>lock-zookeeper-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

引入lock-zookeeper-spring-boot-starter 依赖,默认使用zookeeper分布式锁

指定分布式锁执行器

同时引入 zookeeper 和 redisson 。默认使用redisson,可以指定 executor 执行器来切换分布式类型

        <dependency>
            <groupId>com.admin4j</groupId>
            <artifactId>lock-spring-boot-starter</artifactId>
            <version>0.2.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>lock-redisson-spring-boot-starter</artifactId>
                    <groupId>com.admin4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.admin4j</groupId>
            <artifactId>lock-zookeeper-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

指定 zookeeper分布式锁 例子:

    @DistributedLock( value = "'testDLock:'+#id", user = true, executor = ZookeeperLockExecutor.class)
    public R testDLock(String name, Integer id) throws InterruptedException {

        Thread.sleep(30000);
        return R.ok();
    }

指定 redisson分布式锁 例子:

    @DistributedLock( value = "'testDLock:'+#id", user = true, executor = RedissonLockExecutor.class)
    public R testDLock(String name, Integer id) throws InterruptedException {

        Thread.sleep(30000);
        return R.ok();
    }

使用示例代码 https://github.com/admin4j/admin4j-example

一个注解搞定接口幂等性

	@GetMapping("Idempotent")
    @Idempotent(tryLock = true, key = "'Idempotent'+#id")
    public R Idempotent(String name, Integer id) throws InterruptedException {

        Thread.sleep(30000);
        return R.ok();
    }

需要实现 ILoginUserInfoService 接口,返回当前登录用户唯一ID

分布式锁原理

1. redis 原生命令的不足

使用redis做分布式锁相对于更简单和高效。但不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引来一些意想不到的坑。

1.1 非原子性操作
if (jedis.setnx(lockKey, val) == 1) {
   jedis.expire(lockKey, timeout);
}

改进方式使用

String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
    return true;
}
return false;
1.2 释放了别人的锁

假如线程A和线程B,都使用lockKey加锁。线程A加锁成功了,但是由于业务功能耗时时间很长,超过了设置的超时时间。这时候,redis会自动释放lockKey锁。此时,线程B就能给lockKey加锁成功了,接下来执行它的业务操作。恰好这个时候,线程A执行完了业务功能,接下来,在finally方法中释放了锁lockKey。这不就出问题了,线程B的锁,被线程A释放了。不知道你们注意到没?在使用set
命令加锁时,除了使用lockKey锁标识,还多设置了一个参数:requestId,为什么要需要记录requestId呢?

答:requestId是在释放锁的时候用的。

在释放锁的时候,先获取到该锁的值(之前设置值就是requestId),然后判断跟之前设置的值是否相同,如果相同才允许删除锁,返回成功。如果不同,则直接返回失败。

此外,使用lua脚本,也能解决释放了别人的锁的问题:

if redis.call('get', KEYS[1]) == ARGV[1] then 
 return redis.call('del', KEYS[1]) 
else 
  return 0 
end
1.3 抢不到锁的线程不会阻塞,大量失败请求

当大量请求进入时,只有一个会成功,其他的都是失败。每1万个请求,有1个成功。再1万个请求,有1个成功。如此下去,直到库存不足。这就变成均匀分布的秒杀了,跟我们想象中的不一样。

1.4 不支持锁重入问题
1.5 锁竞争问题,不支持读写锁,锁颗粒度大

如果有大量需要写入数据的业务场景,使用普通的redis分布式锁是没有问题的。

但如果有些业务场景,写入的操作比较少,反而有大量读取的操作。这样直接使用普通的redis分布式锁,会不会有点浪费性能?

我们都知道,锁的粒度越粗,多个线程抢锁时竞争就越激烈,造成多个线程锁等待的时间也就越长,性能也就越差。

所以,提升redis分布式锁性能的第一步,就是要把锁的粒度变细。添加读写锁

1.6 锁超时问题

如果线程A加锁成功了,但是由于业务功能耗时时间很长,超过了设置的超时时间,这时候redis会自动释放线程A加的锁。其他线程就会抢到锁,但是A线程还未结束

1.7 主从复制的问题

如果redis存在多个实例。比如:做了主从,或者使用了哨兵模式,由于redis 主从复制是异步的(AP模型) 就会出现问题。可以通过redLock
解决

2. redisson 分布式锁接口方案

使用原生的redis 会有各种问题,我们来看看redisson框架给我的解决方法

2.1 看门狗原理

如果负责储存这个分布式锁的 Redisson
节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。

默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

如果我们未制定 lock 的超时时间,就使用 30 秒作为看门狗的默认时间。只要占锁成功,就会启动一个定时任务:每隔 10
秒重新给锁设置过期的时间,过期时间为 30 秒。

2.2 主从复制的问题

提供了一个专门的类:RedissonRedLock,使用了Redlock算法。

Redisson
原理参考 https://blog.csdn.net/agonie201218/article/details/115339670

redisson
操作示例 https://blog.csdn.net/agonie201218/article/details/122084140

redis分布式锁的坑 https://blog.csdn.net/agonie201218/article/details/121423212

3.Zookeeper 分布式锁接口方案

zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能
注册个监听器监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。

参考

java分布式锁解决方案 redisson or
ZooKeeper https://blog.csdn.net/agonie201218/article/details/122446601

万字总结Zookeeper客户端Curator操作Api https://andyoung.blog.csdn.net/article/details/130115913

项目地址

https://github.com/admin4j/admin4j-framework/tree/master/admin4j-lock文章来源地址https://www.toymoban.com/news/detail-425850.html

到了这里,关于一个注解解决分布式锁和接口幂等性,springboot 实战 。强到离大谱的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用 redis 实现分布式接口限流注解 RedisLimit

    前言 很多时候,由于种种不可描述的原因,我们需要针对单个接口实现接口限流,防止访问次数过于频繁。这里就用 redis+aop 实现一个限流接口注解 @RedisLimit 代码 点击查看RedisLimit注解代码 AOP代码 点击查看aop代码 lua脚本代码 注意:脚本代码是放在 resources 文件下的,它的类型是

    2024年02月08日
    浏览(56)
  • 【分布式】: 幂等性和实现方式

    幂等(idempotent、idempotence)是一个数学与计算机学概念, 常见于抽象代数中。在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统

    2024年02月08日
    浏览(43)
  • 如何保证分布式情况下的幂等性

    关于这个分布式服务的幂等性,这是在使用分布式服务的时候会经常遇到的问题,比如,重复提交的问题。而幂等性,就是为了解决问题存在的一个概念了。 什么是幂等 幂等(idempotent、idempotence)是⼀个数学与计算机学概念,常⻅于抽象代数中。 在编程中⼀个幂等操作的特

    2024年02月07日
    浏览(49)
  • 云事业群CTO线技术晋升考核机试题-分布式专题-G 分布式幂等架构设计

    作者:田超凡 1 幂等的基本概念 答:幂等指的是同一块业务逻辑重复多次执行时,只能令其生效一次,防止重复执行。 2 幂等的发生场景 答: RPC 调用接口的幂等性问题 MQ 消费者防止重复消费的幂等性问题 定时任务防止重复执行的幂等性问题 3 RPC调用接口的幂等性问题产生

    2024年02月16日
    浏览(38)
  • 一个注解实现接口幂等性,真心优雅!

    简单来说,就是对一个接口执行重复的多次请求,与一次请求所产生的结果是相同的,听起来非常容易理解,但要真正的在系统中要始终保持这个目标,是需要很严谨的设计的,在实际的生产环境下,我们应该保证任何接口都是幂等的,而如何正确的实现幂等,就是本文要讨

    2024年02月03日
    浏览(40)
  • 【Spring Cloud系列】- 分布式系统中实现幂等性的几种方式

    在开发订单系统时,我们常遇见支付问题,既用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。在以前的单应用系统中,

    2024年02月10日
    浏览(43)
  • 开源:Taurus.Idempotent 分布式幂等性锁框架,支持 .Net 和 .Net Core 双系列版本

    分布式幂等性框架的作用是确保在分布式系统中的操作具有幂等性,即无论操作被重复执行多少次,最终的结果都是一致的。幂等性是指对同一操作的多次执行所产生的效果与仅执行一次的效果相同。 以下是分布式幂等性框架的主要作用: 避免重复操作: 在分布式系统中,

    2024年03月09日
    浏览(37)
  • 使用注解实现REDIS分布式锁

    有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响。 使用 Redis 作为分布式锁,将锁的状态放到 Redis 统一维护,解决集群中单机 JVM 信息不互通的问题,规定操作顺序,保护用户的数据正确。 梳理

    2024年02月02日
    浏览(36)
  • 自定义注解,基于redis实现分布式锁

    1.1、注解的基础知识 实现自定义注解其实很简单,格式基本都差不多。也就参数可能变一变。 @Retention:取值决定了注解在什么时候生效,一般都是取运行时,也就是RetentionPolicy.RUNTIME。 @Target:决定了这个注解可以使用在哪些地方,可以取方法,字段,类等。 注解这就定义

    2024年02月08日
    浏览(36)
  • 使用Redission自定义注解实现分布式锁(声明式)

    1.主要依赖 配置redission 自定义注解 sepl工具类 aop

    2024年02月11日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包