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

文章目录

  • 一、作用
  • 二、参数
  • 三、锁的流程
  • 四、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", // 加锁idlockType = LockType.Fair, // 锁类型;公平锁waitTime = 10, // 获取锁最长等待时间:10sleaseTime = 300, // 获得锁后,自动释放锁的时间:300slockTimeoutStrategy = 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) {// 文章点击量+1CmsArticle 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*/@Klockpublic 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);@Autowiredprivate VariableAndMethodLockService vmService;@PostConstructpublic 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.rhkb.cn/news/159250.html

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

相关文章

javascript将html中的dom元素转图片

javascript将html中的dom元素转图片 百度网盘下载html2canvas.min.js&#xff1a; 全部文件-》js插件-》 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>网页中的某个区域转图片</title></head><body styl…

前端--CSS

文章目录 CSS的介绍 引入方式 代码风格 选择器 复合选择器 (选学) 常用元素属性 背景属性 圆角矩形 Chrome 调试工具 -- 查看 CSS 属性 元素的显示模式 盒模型 弹性布局 一、CSS的介绍 层叠样式表 (Cascading Style Sheets). CSS 能够对网页中元素位置的排版进行像素级精…

【K8S】集群中部署nginx应用 运行手写yaml文件报错排查过程

文章目录 ❌报错信息&#x1f50e;排查过程✅问题解决 ❌报错信息 提取报错信息【 unknown field “spec.selector.replicas”】【 unknown field “spec.selector.template”】 [rootmaster ~]# kubectl apply -f nginx-deployment.yaml Error from server (BadRequest): erro…

蓝桥杯双周赛算法心得——数树数(dfs)

大家好&#xff0c;我是晴天学长&#xff0c;一个简单的dfs思想&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。 1) .数树数 2) .算法思路 代码的主要逻辑是&#xff1a; 1.使用Scanner读取输入的整数n和q&#xff0c;其中n表示测试用例的数量&am…

数据结构与算法-栈

栈和队列是两种常用的线性结构&#xff0c;属于特殊的线性表&#xff0c;是线性表相关运算的一个子集。一般来说&#xff0c;线性表上的插入和删除操作不受任何限制&#xff0c;但栈只能在表的一端进行插入和删除操作&#xff0c;而队列则只能在一端进行插入操作&#xff0c;在…

MyBatis的缓存,一级缓存,二级缓存

10、MyBatis的缓存 10.1、MyBatis的一级缓存 一级缓存是SqlSession级别的&#xff0c;通过同一个SqlSession对象 查询的结果数据会被缓存&#xff0c;下次执行相同的查询语句&#xff0c;就 会从缓存中&#xff08;缓存在内存里&#xff09;直接获取&#xff0c;不会重新访问…

Centos下编译ffmpeg动态库

文章目录 一、下载ffmpeg安装包二、编译ffmpeg三、安装yasm 一、下载ffmpeg安装包 下载包 wget http://www.ffmpeg.org/releases/ffmpeg-4.4.tar.gz解压 tar -zxvf ffmpeg-4.4.tar.gz二、编译ffmpeg 进入解压的目录 cd ffmpeg-4.4编译动态库 ./configure --enable-shared…

关于pytorch不区分行向量与列向量的理解

听李沐老师讲深度学习时候解释pytorch不区分行向量和列向量&#xff0c;只相当于是一维数组&#xff0c;一维张量一定是行向量&#xff0c;相当于数组&#xff0c;而行列向量可以放到矩阵中看。 测试如下&#xff1a; rtorch.tensor([1,2,3],dtypetorch.float32) print(r,r.T…

Redis五大数据类型的底层设计

SDS 无论是 Redis 的 Key 还是 Value&#xff0c;其基础数据类型都是字符串。虽然 Redis是使用标准 C 语言开发的&#xff0c;但并没有直接使用 C 语言中传统的字符串表示&#xff0c;而是自定义了一 种字符串。这种字符串本身的结构比较简单&#xff0c;但功能却非常强大&…

