Redis(十七)分布式锁

文章目录

  • 面试题
  • 分布式锁
    • 锁的种类
    • 分布式锁需要具备的条件和刚需
    • 分布式锁
  • 案例
    • nginx分布式微服务部署,单机锁问题
    • 分布式锁注意事项
    • lock/unlock+lua脚本自研版的redis分布式锁搞定
      • lua脚本
    • 可重入锁
      • 可重入锁种类
      • 可重入锁hset实现,对比setnx(重要)
      • 分布式锁需要具备的条件和刚需
      • lua脚本
    • 工厂模式分布式锁
    • 自动续期
      • CAP再提起
    • 总结
      • 引入分布式锁

面试题

基于Redis的什么用法?

  1. 数据共享,分布式Session
  2. 分布式锁
  3. 全局ID
  4. 计算器、点赞
  5. 位统计
  6. 购物车
  7. 轻量级消息队列
  8. 抽奖
  9. 回来的题目
  10. 点赞、签到、打卡
  11. 差集交集并集,用户关注、可能认识的人,推荐模型
  12. 热点新闻、热搜排行榜
  • Redis 做分布式锁的时候有需要注意的问题?
  • 你们公司自己实现的分布式锁是否用的setnx命令实现?

不可以

  • 这个是最合适的吗?你如何考虑分布式锁的可重入问题?
  • 如果是 Redis 是单点部署的,会带来什么问题?那你准备怎么解决单点问题呢?
  • Redis集群模式下,比如主从模式,CAP方面有没有什么问题呢?

CAP:

C:一致性:在分布式系统中的任意一个节点都会查询到相同的信息(拿到的都是最新的)
A:可用性:服务一直可用,而且是正常响应时间,好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。(只要我访问你就给我返回,如果要满足分布式(P),机器之间网络断掉的话,直接和C冲突)
P:分区容错性:当分布式系统中一部分节点崩溃的时候,当前系统仍旧能够正常对外提供服务(多台机器,分布式,不满足P就是单机么)

Redis集群:是AP,Redis单机是C,一致性
区别Zookeeper集群:是CP,全部节点收到后返回ack

  • 那你简单的介绍-下 Redlock吧?你简历上写redisson,你谈谈
  • Redis分布式锁如何续期?看门狗知道吗?

分布式锁

JUC中AQS锁的规范落地参考+可重入锁考虑+Lua脚本+Redis命令实现分布式锁

锁的种类

  1. 单机版同一个M虚拟机内,synchronized或者Lock接口
  2. 分布式多个不同M虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了。

分布式锁需要具备的条件和刚需

  1. 独占性:OnlyOne,任何时刻只能有且仅有一个线程持有
  2. 高可用:若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况;高并发请求下,依旧高性能
  3. 防死锁:杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案
  4. 不乱抢:防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放,自己约的锁含着泪也要自己解
  5. 重入性:同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。

分布式锁

在这里插入图片描述

案例

nginx分布式微服务部署,单机锁问题

分布式部署后,单机锁还是出现超卖现象,需要分布式锁

分布式锁注意事项

  1. 重试:递归重试,容易导致stackoverflowerror
  2. 宕机-防止死锁:部署了微服务的Java程序机器挂了,代码层面根本没有走到finally这块
  3. 防止误删key:stringRedisTemplate.delete(key);只能自己删除自己的锁,不可以删除别人的,需要添加判断
  4. Lua保证原子性:存在问题就是最后的判断+del不是一行原子命令操作,需要用lua脚本进行修改
  5. 可重入锁+设计模式:不满足可重入性

lock/unlock+lua脚本自研版的redis分布式锁搞定

lua脚本

https://redis.io/docs/reference/patterns/distributed-locks/

在这里插入图片描述
使用示例
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可重入锁

可重入锁(递归锁):可以再次进入的同步锁
进入:进入同步域(即同步代码块/方法或显式锁定的代码)
一句话:一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入

可重入锁种类

  1. 隐式锁(即synchronized关键字使用的锁)默认是可重入锁
