redis深入浅出分布式锁实现下篇

 更新时间:2022年08月30日 17:10:40   作者:一个风轻云淡  
在单体应用中,如果我们对共享数据不进行加锁操作,会出现数据一致性问题,我们的解决办法通常是加锁。下面我们一起聊聊使用redis来实现分布式锁

优化之UUID防误删

问题:删除操作缺乏原子性。

场景:

index1执行删除时,查询到的lock值确实和uuid相等

uuid=v1

set(lock,uuid);

index1执行删除前,lock刚好过期时间已到,被redis自动释放,在redis中没有了lock,没有了锁。

index2获取了lock

index2线程获取到了cpu的资源,开始执行方法

uuid=v2

set(lock,uuid);

index1执行删除,此时会把index2的lock删除

index1 因为已经在方法中了,所以不需要重新上锁。index1有执行的权限。index1已经比较完成了,这个时候,开始执行

删除的index2的锁!

优化之LUA脚本保证删除的原子性

@GetMapping("testLockLua")
public void testLockLua() {
    //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
    String uuid = UUID.randomUUID().toString();
    //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
    String skuId = "25"; // 访问skuId 为25号的商品 100008348542
    String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
   // 3 获取锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);
    // 第一种: lock 与过期时间中间不写任何的代码。
    // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
    // 如果true
    if (lock) {
        // 执行的业务逻辑开始
        // 获取缓存中的num 数据
        Object value = redisTemplate.opsForValue().get("num");
        // 如果是空直接返回
        if (StringUtils.isEmpty(value)) {
            return;
        }
        // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
        int num = Integer.parseInt(value + "");
        // 使num 每次+1 放入缓存
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        /*使用lua脚本来锁*/
        // 定义lua 脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    } else {
        // 其他线程等待
        try {
            // 睡眠
            Thread.sleep(1000);
            // 睡醒了之后,调用方法。
            testLockLua();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Lua 脚本详解:

项目中正确使用

定义key,key应该是为每个sku定义的,也就是每个sku有一把锁。

String locKey ="lock:"+skuId; // 锁住的是每个商品的数据
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid,3,TimeUnit.SECONDS);

总结

加锁

使用lua释放锁

重试

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

- 互斥性。在任意时刻,只有一个客户端能持有锁。

- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

- 加锁和解锁必须具有原子性

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

相关文章

  • SpringBoot事件机制相关知识点汇总

    SpringBoot事件机制相关知识点汇总

    这篇文章主要介绍了SpringBoot事件机制相关知识点汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • java ThreadPoolExecutor线程池拒绝策略避坑

    java ThreadPoolExecutor线程池拒绝策略避坑

    这篇文章主要为大家介绍了java ThreadPoolExecutor拒绝策略避坑踩坑示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • java数组及arrays类对数组的操作实例

    java数组及arrays类对数组的操作实例

    下面小编就为大家带来一篇java数组及arrays类对数组的操作实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • Java中Getter和Setter方法及主要区别

    Java中Getter和Setter方法及主要区别

    这篇文章主要给大家介绍了关于Java中Getter和Setter方法及主要区别的相关资料,getter和setter方法是用于封装类中的私有属性的方法,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-04-04
  • Java文件上传下载、邮件收发实例代码

    Java文件上传下载、邮件收发实例代码

    这篇文章主要介绍了Java文件上传下载、邮件收发实例代码的相关资料,非常不错具有参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • Java实现五子棋单机版

    Java实现五子棋单机版

    这篇文章主要为大家详细介绍了Java实现五子棋单机版,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • SpringBoot中MyBatis使用自定义TypeHandler的实现

    SpringBoot中MyBatis使用自定义TypeHandler的实现

    本文主要介绍了SpringBoot中MyBatis使用自定义TypeHandler,当默认的类型映射不能满足需求时,自定义 TypeHandler 就非常有用,具有一定的参考价值,感兴趣的可以了解一下
    2024-08-08
  • idea 2024使用Maven创建Java Web项目详细图文教程

    idea 2024使用Maven创建Java Web项目详细图文教程

    这篇文章主要给大家介绍了关于idea 2024使用Maven创建Java Web项目的相关资料,介绍了如何使用Maven创建一个Spring MVC项目,并配置Tomcat服务器以运行一个简单的Helloworld JSP页面,需要的朋友可以参考下
    2024-12-12
  • Java基于接口实现模拟动物声音代码实例

    Java基于接口实现模拟动物声音代码实例

    这篇文章主要介绍了Java基于接口实现模拟动物声音代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • 微服务Spring Boot 整合Redis 阻塞队列实现异步秒杀下单思路详解

    微服务Spring Boot 整合Redis 阻塞队列实现异步秒杀下单思路详解

    这篇文章主要介绍了微服务Spring Boot 整合Redis 阻塞队列实现异步秒杀下单,使用阻塞队列实现秒杀的优化,采用异步秒杀完成下单的优化,本文给大家分享详细步骤及实现思路,需要的朋友可以参考下
    2022-10-10

最新评论