基于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的几个过期策略
May 27 Redis
解析Redis Cluster原理
Jun 21 Redis
redis客户端实现高可用读写分离的方式详解
Jul 04 Redis
Window server中安装Redis的超详细教程
Nov 17 Redis
redis的list数据类型相关命令介绍及使用
Jan 18 Redis
聊聊redis-dump工具安装问题
Jan 18 Redis
Redis+Lua脚本实现计数器接口防刷功能(升级版)
Feb 12 Redis
Redis调用Lua脚本及使用场景快速掌握
Mar 16 Redis
redis击穿 雪崩 穿透超详细解决方案梳理
Mar 17 Redis
redis 解决库存并发问题实现数量控制
Apr 08 Redis
Redis实现订单过期删除的方法步骤
Jun 05 Redis
使用Redis实现分布式锁的方法
Jun 16 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修改Linux或Unix口令的方法分享
2012/01/30 PHP
实用的简单PHP分页集合包括使用方法
2013/10/21 PHP
PHP中ini_set与ini_get用法实例
2014/11/04 PHP
php循环table实现一行两列显示的方法
2015/06/04 PHP
php设计模式之职责链模式实例分析【星际争霸游戏案例】
2020/03/27 PHP
PHP的imageTtfText()函数深入详解
2021/03/03 PHP
jQuery中调用WebService方法小结
2011/03/28 Javascript
JS 修改URL参数(实现代码)
2013/07/08 Javascript
使用JavaScript实现Java的List功能(实例讲解)
2013/11/07 Javascript
当某个文本框成为焦点时即清除文本框内容
2014/04/28 Javascript
javascript常用的正则表达式实例
2014/05/15 Javascript
js操作模态窗口及父子窗口间相互传值示例
2014/06/09 Javascript
AngularJS ng-mousedown 指令
2016/08/02 Javascript
关于Angular2 + node接口调试的解决方案
2017/05/28 Javascript
Vue.js实现可配置的登录表单代码详解
2018/03/29 Javascript
Angular ElementRef简介及其使用
2018/10/01 Javascript
初学node.js中实现删除用户路由
2019/05/27 Javascript
解决Vue中 父子传值 数据丢失问题
2019/08/27 Javascript
element-plus一个vue3.xUI框架(element-ui的3.x 版初体验)
2020/12/02 Vue.js
Vue-router编程式导航的两种实现代码
2021/03/04 Vue.js
从零学python系列之教你如何根据图片生成字符画
2014/05/23 Python
Python 实现Windows开机运行某软件的方法
2018/10/14 Python
python分布式爬虫中消息队列知识点详解
2020/11/26 Python
纯CSS3实现8组超炫酷鼠标滑过图片动画
2016/03/16 HTML / CSS
凯普林包包西班牙官网:Kipling西班牙
2019/04/12 全球购物
CK加拿大官网:Calvin Klein加拿大
2020/03/14 全球购物
介绍一下Linux文件的记录形式
2012/04/18 面试题
最新销售员个人自荐信
2013/09/21 职场文书
国贸专业个人求职信分享
2013/12/04 职场文书
营销总经理岗位职责范本
2014/09/02 职场文书
普通党员个人剖析材料
2014/10/08 职场文书
2015纪念九一八事变84周年演讲稿
2015/03/19 职场文书
给老婆的保证书怎么写
2015/05/08 职场文书
大学生入党自我鉴定范文
2019/06/21 职场文书
React Native项目框架搭建的一些心得体会
2021/05/28 Javascript
ubuntu下常用apt命令介绍
2022/06/05 Servers