//同步块
public class ReEntryLockDemo
{public static void main(String[] args){final Object objectLockA = new Object();new Thread(() -> {synchronized (objectLockA){System.out.println("-----外层调用");synchronized (objectLockA){System.out.println("-----中层调用");synchronized (objectLockA){System.out.println("-----内层调用");}}}},"a").start();}
}
  1. Synchronized的重入的实现机理
//同步方法
public class ReEntryLockDemo
{public synchronized void m1(){System.out.println("-----m1");m2();}public synchronized void m2(){System.out.println("-----m2");m3();}public synchronized void m3(){System.out.println("-----m3");}public static void main(String[] args){ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();reEntryLockDemo.m1();}
}
  1. 显式锁(即Lock)也有ReentrantLock这样的可重入锁。
//显式锁
public class ReEntryLockDemo
{static Lock lock = new ReentrantLock();public static void main(String[] args){new Thread(() -> {lock.lock();try{System.out.println("----外层调用lock");lock.lock();try{System.out.println("----内层调用lock");}finally {// 这里故意注释,实现加锁次数和释放次数不一样// 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。lock.unlock(); // 正常情况,加锁几次就要解锁几次}}finally {lock.unlock();}},"a").start();new Thread(() -> {lock.lock();try{System.out.println("b thread----外层调用lock");}finally {lock.unlock();}},"b").start();}
}

切记,一般而言,你lock了几次就要unlock几次

public class ReEntryLockDemo
{Lock lock = new ReentrantLock();public void entry(){new Thread(() -> {lock.lock();try{System.out.println(Thread.currentThread().getName()+"\t"+"外层调用lock");lock.lock();try{System.out.println(Thread.currentThread().getName()+"\t"+"内层调用lock");}finally {//这里不解锁,已经加了两次锁//lock.unlock();}}finally {lock.unlock();}},"t1").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {lock.lock();try{System.out.println(Thread.currentThread().getName()+"\t"+"外层调用lock");}finally {lock.unlock();}},"t2").start();}public static void main(String[] args){ReEntryLockDemo demo = new ReEntryLockDemo();demo.entry();//在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的}
}

可重入锁hset实现,对比setnx(重要)

可重入锁模拟redis
hset redis锁名字(zzyyRedisLock) 某个请求线程的UUID+ThreadID 加锁的次数
在这里插入图片描述
setnx:只能解决有无的问题,但是不完美
hset:不但解决有无,还解决可重入问题

分布式锁需要具备的条件和刚需

  1. 独占性
  2. 高可用
  3. 防死锁
  4. 不乱抢

lua脚本

|-先判断redis分布式锁这个key是否存在EXISTS key|-key不存在:返回零说明不存在,hset新建当前线程属于自己的锁BY UUID:ThreadlD |-key存在:返回壹说明已经有锁,需进一步判断是不是当前线程自己的:HEXISTS key uuid:ThreadlD|-返回0说明不是自己的|-返回非0说明是自己的:自增1表示重入
  1. 显示参数版本
if redis.call('exists','key') == 0 or redis.call('hexists','key','uuid:threadid') == 1 thenredis.call('hincrby','key','uuid:threadid',1)redis.call('expire','key',30)return 1
elsereturn 0
end
  1. 参数替换版本
名称替换位置示例值
keyKEYS[1]testRedisLock
valueARGV[1]2f586ae740a94736894ab9d51880ed9d:1
过期时间值ARGV[2]30 秒
if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 
elsereturn 0
end

工厂模式分布式锁

封装锁,使用工厂模式

@Component
public class RedisDistributedLock implements Lock
{private StringRedisTemplate stringRedisTemplate;private String lockName;//KEYS[1]private String uuidValue;//ARGV[1]private long   expireTime;//ARGV[2]//注意这里public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid){this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue = uuid+":"+Thread.currentThread().getId();this.expireTime = 30L;}@Overridepublic void lock(){tryLock();}@Overridepublic boolean tryLock(){try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException{if(time == -1L){String script ="if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then    " +"redis.call('hincrby',KEYS[1],ARGV[1],1)    " +"redis.call('expire',KEYS[1],ARGV[2])    " +"return 1  " +"else   " +"return 0 " +"end";System.out.println("lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);while(!stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName), uuidValue,String.valueOf(expireTime))){//暂停60毫秒try { TimeUnit.MILLISECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); }}//新建一个后台扫描程序,来坚持key目前的ttl,是否到我们规定的1/2 1/3来实现续期renewExpire();return true;}return false;}@Overridepublic void unlock(){System.out.println("unlock(): lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);String script ="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then    " +"return nil  " +"elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then    " +"return redis.call('del',KEYS[1])  " +"else    " +"return 0 " +"end";// nil = false 1 = true 0 = falseLong flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));if(null == flag){throw new RuntimeException("this lock doesn't exists,o(╥﹏╥)o");}}private void renewExpire(){String script ="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then     " +"return redis.call('expire',KEYS[1],ARGV[2]) " +"else     " +"return 0 " +"end";new Timer().schedule(new TimerTask(){@Overridepublic void run(){if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))){renewExpire();}}},(this.expireTime * 1000)/3);}//暂时用不到@Overridepublic void lockInterruptibly() throws InterruptedException{}@Overridepublic Condition newCondition(){return null;}
}

分布式锁工厂

@Component
public class DistributedLockFactory
{@Autowiredprivate StringRedisTemplate stringRedisTemplate;private String lockName;private String uuid;public DistributedLockFactory(){//uuid会变化,所以在类创建的时候就uuid放入内部,否则影响可重入性this.uuid = IdUtil.simpleUUID();}public Lock getDistributedLock(String lockType){if(lockType == null) {return null;}if(lockType.equalsIgnoreCase("REDIS")){this.lockName = "zzyyRedisLock";return new RedisDistributedLock(stringRedisTemplate,lockName,uuid);}else if(lockType.equalsIgnoreCase("ZOOKEEPER")){this.lockName = "zzyyZookeeperLockNode";//TODO zookeeper版本的分布式锁return null;}else if(lockType.equalsIgnoreCase("MYSQL")){//TODO MYSQL版本的分布式锁return null;}return null;}
}

使用工厂锁

public String sale7(){String retMessage = "";Lock redisLock = distributedLockFactory.getDistributedLock("redis");redisLock.lock();try{//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存,每次减少一个if(inventoryNumber > 0){stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;System.out.println(retMessage+"\t"+"服务端口号"+port);testReEntry();}else{retMessage = "商品卖完了,o(╥﹏╥)o";}}finally {redisLock.unlock();}return retMessage+"\t"+"服务端口号"+port;}

自动续期

CAP再提起

CAP即:
1、Consistency(一致性):对于客户端的每次读操作,要么读到的是最新的数据,要么读取失败。换句话说,一致性是站在分布式系统的角度,对访问本系统的客户端的一种承诺:要么我给您返回一个错误,要么我给你返回绝对一致的最新数据,不难看出,其强调的是数据正确。
2、Availability(可用性):任何客户端的请求都能得到响应数据,不会出现响应错误。换句话说,可用性是站在分布式系统的角度,对访问本系统的客户的另一种承诺:我一定会给您返回数据,不会给你返回错误,但不保证数据最新,强调的是不出错。
3、Partition tolerance(分区容忍性):由于分布式系统通过网络进行通信,网络是不可靠的。当任意数量的消息丢失或延迟到达时,系统仍会继续提供服务,不会挂掉。换句话说,分区容忍性是站在分布式系统的角度,对访问本系统的客户端的再一种承诺:我会一直运行,不管我的内部出现何种数据同步问题,强调的是不挂掉。

Redis集群是AP
Zookeeper集群是CP
Eureka集群是AP
Nacos集群是AP

总结

nginx微服务单机锁出现问题:只能锁本服务

引入分布式锁

  1. 只加了锁,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面fnallv释放锁
  2. 宕机了,部署了微服务代码层面根本没有走到fnally这块,没办法保证解锁,这个key没有被删除,需要有lockKey的过期时间设定
  3. redis的分布式锁key,增加过期时间此外,还必须要setnx+过期时间必须同一行
    1. 必须规定只能自己删除自己的锁,不能把别人的锁删除了unlock变为Lua脚本保证
    2. 锁重入,hset替代setnx+lock变为Lua脚本保证
    3. 自动续期

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/726206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

16-Java命令模式 ( Command Pattern )

Java命令模式 摘要实现范例 命令模式&#xff08;Command Pattern&#xff09;中请求以命令的形式包裹在对象中&#xff0c;并传给调用对象 调用对象寻找可以处理该命令的合适的对象&#xff0c;并把该命令传给相应的对象&#xff0c;该对象执行命令 命令模式是行为型模式&…

Clion调试QT程序qDebug()、cout控制台无输出的可能解决方法

qDebug()不输出 在当前项目配置中添加一个环境变量 方法一、单独为配置 QT_ASSUME_STDERR_HAS_CONSOLE1 方法二、全局配置&#xff08;系统变量&#xff09; 一劳永逸 效果 cout不输出 Clion在debug调试C/C的时候&#xff0c;printf/cout不会实时输出情况 结果同上~ 谢阅…

SDM450核心板_高通SDM450安卓核心板模块性能参数

高通SDM450核心板是基于SDM450移动平台开发的一款高性能核心板。采用领先的14纳米技术&#xff0c;该核心板为高端智能设备提供了卓越的性能和优质的体验。板载2GB16GB的内存(可选配4GB32GB)&#xff0c;双 ISP(图像传感器处理器)支持丰富的照片细节和双摄像头体验&#xff0c;…

借助 Terraform 功能协调部署 CI/CD 流水线-Part 1

在当今快节奏的开发环境中&#xff0c;实现无缝、稳健的 CI/CD 流水线对于交付高质量软件至关重要。在本文中&#xff0c;我们将向您介绍使用 Bitbucket Pipeline、ArgoCD GitOps 和 AWS EKS 设置部署的步骤&#xff0c;所有步骤都将利用 Terraform 的强大功能进行编排。在Part…

01_Maven

文章目录 Maven安装MavenMaven的工作流程配置MavenMaven的使用module和project的关系如何用Maven导包 如何用Maven进行项目构建指令介绍clean指令compile指令package指令install指令 Maven的依赖管理如何导包scope作用域依赖传递依赖冲突 使用Maven开发项目Junit如何使用Junit …

力扣刷题Day11--21. 合并两个有序链表(js)

目录 1&#xff0c;题目 2&#xff0c;代码 2.1迭代思想 2.2递归思想 3&#xff0c;学习与总结 3.1js中的链表类 3.2递归思想 3.3提醒自己 1&#xff0c;题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 2&am…

YOLOv9独家原创改进|加入RT-DETR中的HGBlock!

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、改进点介绍 HGBlock是RT-DETR中使用的特征提取模块。 二、HGBlock模块详解 2.1 模块简介 HGBlock的主要思想&#xff1a; 一个并联的卷积模块与…

java上传本地文件到服务器共享

在Windows系统中,将本地文件夹中的某个文件上传到另一台Windows服务器电脑上,前提:两台电脑网络互通,要接收文件的Windows服务器文件夹开启了共享,可以被本机用如下方式进行写入和读取: 如何配置服务器共享请自行百度查找。 所需要的maven依赖如下: <dependency>…

AI辅助研发的崭新前景:技术进展、应用案例与挑战机遇

目录 前言1. 技术进展&#xff1a;深度学习、强化学习与生成模型的崭新应用1.1 深度学习的崭新应用1.2 强化学习的优化应用1.3 生成模型在创意设计中的应用 2. 行业应用案例&#xff1a;医药、汽车、电子等领域的AI助力2.1 医药领域的AI辅助研发2.2 汽车设计中的AI助力2.3 电子…

Qwen-Agent自定义Tool

qwen-agent项目部署 1、下载qwen-agent https://github.com/QwenLM/Qwen-Agent2、安装依赖环境 pip3 install -r requirements.txt自定义Tool cd qwen_agent/tools参考其他的工具&#xff0c;我这里创建了一个查询手机号归属地的工具get_mobile_address.py&#xff1a; im…

猜猜:哪句古诗与古代女子妆容有关?2024.3.8蚂蚁庄园今日答案:金盆水里拨红泥

蚂蚁庄园是一款爱心公益游戏&#xff0c;用户可以通过喂养小鸡&#xff0c;产生鸡蛋&#xff0c;并通过捐赠鸡蛋参与公益项目。用户每日完成答题就可以领取鸡饲料&#xff0c;使用鸡饲料喂鸡之后&#xff0c;会可以获得鸡蛋&#xff0c;可以通过鸡蛋来进行爱心捐赠。其中&#…

Docker部署ruoyi前后端分离项目

目录 一. 介绍前后端项目 二. 搭建局域网 2.1 创建网络 2.2 注意点 三. Redis 3.1 安装 3.2 配置redis.conf文件 3.3 测试 四. 安装MySQL 4.1 安装 4.2 配置my2.cnf文件 4.3 充许远程连接 五. 若依部署后端服务 5.1 数据导入 5.2 使用Dockerfile自定义镜像 5.3 运行…

Elasticsearch:从 ES|QL 到 Python 数据帧

在我之前的文章 “Elasticsearch&#xff1a;ES|QL 查询展示”&#xff0c;我展示了如何在 Kibana 中使用 ES|QL 对索引来进行查询及统计。在很多的情况下&#xff0c;我们需要在客户端中来对数据进行查询&#xff0c;那么我们该怎么办呢&#xff1f;我们需要使用到 Elasticsea…

能源大数据采集,为您提供专业数据采集服务

随着经济的不断发展&#xff0c;能源产业也逐渐成为国民经济的支柱产业之一。而对于能源行业来说&#xff0c;数据采集是一项至关重要的工作。以往&#xff0c;能源企业采集数据主要依靠人工收集、整理&#xff0c;但是这种方式不仅效率低下&#xff0c;而且容易出现数据不准确…

ai智能写作软件推荐,ai一键生成作文

很多小伙伴们都觉得写作是一件让人头痛的事情。因为不仅要让自己的文字流畅有条理&#xff0c;还需要通过一些修辞手法来使文章更加生动有趣。市场上不断涌现出各种各样的AI人工智能原创文章写作平台&#xff0c;哪些才好用&#xff0c;才是适合自己的呢&#xff1f; 爱制作ai …

如何在“Ubuntu 服务器上使用MariaDB配置Galera集群”?

一、 安装好三个MariaDB数据库 如何使用“Ubuntu 20.04桌面版&#xff0c;安装MariaDB数据库“&#xff1f;win10系统&#xff1f;-CSDN博客 二、第一个node1&#xff0c;修改 sudo nano /etc/mysql/conf.d/galera.cnf [mysqld] binlog_formatROW default-storage-enginei…

功能安全概念梳理二

什么是SEooC&#xff1f;SEooC和element有什么不一样&#xff1f; 参考链接&#xff1a;车规级 | ISO26262中对独立安全要素&#xff08;SEooC&#xff09;的开发要求 汽车功能安全(ISO 26262)系列: 到底什么是SEooC开发 安全措施(Safety measure)和安全机制(Safety mechanis…

【Leetcoode】2917. 找出数组中的 K-or 值

文章目录 题目思路代码结果 题目 题目链接 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 nums 中的 K-or 是一个满足以下条件的非负整数&#xff1a; 只有在 nums 中&#xff0c;至少存在 k 个元素的第 i 位值为 1 &#xff0c;那么 K-or 中的第 i 位的值才是 1 。…

安卓手机投屏到win10系统电脑,在电脑上可操作手机

使用scrcpy工具实现 scrcpy 就是通过 adb 调试的方式来将手机屏幕投到电脑上&#xff0c;并可以通过电脑控制您的 Android 设备。它可以通过 USB 连接&#xff0c;也可以通过 Wifi 连接&#xff08;类似于隔空投屏&#xff09;&#xff0c;而且不需要任何 root 权限&#xff0…

openGauss基于存储复制的资源池化安装部署流程

第一步&#xff1a;在主存储上创建资源池化需要的lun&#xff0c;以及远程同步复制xlog卷对应的lun&#xff0c;并且所有lun全部映射到业务计算节点上 1. 登录主集群DeviceManager&#xff0c;选择服务->LUN组->创建 来创建主集群LUN组&#xff1b; 2.登录主集群Device…