Redis 分布式锁 @Klock 注解详解及使用教程

news2024/11/17 14:22:21

文章目录

  • 一、作用
  • 二、参数
  • 三、锁的流程
  • 四、SpringBoot 集成
      • 1. pom 依赖
      • 2. yaml 配置
      • 3. 使用方式
  • 五、变量级锁和方法级锁
  • 六、常见应用场景
      • 1. 页面重复提交
      • 2. 定时任务
      • 3. 核心业务
  • 七、锁的粒度与锁的时间


一、作用

注解 @klock 是基于 Redis 的分布式锁,作用在分布式事务中避免并发冲突。只作用于方法上。

二、参数

  1. name:锁的名称。锁唯一标识。

  2. keys:加锁 id。加锁对象的唯一标识。

  3. lockType:锁类型。默认可重入锁。

     LockType.Reentrant 可重入锁。默认。
     LockType.Fair 公平锁。
     LockType.Reentrant 读锁。
     LockType.Reentrant 写锁。
    
  4. waitTime:获取锁最长等待时间。单位秒。

  5. leaseTime:获取到锁后,自动释放锁的时间。

  6. lockTimeoutStrategy:获取锁超时的处理策略。默认 NO_OPERATION 继续执行业务逻辑,不做任何处理。

     NO_OPERATION:不做处理,不加锁继续执行业务逻辑。(默认)
     FAIL_FAST:快速失败,直接抛出获取锁超时异常。
     KEEP_ACQUIRE:阻塞等待,一直阻塞,直到获得锁,在太多的尝试后,仍会报错。
     		       一般下一次获取锁的间隔时间初始为0.1秒、总共尝试次数为10次,下一次获取锁的时间间隔会成倍增长。很可能引起死锁。
    
  7. customLockTimeoutStrategy:自定义加锁超时的处理策略。String 类型,当前类中的方法名。会覆盖 lockTimeoutStrategy 的处理策略。

     自定义处理方法,方法参数必须与加锁的方法参数一致。
    
  8. releaseTimeoutStrategy:占有锁超时的处理策略。默认 NO_OPERATION 继续执行业务逻辑,不做任何处理。

     NO_OPERATION:不做处理,不加锁继续执行业务逻辑。(默认)
     FAIL_FAST:快速失败,直接抛出获取锁超时异常。
    
  9. customReleaseTimeoutStrategy:自定义释放锁时已超时的处理策略。String 类型,当前类中的方法名。会覆盖 releaseTimeoutStrategy 的处理策略。

     自定义处理方法,方法参数必须与加锁的方法参数一致。
    

三、锁的流程

在这里插入图片描述

四、SpringBoot 集成

1. pom 依赖

<!--klock-->
<dependency>
	<groupId>cn.keking</groupId>
	<artifactId>spring-boot-klock-starter</artifactId>
	<version>1.4-RELEASE</version>
</dependency>

2. yaml 配置

  1. 单节点配置
spring:
  klock:
    #单节点地址
    address: 192.168.0.193:6379
    #密码,没有密码就不要配置password
    #password:
    #获取锁最长阻塞时间(默认:60,单位:秒)
    wait-time: 20
    #已获取锁后自动释放时间(默认:60,单位:秒)
    lease-time: 20
  1. 集群配置
spring:
  klock:
    #密码
    #password:
    #获取锁最长阻塞时间(默认:60,单位:秒)
    wait-time: 20
    #已获取锁后自动释放时间(默认:60,单位:秒)
    lease-time: 20
    #集群配置
    cluster-server:
    node-addresses: 192.168.0.111:6379,192.168.0.112:6379,192.168.0.113:6379,192.168.0.101:6379,192.168.0.102:6379,192.168.0.103:6379,192.168.0.114:6379,192.168.0.104:6379

3. 使用方式

/**
 * 根据id查询
 *
 * @param id 文章id
 * @return 文章
 */
