分布式锁,学习笔记

这篇具有很好参考价值的文章主要介绍了分布式锁,学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  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>() {
        @Override
        public 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 itself
                scheduleExpirationRenewal(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 实战总结文章来源地址https://www.toymoban.com/news/detail-583258.html

到了这里,关于分布式锁,学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【学习笔记】minIO分布式文件服务系统

    1.1 minIO是什么? MinIO是专门为海量数据存储、人工智能、大数据分析而设计的对象存储系统。(早前流行的还有FastDFS) 据官方介绍,单个对象最大可存储5T,非常适合存储海量图片、视频、日志文件、备份数据和容器、虚拟镜像等。 采用golang语言编译 客户端和用户端交互采

    2024年02月07日
    浏览(40)
  • 机器学习分布式框架ray tune笔记

    Ray Tune作为Ray项目的一部分,它的设计目标是简化和自动化机器学习模型的超参数调优和分布式训练过程。Ray Tune简化了实验过程,使研究人员和数据科学家能够高效地搜索最佳超参数,以优化模型性能。 Ray Tune的主要特点包括: 超参数搜索空间规范 : Ray Tune允许您使用多种方

    2024年02月15日
    浏览(42)
  • 黑马程序员--分布式搜索ElasticSearch学习笔记

    黑马视频地址:https://www.bilibili.com/video/BV1LQ4y127n4/ 想获得最佳的阅读体验,请移步至我的个人博客 SpringCloud学习笔记 消息队列MQ学习笔记 Docker学习笔记 分布式搜索ElasticSearch学习笔记 ElasticSearch的作用 ElasticSearch 是一款非常强大的开源搜素引擎,具备非常强大的功能,可以帮

    2024年02月04日
    浏览(47)
  • Maven学习笔记(SSM 整合伪分布式案例)

    目录 第一节 创建工程,引入依赖 1 创建工程 ①工程清单 ②工程间关系 2、各工程 POM 配置 ①父工程 ②Mybatis 逆向工程 ③环境依赖工程 ④工具类工程 ⑤实体类工程 ⑥组件工程 ⑦Web 工程 第二节 搭建环境:持久化层 1、物理建模 2、Mybatis 逆向工程 ①generatorConfig.xml ②执行逆

    2024年02月10日
    浏览(47)
  • 学习笔记MinIo对象存储-Docker分布式集群搭建踩坑!

    ​ MinIO 是一款基于Go语言的高性能对象存储服务,在Github上已有39K+Star。它采用了Apache License v2.0开源协议,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。 本文将使用 MinIO 来自建一个对象存储服务用于存储图片。 ​ M

    2024年02月11日
    浏览(53)
  • 【DDD分布式系统学习笔记】RPC调用以及系统初步搭建

    modelVersion: 模型版本,指定POM模型的版本,目前使用的是Maven 4.0.0版本。 groupId: 项目的组织标识符,通常是组织的域名倒序。在这里是 cn.itedus.lottery。 artifactId: 项目的唯一标识符,通常是项目的名称。在这里是 Lottery。 packaging: 项目的打包方式,这里是 pom,表示这是一个聚合

    2024年01月18日
    浏览(48)
  • 《分布式中间件技术实战:Java版》学习笔记(一):抢红包

    数据库建表 (1)red_send_record 记录用户发送了若干总金额的若干个红包。 (2)red_detail 记录用户发送的红包被分成的小红包金额。 (3)red_rob_record 记录用户抢到的红包金额。 随机生成红包金额 红包金额的最小单位是分,将红包金额放大100倍到int类型(为了方便生成随机数),保证

    2024年02月10日
    浏览(51)
  • 学习笔记:NATS--自适应边缘和分布式系统的连接技术。(更新中)

    目录 1. NATS: 自适应边缘和分布式系统 的连接技术 nats可以做什么: 为什么需要NATS技术: NATS连接技术的特性: 2. 什么是NATS: 面向消息的中间件 nats用来做什么: NATS 服务基础设施是什么? NATS 客户端如何 连接 到 NATS 服务器? NATS是如何设计消息传递的? 该设计的 好处 :

    2024年04月08日
    浏览(51)
  • 分布式任务调度平台XXL-JOB学习笔记-helloworld运行

    环境:win10 eclipse java17 mysql8.0.17 xxl-job 2.4 源码:https://github.com/xuxueli/xxl-job/ 导入时按Existing Maven Projects导入,先导入xxl-job-admin(管理平台)和xxl-job-executor-sample-springboot(通过springboot管理的执行器实例)。 如果导入时速度非常慢,或者报错如 Plugin ‘org.apache.maven.plugins:maven-

    2024年02月13日
    浏览(49)
  • 学习笔记20230629 -- 《分享在jsp分布式项目支援开发衍生功能时遇到和解决的问题》

    1.jsp项目的页面跳转,需要后端的java技术做支撑,在java的接口文件中写跳转接口,使用ajax去请求这个跳转接口,将返回的数据(html标签代码),放到当前页面或弹窗的\\\"content\\\"属性中 2.使用原生js写法实现 全选 功能和删除功能的逻辑思想 将每条回显数据的唯一id值作为每一条

    2024年02月11日
    浏览(94)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包