基于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限流的实际应用
Apr 24 Redis
详解Redis实现限流的三种方式
Apr 27 Redis
redis内存空间效率问题的深入探究
May 17 Redis
Redis可视化客户端小结
Jun 10 Redis
redis实现的四种常见限流策略
Jun 18 Redis
Redis源码阅读:Redis字符串SDS详解
Jul 15 Redis
解析redis hash应用场景和常用命令
Aug 04 Redis
Redis如何实现分布式锁
Aug 23 Redis
使用redis实现延迟通知功能(Redis过期键通知)
Sep 04 Redis
Jedis操作Redis实现模拟验证码发送功能
Sep 25 Redis
Redis之RedisTemplate配置方式(序列和反序列化)
Mar 13 Redis
Redis全局ID生成器的实现
Jun 05 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网页游戏学习之Xnova(ogame)源码解读(二)
2014/06/23 PHP
thinkphp多层MVC用法分析
2015/12/30 PHP
PHP7新功能总结
2019/04/14 PHP
一个简单的弹性返回顶部JS代码实现介绍
2013/06/09 Javascript
JS实现div居中示例
2014/04/17 Javascript
Javascript获取表单名称(name)的方法
2015/04/02 Javascript
jquery ui resize 中border-box的bug修正
2015/04/26 Javascript
用户代理字符串userAgent可实现的四个识别
2015/09/20 Javascript
AngularJS基础 ng-cut 指令介绍及简单示例
2016/08/01 Javascript
AngularJS封装指令方法详解
2016/12/12 Javascript
js实现将json数组显示前台table中
2017/01/10 Javascript
js 单引号替换成双引号,双引号替换成单引号的实现方法
2017/02/16 Javascript
使用get方式提交表单在地址栏里面不显示提交信息
2017/02/21 Javascript
jquery实现超简单的瀑布流布局【推荐】
2017/03/08 Javascript
详解vue.js移动端导航navigationbar的封装
2017/07/05 Javascript
react+ant design实现Table的增、删、改的示例代码
2018/12/27 Javascript
jquery获取file表单选择文件的路径、名字、大小、类型
2019/01/18 jQuery
JS隐藏号码中间4位代码实例
2019/04/09 Javascript
Element实现表格分页数据选择+全选所有完善批量操作
2019/06/07 Javascript
[02:43]DOTA2英雄基础教程 半人马战行者
2014/01/13 DOTA
[01:32]TI奖金增速竟因它再创新高!DOTA2勇士令状不朽珍藏Ⅰ饰品欣赏
2018/05/18 DOTA
python正则匹配抓取豆瓣电影链接和评论代码分享
2013/12/27 Python
python使用paramiko实现远程拷贝文件的方法
2016/04/18 Python
Python实现基本数据结构中队列的操作方法示例
2017/12/04 Python
Python设计模式之代理模式简单示例
2018/01/09 Python
Python enumerate索引迭代代码解析
2018/01/19 Python
python绘制雪景图
2019/12/16 Python
CSS3 滤镜 webkit-filter详细介绍及使用方法
2012/12/27 HTML / CSS
20岁生日感言
2014/01/13 职场文书
护士个人自我鉴定
2014/03/24 职场文书
五月的鲜花活动方案
2014/08/21 职场文书
2014年政教处工作总结
2014/12/20 职场文书
幼儿园教师节感谢信
2015/01/23 职场文书
答谢酒会主持词
2015/07/02 职场文书
2015年治庸问责工作总结
2015/07/27 职场文书
参加招聘会后的感想
2015/08/10 职场文书