@Override
@Klock(name = "article-hist-lock", // 锁名称
 		keys = "#id", // 加锁id
        lockType = LockType.Fair, // 锁类型;公平锁
        waitTime = 10, // 获取锁最长等待时间:10s
        leaseTime = 300, // 获得锁后,自动释放锁的时间:300s
        lockTimeoutStrategy = LockTimeoutStrategy.FAIL_FAST, // 获取锁超时的处理策略:直接返回失败
        releaseTimeoutStrategy = ReleaseTimeoutStrategy.FAIL_FAST) // 占有锁超时的处理策略:直接返回失败
public CmsArticleVo selectCmsArticleVoByIdForApi(Long id) {
    // 设置查询文章的内容类型
    String contentType = CmsConstants.CONTENT_TYPE_ARTICLE;
    CmsArticleVo cmsArticleVo = cmsArticleMapper.selectCmsArticleVoById(id, contentType);
    if (cmsArticleVo != null) {
        // 文章点击量+1
        CmsArticle cmsArticle = new CmsArticle();
        cmsArticle.setId(cmsArticleVo.getId());
        cmsArticle.setHist(cmsArticleVo.getHist() + 1);
        cmsArticleMapper.updateById(cmsArticle);
    }
    return cmsArticleVo;
}

五、变量级锁和方法级锁

VariableAndMethodLockService

package com.alian.redisklock.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.klock.annotation.Klock;
import org.springframework.boot.autoconfigure.klock.model.LockTimeoutStrategy;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class VariableAndMethodLockService {

    /**
     * 变量级加锁
     * @param userId
     */
    @Klock(keys = "#userId", lockTimeoutStrategy = LockTimeoutStrategy.FAIL_FAST)
    public void variableLock(String userId) {
        log.info("变量级加锁收到的消息:{}", userId);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("变量级加锁业务处理完:{}", userId);
    }

    /**
     * 方法级加锁
     * @param userId
     */
    @Klock
    public void methodLock(String userId) {
        log.info("方法级加锁收到的消息:{}", userId);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("方法级加锁业务处理完:{}", userId);
    }
}

使用5个线程来进行并发验证:

TestLockTypeService

package com.alian.redisklock.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.concurrent.CountDownLatch;

@Slf4j
@Service
public class TestLockTypeService {

    private final CountDownLatch countDownLatch = new CountDownLatch(1);

    @Autowired
    private VariableAndMethodLockService vmService;

    @PostConstruct
    public void testVariableAndMethodLock() {
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //变量级加锁
                vmService.variableLock("10001_" + finalI);
                //方法级加锁
//                vmService.methodLock("10002_"+ finalI);
            }, "Thread" + i).start();
        }
        countDownLatch.countDown();
    }

}

变量级加锁结果

2023-09-23 17:42:15 432 [Thread0] INFO :变量级加锁收到的消息:10001_0
2023-09-23 17:42:15 432 [Thread1] INFO :变量级加锁收到的消息:10001_1
2023-09-23 17:42:15 432 [Thread4] INFO :变量级加锁收到的消息:10001_4
2023-09-23 17:42:15 432 [Thread2] INFO :变量级加锁收到的消息:10001_2
2023-09-23 17:42:15 432 [Thread3] INFO :变量级加锁收到的消息:10001_3
2023-09-23 17:42:16 439 [Thread3] INFO :变量级加锁业务处理完:10001_3
2023-09-23 17:42:16 439 [Thread2] INFO :变量级加锁业务处理完:10001_2
2023-09-23 17:42:16 439 [Thread0] INFO :变量级加锁业务处理完:10001_0
2023-09-23 17:42:16 439 [Thread4] INFO :变量级加锁业务处理完:10001_4
2023-09-23 17:42:16 439 [Thread1] INFO :变量级加锁业务处理完:10001_1

方法级加锁结果