Master PDF Editor v5.9.70便携版

软件介绍 Master PDF Editor中文版是一款小巧的多功能PDF编辑器,可以轻松查看,创建,修改,批注,签名,扫描,OCR和打印PDF文档.高级注释工具,可以添加任意便笺指示对象突出显示,添加下划线和删除,而无需更改源PDF文件. 软件截图 更新日志 code-industry.net/what-is-new-in-mas…

4.1 继承性

知识回顾 &#xff08;1&#xff09;类和对象的理解&#xff1f; 对象是现实世界中的一个实体&#xff0c;如一个人、一辆汽车。一个对象一般具有两方面的特征&#xff0c;状态和行为。状态用来描述对象的静态特征&#xff0c;行为用来描述对象的动态特征。 类是具有相似特征…

goland 旧版本使用1.19环境

C:\Go\src\runtime\internal\sys\zversion.go // Code generated by go tool dist; DO NOT EDIT.package sysconst StackGuardMultiplierDefault 1const TheVersion go1.19引入其他包的标识符 package mainimport ("fmt""gotest/test")func main() {f…

Flask (Jinja2) 服务端模板注入漏洞复现

文章目录 Flask (Jinja2) 服务端模板注入漏洞1.1 漏洞描述1.2 漏洞原理1.3 漏洞危害1.4 漏洞复现1.4.1 漏洞利用 1.5 漏洞防御 Flask (Jinja2) 服务端模板注入漏洞 1.1 漏洞描述 说明内容漏洞编号漏洞名称Flask (Jinja2) 服务端模板注入漏洞漏洞评级高危影响版本使用Flask框架…

Linux友人帐之调试器--gdb的使用

一、debug和realease版本的区别 区别 debug是给程序员用的版本&#xff0c;添加了调试信息&#xff0c;用于解决软件或程序中出现的问题&#xff0c;realease是发行给客户使用的版本&#xff0c;并未添加调试信息&#xff0c;只需要给客户提供优越的产品使用环境即可&#xff…

数据库系统概论学习 1 绪论

1.1.1 数据、数据库、数据库管理系统、数据库系统 一、数据 Data 数据是数据库中存储的基本对象 定义&#xff1a;描述事物的符号记录称为数据&#xff0c;描述事物的符号可以是数字、文字、图像、图形、声音、语言等表现形式&#xff0c;它们都可以经过数字化后存入计算机。…

【C++进阶】:C++类型转换

C类型转换 一.C语言里的类型转换二.C语音类型转换的一些弊端三.C的四种类型转换1.static_cast2.reinterpret_cast3.const_cast4.dynamic_cast 一.C语言里的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者…

ST‐LINK V2 使用说明(安装,调试,烧录)

目录 1. 初识 ST-LINK V2 1.1 ST-LINK V2 简介 2. ST-LINK V2 驱动的安装与固件升级 2.1 驱动的安装 2.2 固件的升级 3. 使用 STM32 ST-LINK Utility 烧写目标板 hex 3.1 ST-LINK 烧写 hex 文件 4.使用 ST-LINK V2 调试 STM8 4.1 ST‐LINK 调试 STM8 5.…

String方法:分割、梦回C语言,格式化输出format

String poem "锄禾日当午&#xff0c;汗滴禾下土&#xff0c;谁知盘中餐&#xff0c;粒粒皆辛苦";String[] split poem.split("&#xff0c;");System.out.println("悯农");for (int i 0; i < split.length; i) {System.out.println(split…

53 打家劫舍

打家劫舍 题解1 DP1题解2 DP2 &#xff01;经典DP&#xff01; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果 两间相邻的房屋在同一晚上被小偷闯入…

【C++】继承 -- 详解

一、继承的概念及定义 1、继承的概念 继承 (inheritance) 机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。 继承呈现了面向对象 程序设…