• 基于Redission的分布式锁实战【代码案例详解】


    【辰兮要努力】:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行!

    博客来源于项目以及编程中遇到的问题总结,偶尔会有读书分享,我会陆续更新Java前端、后台、数据库、项目案例等相关知识点总结,感谢你的阅读和关注,希望我的博客能帮助到更多的人,分享获取新知,大家一起进步!

    吾等采石之人,应怀大教堂之心,愿我们奔赴在各自的热爱里…

    一、为什么需要分布式锁

    在系统中,当存在多个进程和线程可以改变某个共享数据时,就容易出现并发问题导致共享数据的不一致性。

    在这里插入图片描述

    单体系统:如果多个线程要访问共享资源的时候,我们通常线程间加锁的机制,在某一个时刻,只有一个线程可以对这个资源进行操作,其他线程需要等待锁的释放,Java中也有一些处理锁的机制,比如synchronized。

    分布式系统:当某个资源可以被多个系统访问使用到的时候,为了保证大家访问这个数据是一致性的,那么就要求再同一个时刻,只能被一个系统使用,这时候线程之间的锁机制就无法起到作用了,因为分布式环境中,系统是会部署到不同的机器上面的,那么就需要【分布式锁】了。

    在这里插入图片描述

    解决共享资源操作可能引发的数据问题


    二、Redission入门

    2.1 Redission执行流程

    Redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行

    在这里插入图片描述
    Redisson设置一个key的默认过期时间为30s,如果某个客户端持有一个锁超过了30s怎么办?


    2.2 Watch Dog 机制

    Redisson中有一个watchdog看门狗的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔10秒帮你把key的超时时间设为30s(默认配置)

    这样的话,就算一直持有锁也不会出现key过期了,其他线程获取到锁的问题了。

    Redisson的"看门狗"逻辑保证了没有死锁发生。

    备注:如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了,其他线程可以获取到锁

    在这里插入图片描述


    2.3 对比setnx

    1、加锁:使用setnx进行加锁,当该指令返回1时,说明成功获得锁

    2、解锁:当得到锁的线程执行完任务之后,使用del命令释放锁,以便其他线程可以继续执行setnx命令来获得锁

    1)存在的问题:假设线程获取了锁之后,在执行任务的过程中挂掉,来不及显示地执行del命令释放锁,
    那么竞争该锁的线程都会执行不了,产生死锁的情况。
    (2)解决方案:设置锁超时时间
    
    • 1
    • 2
    • 3

    3、设置锁超时时间:setnx 的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。可以使用expire命令设置锁超时时间

    1)存在问题:setnx 和 expire 不是原子性的操作,假设某个线程执行setnx 命令,成功获得了锁,
    但是还没来得及执行expire 命令,服务器就挂掉了,这样一来,这把锁就没有设置过期时间了,变成了死锁,别的线程再也没有办法获得锁了。
    (2)解决方案:redis的set命令支持在获取锁的同时设置key的过期时
    
    • 1
    • 2
    • 3

    4、使用set命令加锁并设置锁过期时间:

    1)存在问题:假如线程A成功得到了锁,并且设置的超时时间是 30 秒。
    如果某些原因导致线程 A 执行的很慢,过了 30 秒都没执行完,这时候锁过期自动释放,线程 B 得到了锁。
    
    (2)解决方案:可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁。
    在加锁的时候把当前的线程 ID 当做value,并在删除之前验证 key 对应的 value 是不是自己线程的 ID。
    但是,这样做其实隐含了一个新的问题,get操作、判断和释放锁是两个独立操作,不是原子性。对于非原子性的问题,我们可以使用Lua脚本来确保操作的原子性
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    如上总结下来,如果使用传统的Redission的底层封装相关的代码帮助我们解决了一系列此问题

    原子性 原子性 原子性


    三、代码案例

    分享一下Redission的代码使用案例:超简单

    引入pom.xml依赖

           <dependency>
    			<groupId>org.redisson</groupId>
    			<artifactId>redisson</artifactId>
    			<version>3.6.5</version>
    	</dependency>	
    
    • 1
    • 2
    • 3
    • 4
    • 5

    模拟代码

    @RestController
    public class IndexController {
    
        @Autowired
        private Redisson redisson;
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @RequestMapping("/deduct_stock")
        public String deductStock() {
            String lockKey = "product_101";
            RLock redissonLock = redisson.getLock(lockKey);
            try {
                //执行锁
                redissonLock.lock();  //setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
                if (stock > 0) {
                    int realStock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                    System.out.println("扣减成功,剩余库存:" + realStock);
                } else {
                    System.out.println("扣减失败,库存不足");
                }
            } finally {
                //释放锁
                redissonLock.unlock();
     
            }
    
            return "end";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    Redis在命令队列层面还是单线程的, Redis在IO层面是做了多线程的优化

    从上面的实现机制可以看出,Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。

    本期的分享到这里了,最近会陆续分享项目中遇到的问题以及解决方案,我们下期见!


    📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤️ 分享👥 留言💬thanks!!!

    📚愿我们奔赴在各自的热爱里!

  • 相关阅读:
    C++模拟OpenGL库——图形光栅化理论及实现(二):Brensenham直线算法
    Redis之cluster集群
    CCF推荐国际会议接收率(持续更新)
    Spring基础知识总结(纯文字版)
    【题解】同济线代习题一.8.3
    敏捷管理工具和scrum敏捷管理方法介绍
    实验四+ R型指令设计实验【计算机组成原理】
    MAUI 框架安卓入门开发04 内容模板加载
    前端如何对cookie加密
    Android用View实现球形旋转滚动效果(中秋篇)
  • 原文地址:https://blog.csdn.net/weixin_45393094/article/details/126236861