2023-09-23 17:43:15 779 [Thread3] INFO :方法级加锁收到的消息:10002_3
2023-09-23 17:43:16 787 [Thread3] INFO :方法级加锁业务处理完:10002_3
2023-09-23 17:43:16 797 [Thread2] INFO :方法级加锁收到的消息:10002_2
2023-09-23 17:43:17 800 [Thread2] INFO :方法级加锁业务处理完:10002_2
2023-09-23 17:43:17 819 [Thread0] INFO :方法级加锁收到的消息:10002_0
2023-09-23 17:43:18 833 [Thread0] INFO :方法级加锁业务处理完:10002_0
2023-09-23 17:43:18 844 [Thread1] INFO :方法级加锁收到的消息:10002_1
2023-09-23 17:43:19 847 [Thread1] INFO :方法级加锁业务处理完:10002_1
2023-09-23 17:43:19 863 [Thread4] INFO :方法级加锁收到的消息:10002_4
2023-09-23 17:43:20 864 [Thread4] INFO :方法级加锁业务处理完:10002_4

结论:

  • 变量级锁,针对的是一个变量,变量不同,获取的锁就不存在先后顺序,都可以获取到自己的锁。
  • 方法级锁,针对的是方法,哪怕请求值不一样,也只能一个线程获取到锁,直到释放后,下一个线程才能获取。
  • 变量级锁的效率更适合高并发场景,而方法级锁可能引起阻塞。

六、常见应用场景

1. 页面重复提交

最常见的就比如手机端录入信息到后台,比如注册之类的等等,用户端可能因为各种原因可能会点击多次,导致后台可能会出现多笔记录的情况,这个时候很简单,用到我们的锁,假设,我们是注册用户,手机号是唯一的。

