分布式锁,学习笔记

  1. 什么是分布式锁
    1.1 作用:
    保证数据的正确性: 比如:秒杀的时候防止商品超卖,接口幂等性。
    避免重复处理数据: 比如:1避免调度任务在多台机器重复执行,2避免缓存过期所有请求都去加载数据库。
    一个分布式锁需要考虑的问题:
    1互斥阻塞。2锁需要可重入。3过期时间 4锁续期
    1.2 redission的实现原理是什么?
    1.2.1 如何解决这四个问题呢?
    redission如何解决互斥 : redis内部使用key冲突,解决互斥,也就是相同的key只能出现一次。 使用lua脚本进行加锁和设置expire,保证原子。
    可重入:每个线程在进行获取锁前都会去生成一个唯一id, 会判断加锁是否为自己的UUID。来进行可重入判断。

    过期时间: 通过expire进行过期过期时间。
    锁续期: 看门狗机制进行锁时间的续期,默认锁时间设置的是30S,到10S,三分之一的时候会把锁时间重新设置为30S。
    取消更新锁时长的任务场景有几种 :
  2. 1正常释放锁
  3. 2关闭了Redisson实例
  4. 客户端在持有锁的过程中崩溃,也就是服务器down机,看门狗也会因为无法继续运行而停止续约,这样,过了租约时间后,锁就会被自动释放。
    1.2.2 redission使用的什么数据结构。
    锁结构举例,redission使用hash结构进行加锁的。
    {
    “lock-name”: {
    //线程id
    “threadId”: “Java-VM-thread-ID”,
    //UUID , 用于判断可重入,当设置锁时生成UUID。
    “UUID”: “unique-identifier”,
    //看门狗守护线程,进行续约。
    “lockWatchdogTimeout”: “timeout-in-seconds”,
    //加锁时间
    “internalLockLeaseTime”: “lease-time-in-seconds”
    }
    }

源码:
RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
internalLockLeaseTime = unit.toMillis(leaseTime);

return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
//加锁成功的话,创建hashset,并设置过期时间"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hset', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +//如果不存在进行判断线程是否为自己。"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"return redis.call('pttl', KEYS[1]);",Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

}
redis在设置leaseTime的情况下,看门狗是不会续命的。
当不使用leaseTime的情况下,会设置为-1.

1比如我加锁时间是60S, 然后业务执行了70S,在60S的时候,锁会释放, 也就出现了重复加锁的情况, 所以需要根据业务设置合理的加锁时长。

2当我没有设置leaseTime时,默认为-1,也就是永远不停。 为了使用同一套实现 也就是 setnx expire同时使用, 那么默认会设置一个时长为30S,的key。 然后进行锁续命,续命来解决锁失效的问题。

