基于Redission的分布式锁实战


Posted in Redis onAugust 14, 2022

一、为什么需要分布式锁

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

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

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

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

二、Redission的实战使用

2.1 Redission执行流程

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

基于Redission的分布式锁实战

Redisson设置一个key的默认过期时间为30s,如果某个客户端持有一个锁超过了30s怎么办?

2.2 Watch Dog 机制

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

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

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

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

基于Redission的分布式锁实战

2.3 对比setnx

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

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

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

(2)解决方案:设置锁超时时间

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

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

(2)解决方案:redis的set命令支持在获取锁的同时设置key的过期时

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

(1)存在问题:假如线程A成功得到了锁,并且设置的超时时间是 30 秒。 如果某些原因导致线程 A 执行的很慢,过了 30 秒都没执行完,这时候锁过期自动释放,线程 B 得到了锁。

(2)解决方案:可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁。 在加锁的时候把当前的线程 ID 当做value,并在删除之前验证 key 对应的 value 是不是自己线程的 ID。 但是,这样做其实隐含了一个新的问题,get操作、判断和释放锁是两个独立操作,不是原子性。对于非原子性的问题,我们可以使用Lua脚本来确保操作的原子性

………………

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

原子性 原子性 原子性

三、代码案例

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

引入pom.xml依赖

<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.6.5</version>
		</dependency>

模拟代码

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

}

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

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

到此这篇关于基于Redission的分布式锁实战的文章就介绍到这了,更多相关Redission 分布式锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
解决redis sentinel 频繁主备切换的问题
Apr 12 Redis
redis三种高可用方式部署的实现
May 11 Redis
详解redis分布式锁的这些坑
May 19 Redis
Redis可视化客户端小结
Jun 10 Redis
浅谈Redis中的RDB快照
Jun 29 Redis
嵌入式Redis服务器在Spring Boot测试中的使用教程
Jul 21 Redis
redis击穿 雪崩 穿透超详细解决方案梳理
Mar 17 Redis
Redis超详细讲解高可用主从复制基础与哨兵模式方案
Apr 07 Redis
redis 解决库存并发问题实现数量控制
Apr 08 Redis
Redis 报错 error:NOAUTH Authentication required
May 15 Redis
Redis入门基础常用操作命令整理
Jun 01 Redis
基于redis+lua进行限流的方法
Jul 23 Redis
基于redis+lua进行限流的方法
Jul 23 #Redis
Redis过期数据是否会被立马删除
Jul 23 #Redis
如何使用注解方式实现 Redis 分布式锁
Jul 23 #Redis
redis lua限流算法实现示例
Redis Lua脚本实现ip限流示例
Jul 15 #Redis
redis protocol通信协议及使用详解
Jul 15 #Redis
Redis sentinel哨兵集群的实现步骤
Jul 15 #Redis
You might like
PHP 数组入门教程小结
2009/05/20 PHP
PHP扩展开发教程(总结)
2015/11/04 PHP
基于PHP给大家讲解防刷票的一些技巧
2015/11/18 PHP
关于php中的json_encode()和json_decode()函数的一些说明
2016/11/20 PHP
PHP自动生成缩略图函数的源码示例
2019/03/18 PHP
使用Jquery搭建最佳用户体验的登录页面之记住密码自动登录功能(含后台代码)
2011/07/10 Javascript
JavaScript中的变量声明早于赋值分析
2012/03/01 Javascript
你必须知道的Javascript知识点之&quot;深入理解作用域链&quot;的介绍
2013/04/23 Javascript
JS实现定时页面弹出类似QQ新闻的提示框
2013/11/07 Javascript
jquery遍历之parent()和parents()的区别及parentsUntil()方法详解
2013/12/02 Javascript
JS通过分析userAgent属性来判断浏览器的类型及版本
2014/03/28 Javascript
浅谈jQuery中 wrap() wrapAll() 与 wrapInner()的差异
2014/11/12 Javascript
比例尺、缩略图、平移缩放之百度地图添加控件方法
2015/08/03 Javascript
JavaScript中的return语句简单介绍
2015/12/07 Javascript
JS中递归函数
2016/06/17 Javascript
html+js实现简单的计算器代码(加减乘除)
2016/07/12 Javascript
nodejs进阶(6)—连接MySQL数据库示例
2017/01/07 NodeJs
基于js中this和event 的区别(详解)
2017/10/24 Javascript
Vue组件的使用教程详解
2018/01/05 Javascript
Vue微信项目按需授权登录策略实践思路详解
2018/05/07 Javascript
对VUE中的对象添加属性
2018/09/18 Javascript
js中实例与对象的区别讲解
2019/01/21 Javascript
[59:53]DOTA2-DPC中国联赛 正赛 VG vs Elephant BO3 第二场 3月6日
2021/03/11 DOTA
Python实现检测服务器是否可以ping通的2种方法
2015/01/01 Python
Python利用ansible分发处理任务
2015/08/04 Python
python修改字典键(key)的方法
2019/08/05 Python
python随机模块random使用方法详解
2020/02/14 Python
python 安装教程之Pycharm安装及配置字体主题,换行,自动更新
2020/03/13 Python
Python word文本自动化操作实现方法解析
2020/11/05 Python
Python实现异步IO的示例
2020/11/05 Python
CSS3圆角和渐变2种常用功能详解
2016/01/06 HTML / CSS
策划助理岗位职责
2013/11/18 职场文书
幼儿园义卖活动方案
2014/01/17 职场文书
幸福来敲门观后感
2015/06/04 职场文书
女性励志书籍推荐
2019/08/19 职场文书
Redis超详细讲解高可用主从复制基础与哨兵模式方案
2022/04/07 Redis