@Klock(keys = "#phone", lockTimeoutStrategy = LockTimeoutStrategy.FAIL_FAST)
@RequestMapping("register")
public void register(String phone,String nickName) {
    log.info("注册账户收到的信息:{},{}", phone,nickName);
    try {
        //模拟业务过程
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("注册账户业务处理完");
}

这个时候,如果是点击了两次,第一次业务进入获取到锁进行处理,第二过来了也是一个等待,要么第一次处理完成,第二次业务判断已注册,要么第二次直接超时了。

2. 定时任务

工作中定时任务使用还是蛮多的,但是也会有很多问题,当遇到分布式服务时,一个服务部署多台,定时任务就可能会同时运行,这种情况怎么处理呢?有些人可能会给两个服务的配置改成不一样,比如定时任务的时间修改,一个正常执行,一个在不可能的时间执行,还有人直接给服务设置一个标志位,只有某个标志位的能执行。好吧,在分布式环境,并且服务不是很多的情况下,也许还能勉强维护,那么如果是容器下呢?所以分布式锁的方案就更加重要了。

@Scheduled(cron = "0 0 2 * * ?")
public void dataCollector(){
    //开始做任务
    String dataTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
    createDataFile(dataTime);
    //结束任务
}

@Klock(keys = "#dataTime", lockTimeoutStrategy = LockTimeoutStrategy.FAIL_FAST)
public void createDataFile(String dataTime) {
    log.info("开始生成对账文件:{}", dataTime);
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("对账文件生成完成:");
}

这是一个示例,实际中,createDataFile方法是另外一个service中的方法,这样不管是单机,分布式多机,还是在容器中都只有一个定时任务在执行,而不会导致重复数据问题。

3. 核心业务

其实这个场景是用的最多的,比如商品库存的扣减,因为我们不能超卖啊。实际工作中,需要根据自己的业务定义特定意义的key就可以了。

@Klock(keys = "#goodId", lockTimeoutStrategy = LockTimeoutStrategy.FAIL_FAST)
public void deductCommodityInventory(String goodId,int num) {
    log.info("商品【{}】扣减库存:{}", goodId,num);
    //扣减库存操作
    //dosomething()
    log.info("商品扣减库存完成");
}

七、锁的粒度与锁的时间

锁的粒度一定要把握好,不能过小或者过大。能使用粒度小的锁,就尽量使用。比如尽量使用粒度更小的变量级锁,而不是方法级锁。尽量使用对象的唯一性字段作为锁的 key,而不是对象。

锁的时间控制一定要合理,不能太长也不能太短,根据相应的需求来合理设置以达到较好的性能。包括等待时间、释放时间、超时策略的时间响应问题。

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

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

相关文章

数据结构--》解锁数据结构中树与二叉树的奥秘(二)

数据结构中的树与二叉树&#xff0c;是在建立非线性数据结构方面极为重要的两个概念。它们不仅能够模拟出生活中各种实际问题的复杂关系&#xff0c;还常被用于实现搜索、排序、查找等算法&#xff0c;甚至成为一些大型软件和系统中的基础设施。 无论你是初学者还是进阶者&…

PHP会话技术跟踪和记录用户?使用cookie会话你必须掌握

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《速学数据结构》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言会话技术的概述一. Cookie简介二. Cookie基本使用——创建Cookie2.1 创建Cookie演示实例&am…

Zinquin ethyl ester(CAS NO. 181530-09-6),TSQ的类似物

Zinquin ethyl ester&#xff08;CAS NO. 181530-09-6&#xff09;&#xff0c;是广泛使用锌离子荧光探针TSQ的类似物&#xff0c;也是一种细胞渗透性、基于喹诺酮结构的锌离子&#xff08;Zn2&#xff09;荧光探针&#xff0c; 该探针可以用于在细胞中检测和定位锌离子。它具…

软件公司的项目管理软件选择指南

我们经常在项目推进中经常遇到各种各样的问题&#xff0c;最常见的是因团队工作效率低而无法在截止日期之前按时完成工作。但是如果能合理使用项目管理软件&#xff0c;可以有效监控项目进程&#xff0c;提高工作效率&#xff0c;从而保证按时完成任务。那么软件公司适合什么项…

Qt编程-QTableView同时冻结行和列

前言 Qt编程-QTableView同时冻结行和列。如题&#xff0c;先看效果是不是你需要的。网上找到的代码片段要么不全要么不是想要的。如果你只需要需要冻结行或冻结列&#xff0c;请看上篇博客 Qt编程-QTableView冻结行或冻结列或冻结局部单元格 &#xff0c;代码更少一些。 同时…

ansible 调研

参考&#xff1a;自动化运维工具——ansible详解&#xff08;一&#xff09; - 珂儿吖 - 博客园 (cnblogs.com) ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xf…

专用/独享代理与共享代理有何区别?如何选择?

近年来&#xff0c;互联网发展快速&#xff0c;随着许多互联网业务的迸发&#xff0c;代理IP也作为一种互联网工具进入大家的业务&#xff0c;广泛地运用于跨境电商、社媒运营、SEO检测、市场研究等业务中。那么代理IP分为共享与独享&#xff0c;他们使用上有什么区别&#xff…

让你的对象变得拗口:JSON.stringify(),我把对象夹进了 JSON 魔法帽!

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 引言 1. JSON.stringify() 属性 replacer …

Gartner中国零信任网络访问市场指南发布!持安科技获评代表厂商

近日&#xff0c;全球权威市场研究与咨询机构Gartner发布《中国零信任网络访问市场指南》。其中&#xff0c;零信任办公安全企业持安科技入选为中国零信任网络访问领域“代表厂商”。 市场指南报告&#xff08;Market Guide&#xff09;是Gartner基于技术发展、落地案例等进行严…

Linux 实时补丁开启内核抢占了吗?

Linux 实时补丁开启内核抢占了吗&#xff1f; 开启了。 查看Linux实时补丁&#xff0c;发现修了如下内核宏&#xff1a; PREEMPT_RT补丁的关键点是最小化不可抢占的内核代码量&#xff0c;同时最小化为了提供这种额外的可抢占性而必须更改的代码量。特别是&#xff0c;临界区…

深度学习DAY3:激活函数

激活函数映射——引入非线性性质 h &#xff08;Σ(W * X)b&#xff09; yσ&#xff08;h&#xff09; 将h的值通过激活函数σ映射到一个特定的输出范围内的一个值&#xff0c;通常是[0, 1]或[-1, 1] 1 Sigmoid激活函数 逻辑回归LR模型的激活函数 Sigmoid函数&#xff0…

竞赛 深度学习 大数据 股票预测系统 - python lstm

文章目录 0 前言1 课题意义1.1 股票预测主流方法 2 什么是LSTM2.1 循环神经网络2.1 LSTM诞生 2 如何用LSTM做股票预测2.1 算法构建流程2.2 部分代码 3 实现效果3.1 数据3.2 预测结果项目运行展示开发环境数据获取 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天…

Bootstrap中让元素尽可能往父容器的左侧靠近或右侧造近(左浮动和右浮动)

在Bootstrap中&#xff0c;float-left是一个用于浮动元素的CSS类。它的作用是将一个元素向左浮动&#xff0c;使其在父容器内尽可能靠近左侧边缘&#xff0c;同时允许其他元素在其右侧排列。 使用float-left类可以创建多列布局&#xff0c;将元素水平排列在一行上&#xff0c;…

【脑机接口论文与代码】High-speed spelling with a noninvasive brain–computer interface

High-speed spelling with a noninvasive brain–computer interface 中文题目 &#xff1a;非侵入性的高速拼写脑机接口论文下载算法程序下载摘要1 项目介绍2 方法2.1SSVEPs的基波和谐波分量JFPM刺激产生算法2.3基波和谐波SSVEP分量的幅度谱和信噪比 3讨论4实验环境设置与方法…

全球邮企业邮箱服务比较:找寻最佳选择

“全球邮企业邮箱服务比较&#xff1a;Gmail、Outlook、Yahoo Mail、Zoho Mail&#xff0c;更适合中国用户的是Zoho Mail。” 在全球化的商业环境中&#xff0c;企业邮箱已经成为了一种重要的沟通工具。它不仅提供了安全、可靠的电子邮件服务&#xff0c;而且还能够集成其他企业…

hive add columns 后查询不到新字段数据的问题

分区表add columns 查询不到新增字段数据的问题&#xff1b; 5.1元数据管理 &#xff08;1&#xff09;基本架构 Hive的2个重要组件&#xff1a;hiveService2 和metastore,一个负责转成MR进行执行&#xff0c;一个负责元数据服务管理 beeline-->hiveService2/spar…

性能分析与调优(硬核分享)

前言 常看到性能测试书中说&#xff0c;性能测试不单单是性能测试工程师一个人的事儿。需要DBA 、开发人员、运维人员的配合完成。但是在不少情况下性能测试是由性能测试人员独立完成的&#xff0c;退一步就算由其它人员的协助&#xff0c;了解系统架构的的各个模块对于自身的…

MAX4/11/03/016/08/1/1/00 MAX-4/11/01/008/08/1/1/00

MAX4/11/03/016/08/1/1/00 MAX-4/11/01/008/08/1/1/00 sales force宣布推出制造业云(Manufacturing Cloud)&#xff0c;这是一款面向制造商的行业专用产品。制造云致力于将销售和运营团队聚集在统一的市场和客户需求视图周围&#xff0c;目标是更准确地预测、规划和推动可预测…

口袋参谋:如何对宝贝关键词进行词根分析?用它就对了!

​为什么宝贝转化不好&#xff1f;90%的原因是宝贝关键词没选好&#xff0c;关键词选择得不好&#xff0c;会出现点击率、展现、访客、收藏加购率等数据降低的情况&#xff0c;还会导致关键词质量得分波动大&#xff0c;甚至影响整个店铺的经营。 所以对电商卖家来说&#xff…

微信照片过期打不开怎么办?用这个办法可找回

时间太久想找之前的聊天图片 却发现图片已被清理 因为忙碌或者在外游玩一时间忘了点开 想起要找回的时候却发现已经过期 不妨试试这样几个找回小方法 PART2 图片找回 收藏和搜一搜找回 长按要找回的图片 点击收藏或搜一搜 不能保证百分百的成功率哦 存储空间找回 打开【存…