代码中看门狗也就是一个定时任务,会在1/3的时候进行更新任务。
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {

    RFuture<Boolean> future = renewExpirationAsync(threadId);future.addListener(new FutureListener<Boolean>() {@Overridepublic void operationComplete(Future<Boolean> future) throws Exception {expirationRenewalMap.remove(getEntryName());if (!future.isSuccess()) {log.error("Can't update lock " + getName() + " expiration", future.cause());return;}if (future.getNow()) {// reschedule itselfscheduleExpirationRenewal(threadId);}}});
}

}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
internalLockLeaseTime默认为30S。
为什么redis不设置一个永久不过期的时间,而是使用续期的方式进行保证永不过呢?
我的理解是redis想要复用同一套代码,也就是底层全部使用这一套lua。所以使用上层定时维护key时长的方式,达到key永不过期的效果。
RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
internalLockLeaseTime = unit.toMillis(leaseTime);

return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
//加锁成功的话,创建hashset,并设置过期时间"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hset', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +//如果"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"return redis.call('pttl', KEYS[1]);",Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

}

实战:
场景分类:
1单个锁资源,资源可从前端传入。(简单场景) ----使用注解加锁。
2单个锁,复杂场景 使用模版进行处理。 ---- 自定义,使用模版加锁。
3批量场景,一般资源都是后端查出相关的进行锁定,这种复杂的,—自定义处理,使用模版加锁。
1.2.2.1 分布式锁注解
package com.pzhu.spring.cloud.alibaba.consumer.annotation;

import com.pzhu.spring.cloud.alibaba.consumer.Enum.RedisLockEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**

  • 分布式锁注解

  • @author jinhaiyang
    */
    @Retention(RetentionPolicy.RUNTIME)//运行时生效
    @Target(ElementType.METHOD)//作用在方法上
    public @interface RedissonLock {

    /**

    • key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做分布式锁,就自己指定
    • @return key的前缀
      */
      String prefixKey() default “”;

    /**

    • springEl 表达式
    • @return 表达式
      */
      String key();

    /**

    • 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1
    • @return 单位秒
      */
      int waitTime() default -1;

    /**

    • 自动释放锁时长,默认为-1,也就是不会自动释放锁。根据业务设置自动释放锁时长,避免死锁
    • @return 单位秒
      */
      int expireTime() default -1;

    /**

    • 等待锁的时间单位,默认毫秒
    • @return 单位
      */
      TimeUnit unit() default TimeUnit.MILLISECONDS;

}
1.2.2.2 使用说明:

含义
默认值
是否必填
prefixKey
锁前缀
方法的全命名

key
操作锁定key


waitTime
默认锁等待
-1

expireTime
释放锁时间
-1

unit
锁时间单位
毫秒

1.2.2.2.1 单个资源使用示例
1.2.2.2.1.1 使用自定义注解,锁资源可以通过前端传入。只能单个资源加锁。
我用reqDto中的resumeId作为加锁的key,加锁的前缀为reception-web:listLock 因为没有指定默认等待时间,所以立即获取到锁,获取不到会自动失败。因为没有指定expireTime,默认为-1,所以并不会自动释放锁。
@PostMapping(“list”)
@RedissonLock(prefixKey = “reception-web:listLock” ,key = “#order.id”)
public List list(@RequestBody Order order ) {
System.out.println(JSON.toJSONString(reqDTO));
return serviceFeign.list();
}
1.2.2.2.1.2 自己有复杂业务处理的,使用以下模版。

if (Boolean.TRUE.equals(redissionUtil.tryAcquire(RedisLockEnum.ROOM_ARRANGE,GatewayHeaderUtil.getHotelCode() + RedissionUtil.LOCK_ARRANGE))) {
try {
//加锁成功后业务处理
rowHouses(orderArrangeReq);
return true;
} catch (Exception e) {
// 加锁异常后自定义处理
log.error(“rowHouses hotelCode为{} 排房期间出现未知异常”, GatewayHeaderUtil.getHotelCode(), e);
throw e;
} finally {
//重要! 必须在finally中解锁。
redissionUtil.release(RedisLockEnum.ROOM_ARRANGE,GatewayHeaderUtil.getHotelCode() + RedissionUtil.LOCK_ARRANGE);
}
} else {
throw new ReceptionException(ReceptionEnums.ROOM_ARRANGE_NOW, ReceptionEnums.ROOM_ARRANGE_NOW.getResultMsg());
}

1.2.2.2.2 多资源加锁-批量加锁
一般不存在这种场景, 比如当我要去通过团队id,查询团队下的订单id,锁住这些订单。我需要在业务中进行查询,这种情况比较复杂,所以不建议使用注解实现。
使用模版
if (Boolean.TRUE.equals(redissionUtil.multiLockTryAcquire(roomNos,GatewayHeaderUtil.getHotelCode(), 30L))) {
try {
rowHouses(orderArrangeReq);
return true;

} catch (Exception e) {
log.error(“rowHouses hotelCode为{} 房间号列表为{} 排房期间出现未知异常”,GatewayHeaderUtil.getHotelCode(), JSON.toJSONString(roomNos), e);
throw e;
} finally {
redissionUtil.mutiLockRelease(roomNos,GatewayHeaderUtil.getHotelCode());
}
} else {
//加锁失败,证明存在部分房间冲突
throw new ReceptionException(ReceptionEnums.ROOM_IS_ARRANGE_NOW, ReceptionEnums.ROOM_IS_ARRANGE_NOW.getResultMsg());
}
参考文章:
面试官竟然问我怎么实现分布式锁?幸亏我总结了全套八股文
Redis 分布式锁的正确实现原理演化历程与 Redission 实战总结

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

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

相关文章

Redis实战案例19-Redis解决主从一致性问题

主节点&#xff08;Master&#xff09;“写操作”&#xff1a; 接收并响应客户端的读写请求。持久化数据到磁盘&#xff08;根据配置可以选择使用RDB快照或者AOF日志&#xff09;。将自己的写操作同步给所有的从节点。处理发布/订阅&#xff08;Pub/Sub&#xff09;模式中的发…

【剑指offer】11. 机器人的运动范围(java)

文章目录 机器人的运动范围描述示例1示例2示例3示例4思路完整代码 机器人的运动范围 描述 地上有一个 rows 行和 cols 列的方格。坐标从 [0,0] 到 [rows-1,cols-1] 。一个机器人从坐标 [0,0] 的格子开始移动&#xff0c;每一次只能向左&#xff0c;右&#xff0c;上&#xff…

ARM 架构是什么?

ARM&#xff08;Advanced RISC Machines&#xff09;架构是一种处理器架构&#xff0c;它是一种精简指令集计算机&#xff08;RISC&#xff09;架构。ARM架构最初由ARM Holdings&#xff08;现在是SoftBank Group的子公司&#xff09;开发&#xff0c;并在1980年代末和1990年代…

【PAT】1028.List Sorting

【PAT】1028.List Sorting Excel can sort records according to any column. Now you are supposed to imitate this function. Input Specification: Each input file contains one test case. For each case, the first line contains two integers N (≤105) and C, wher…

mysql的完全包含关系怎么写

问&#xff1a; mysql从a表查到aid有两值1&#xff0c;2&#xff0c;b表中存在a表的主键作为外键&#xff0c;从b表中查找完全包含aid的的值&#xff08;1&#xff0c;2&#xff09;的bid 答 如果你有一个表a包含主键列a_id&#xff0c;并且有一个表b&#xff0c;它具有一个…

数据结构和算法:深度优先搜索 (DFS) 和广度优先搜索 (BFS) 相关题目

文章目录 1. 岛屿问题&#xff08;岛屿连通&#xff09;1.1 岛屿数量1.1.1 DFS 解法1.1.2 BFS 解法 深度优先搜索 (DFS) 和广度优先搜索 (BFS)是比较难的算法问题&#xff0c;但也是面试常考题&#xff0c;因此需要认真研究并掌握。 DFS 用递归实现&#xff0c;BFS用栈实现 1. …

使用 SageMaker 对 Whisper 模型进行微调及部署

使用 SageMaker 对 Whisper 模型进行微调及部署 Whisper 作为 OpenAI 最新开源的自动语音识别&#xff08;ASR&#xff09;模型&#xff0c;采用了编码器-解码器&#xff08;encoder- decoder&#xff09;transformer架构&#xff0c;并使用了 68 万小时的从互联网收集的多语言…

PersistentVolume:怎么解决数据持久化

Kubernetes 的 Volume 对数据存储已经给出了一个很好的抽象&#xff0c;它只是定义了有这么一个“存储卷”&#xff0c;而这个“存储卷”是什么类型、有多大容量、怎么存储&#xff0c;我们都可以自由发挥。Pod 不需要关心那些专业、复杂的细节&#xff0c;只要设置好 volumeMo…

安卓APK反编译+修改+重打包+签名

目录 1.下载反编译工具包。2.将APK包&#xff0c;重命名为ZIP&#xff0c;解压。放到反编译根目录下。3.使用apktool反编译修改smail文件&#xff0c;进行重打包4.重新打包5.重签名 1.下载反编译工具包。 反编译工具包地址&#xff1a;百度网盘 提取码&#xff1a;dsu3 解压后…

AtcoderABC243场

A - Shampoo A - Shampoo ] 题目大意 高桥家有三个人&#xff1a;高桥、他的父亲和他的母亲。每个人每晚都在浴室洗头发。他们按照顺序使用AA、BB和CC毫升的洗发水。 问&#xff0c;今天早上瓶子里有VV毫升的洗发水。在不重新装满的情况下&#xff0c;谁会第一个用完洗发水洗头…

网工内推 | 美图秀秀招网工,大专以上,15薪,NP认证优先

01 美图公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、美图大厦网络、分公司网络、IT相关项目的网络、办公内网服务器&#xff1b; 2、负责网络的设计、运行、管理和维护等工作&#xff1b; 3、负责远程办公环境的优化、运行、管理和维护工作&#xff1b; 4、…

python匿名函数Lambda

1、Lambda 函数简介 Lambda函数也被称为匿名(没有名称)函数&#xff0c;它直接接受参数的数量以及使用该参数执行的条件或操作&#xff0c;该参数以冒号分隔&#xff0c;并返回最终结果。为了在大型代码库上编写代码时执行一项小任务&#xff0c;或者在函数中执行一项小任务&a…

git merge详细用法

git merge用法 一、开发分支&#xff08;dev&#xff09;上的代码达到上线的标准后&#xff0c;要合并到 master 分支 git checkout dev git pull git checkout master git merge dev git push -u origin master二、当master代码改动了&#xff0c;需要更新开发分支&#xff…

吴恩达机器学习2022-Jupyter-用scikitlearn实现线性回归

1可选实验:使用Scikit-Learn进行线性回归 有一个开源的、商业上可用的机器学习工具包&#xff0c;叫做 scikit-learn。本工具包包含您将在本课程中使用的许多算法的实现。 1.1工具 您将利用 scikit-learn 以及 matplotlib 和 NumPy 中的函数。 2线性回归封闭式解决方案 Sc…

nvm常用命令

查看node.js的所有版本&#xff1a; npm view node versions 查看 nvm 是否安装成功&#xff1a; command -v nvm查看当前使用的 Node 版本&#xff1a; node -v将安装 12.7.0 版本的Node&#xff1a; nvm install 12.7.0卸载 12.7.0 版本的Node&#xff1a; nvm uninstall 12…

为什么低代码只能掀起小浪花?了解低代码的得失与前景

导语&#xff1a;低代码是相对于高代码和无代码的一个中间概念&#xff0c;通常强调的是用户不需要学习如何写代码&#xff0c;就能完成工作。然而低代码模式一直不温不火&#xff0c;原因是什么呢&#xff1f;一起来看一下吧。 最近互联网大公司裁员消息又起&#xff0c;“低代…

Linux 配置常见服务器命令

Linux常见配置服务器的命令整理&#xff0c;基于Centos 7 。 基本操作命令 查看ens33 ip ifconfig ens33 查看系统版本 cat /etc/redhat-release 查找文件install.log、httpd.conf、hosts、smb.conf、network find -name te.txt 使用“cp 源文件 目标文件”命令进行复制 cp t…

Serial/TCP/NTRIP通信

1 串口通信 串口通讯 串口通信详解 串口通信中的4大参数含义 Qt 串口通信的简单demo 2 TCP通信 TCP协议简介 TCP协议详细介绍 TCP协议(全面) IP地址和端口号的详解 3 NTRIP通信 Ntrip通讯协议1.0 什么是Ntrip&#xff1f;Ntrip协议简介 TCP高并发数据转接服务器&#xff0…

【ElasticSearch】java 如何连接 elasticsearch

不同版本的java 与 Elastic 如何连接。 在 es 与 java 连接中最重要的就是『兼容性矩阵」&#xff0c;要严格按照兼容性矩阵的要求来部署 spring boot 或者 es的版本&#xff0c;否则会有意向不到的错误或者程序无法运行等等。 针对 es 6.2.2&#xff0c;java 客户端的配置代…

Linux: cannot read file data

报错&#xff1a; Could not load library libcudnn_cnn_infer.so.8. Error: /home/qc/miniconda3/envs/DNAqc/lib/python3.10/site-packages/torch/lib/libcudnn_cnn_infer.so.8: cannot read file data Please make sure libcudnn_cnn_infer.so.8 is in your library path! A…