Java使用Redis实现分布式锁
1、概述
此处使用Redis的setNx命令和expire命令和del命令来实现分布式锁。
首先我们要知道, 我们的redis执行命令是队列方式的,并不存在多个命令同时运行,所有命令都是串行的访问。那么这就说明我们多个客户端连接Redis的时候不存在其并发的问题。
其实实现分布式锁并不仅仅可以使用Redis完成,也可以使用其他的方式来完成,最主要的目的就是有一个地方能作为锁状态,然后通过这个锁的状态来实现代码中的功能。只要我们这个锁操作的时候是是串行的,那么就能实现分布式锁。
其实有一个问题,为什么我们不使用Java中的synchronized而要去搞一个分布式锁呢?其实就是因为现在都是分布式环境,而Java内置的synchronized是针对单个Java进程的锁,而分布式环境下有n个Java进程,而分布式锁实现的多个Java进程之间的锁。
那么为什么我们要使用setNx命令,而不使用其他命令呢?例如get命令,这种当我们获取到key以后,可能已经是脏数据了,而我们的setNx的意思是,我们设置一个key,如果此key已经存在,那么则返回0,不存在则返回1并设置成功,我们就可以利用这个方式来实现所谓的分布式锁。
注意,分布式锁实现最重要的地方就是有一个步骤能做到串行且不会脏数据。
废话不多说直接上现成的方法。
2、代码
/**
* Redis 锁工具类
*
* @author dh
* @date 20211028103258
**/
@Component
public class RedisLockHelper {
@Autowired
public RedisTemplate redisTemplate;
/**
* 获取锁
* @param key 锁key
* @param seconds 最大锁时间
* @return true:成功,false:失败
*/
public boolean lock(String key,Long seconds){
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
/** 如果不存在,那么则true,则允许执行, */
Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(key).getBytes());
/** 防止死锁,将其key设置过期时间 */
connection.expire(key.getBytes(), seconds);
if (acquire) {
return true;
}
return false;
});
}
/**
* 删除锁
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
}
3、案例
如果理解力强的朋友拿到这个方法就很快的能实现业务中的功能,我们这里给一个防止重复提交的实现案例。
防重复提交注解RepeatSubmitIntercept
/**
* 重复提交拦截注解
* @author dh
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmitIntercept {
/**
* 最大阻挡时间,默认5s
*/
long maxTime() default 5L;
/**
* 重复提交时返回msg
*/
String errorTitle() default "当前操作重复!";
/**
* 拦截方式:
* 1、如果为0:那么则根据当前用户拦截,那么当前方法该用户在上次请求完成前内只能访问一次.
* 2、如果为1:那么则根据当前指定参数名进行拦截,那么当前方法该用户同一参数在上次请求完成前只能访问一次.
*/
int type() default 0;
/**
* 拦截方式:
* 如果拦截方式为0,那么根据请求头来判断用户
*/
String userHead() default CacheConstants.AUTHORIZATION_HEADER;
/**
* 如果拦截方式为1时,指定的参数名称集合
* @return
*/
String []parameters() default {};
/**
* redis中key前缀,一般不需要修改此
*/
String redis_lock_prefix() default "super_bridal_repeat_submit_lock_prefix_";
/**
* 当该方法处于被拦截状态时,重复尝试次数,0则不尝试
* @return
*/
int rewaitCount() default 0;
}
aop
/**
* 防重复提交的注解
*
* @param point
* @return
* @throws Throwable
*/
@Around("@annotation(包名.........RepeatSubmitIntercept)")
public Object noRepeatSubmitAround(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ServletUtils.getRequest();
String uriStringBase64 = Base64.getEncoder().encodeToString(request.getRequestURI().getBytes());
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
RepeatSubmitIntercept repeatSubmitIntercept = method.getAnnotation(RepeatSubmitIntercept.class);
if (repeatSubmitIntercept.maxTime() < 1L) {
throw new RepeatSubmitInterceptException("重复提交拦截器报错--设置最大阻挡时间错误,至少大于1s", 500);
}
if (StringUtils.isBlank(repeatSubmitIntercept.errorTitle())) {
throw new RepeatSubmitInterceptException("重复提交拦截器报错--错误信息提醒请勿设置为空/空串", 500);
}
if (StringUtils.isBlank(repeatSubmitIntercept.redis_lock_prefix())) {
throw new RepeatSubmitInterceptException("重复提交拦截器报错--前缀Key不能为空/空串", 500);
}
String token = Convert.toStr(ServletUtils.getRequest().getHeader(repeatSubmitIntercept.userHead()));
StringBuilder key = new StringBuilder()
.append(repeatSubmitIntercept.redis_lock_prefix())
.append(token)
.append("/")
.append(uriStringBase64);
if (StringUtils.isEmpty(token)) {
throw new RepeatSubmitInterceptException("重复提交拦截器报错--当前拦截方式为[用户拦截],但其请求头中token为空!", 500);
}
/** 用户拦截的方式 */
if (repeatSubmitIntercept.type() == 0) {
/** 此处应该使用请求头中token作为key,那么此处不做其他操作. */
} else if (repeatSubmitIntercept.type() == 1) {
/** 从请求参数中获取key */
// ...................省略
} else {
throw new RepeatSubmitInterceptException("重复提交拦截器报错--当前拦截方式为未设置!", 500);
}
if (redisLockHelper.lock(key.toString(), repeatSubmitIntercept.maxTime())) {
return execute(key.toString(), point);
} else {
/**
* 1、判断允许重复等待
* 2、重复等待操作
* */
if (repeatSubmitIntercept.rewaitCount() > 0) {
int i = 0;
while (i < repeatSubmitIntercept.rewaitCount()) {
/** 暂停100ms再去拿 */
Thread.sleep(100);
i++;
if (redisLockHelper.lock(key.toString(), repeatSubmitIntercept.maxTime())) {
return execute(key.toString(), point);
}
}
}
}
throw new RepeatSubmitInterceptException(repeatSubmitIntercept.errorTitle(), 500);
}
注意这里的RepeatSubmitInterceptException是自定义的异常。
使用的地方
@GetMapping("/test1")
@RepeatSubmitIntercept()
public AjaxResult test1(){
System.out.println("进入了请求:" + System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return AjaxResult.success();
}
该实现中如有问题欢迎留言。
Java使用Redis实现分布式锁的更多相关文章
- Java使用Redis实现分布式锁来防止重复提交问题
如何用消息系统避免分布式事务? - 少年阿宾 - BlogJavahttp://www.blogjava.net/stevenjohn/archive/2018/01/04/433004.html [ ...
- java中redis的分布式锁工具类
使用方式 try { if(PublicLock.getLock(lockKey)){ //这里写代码逻辑,执行完后需要释放锁 PublicLock.freeLock(lockKey); } } ca ...
- Java基于redis实现分布式锁(SpringBoot)
前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...
- Java基于Redis的分布式锁
分布式锁,其实最终还是要保证锁(数据)的一致性,说到数据一致性,基于ZK,ETCD数据一致性中间件做分数是锁,才是王道.但是Redis也能满足最基本的需求. 参考: https://www.cnblo ...
- 基于redis的 分布式锁 Java实现
package com.hs.services.lock; import java.util.concurrent.TimeUnit; import javax.annotation.Resource ...
- Redis实现分布式锁的正确使用方式(java版本)
Redis实现分布式锁的正确使用方式(java版本) 本文使用第三方开源组件Jedis实现Redis客户端,且只考虑Redis服务端单机部署的场景. 分布式锁一般有三种实现方式: 1. 数据库乐观锁: ...
- (java 实现开箱即用基于 redis 的分布式锁
项目简介 lock 为 java 设计的分布式锁,开箱即用,纵享丝滑. 开源地址:https://github.com/houbb/lock 目的 开箱即用,支持注解式和过程式调用 基于 redis ...
- 用Redis构建分布式锁-RedLock(真分布)
在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...
- 用Redis实现分布式锁 与 实现任务队列(转)
这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说分享思路比分享代码更重要(貌似大概是这个意 ...
- Redis实现分布式锁
http://redis.io/topics/distlock 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但 ...
随机推荐
- notepad++ 编写html代码快捷键切换到浏览器查看
1.右键chrome属性,查看目标C:\Users\duanx\AppData\Local\Google\Chrome\Application\chrome.exe 2.然后notepad运行,输入如 ...
- 1.EditPlus
EditPlus软件使用 1.新建 XML 文件 2.编辑器设置 在文件保存时选择编码格式
- loj6851
(CF1761D Tester Solution in Chinese) 定义 \(L(v)=\log_2\operatorname{lowbit}(v+1)\):也就是说,\(L(v)\) 是 \( ...
- Python获取当前在线设备ip和mac地址
获取局域网所在的网段 with os.popen("ipconfig /all") as res: for line in res: line = line.strip() if ...
- JS篇(001)-document load 和 document ready 的区别
答案: 页面加载完成有两种事件 1.load是当页面所有资源全部加载完成后(包括DOM文档树,css文件,js文件,图片资源等),执行一个函数 问题:如果图片资源较多,加载时间较长,onload后等待 ...
- 替代学习物联网-云服务-03腾讯云MQTT
1.登录(利用微信) https://console.cloud.tencent.com/iothub 2.新建产品 3.添加设备 4.设备详细参数 域名IP固定: iotcloud-mqtt.gz. ...
- element的el-table合计显示不出来
在updated中写入 this.$nextTick(() => { this.$refs["printStatisticsTableData"].doLayout(); } ...
- iview 自定义指令实现Table左右横向拖拽
有时候表格内容会很多,需要横向滚动查看右边的内容,又不想到底部拖动滚动条,如果能直接在内容中拖动就好了,这个时候就可以用 vue 的自定义指令来实现了. 为了以后扩展指令方便,创建 directive ...
- jquery.easyui.min.js:12401 Uncaught TypeError: Cannot read property 'combo' of undefined jquery.easyui.min.js:12401
踩坑中成长! jquery1.4.1升级到1.4.3 点击添加报错. 一步步调试js,发现是combox使用问题. 前端报错,未声明,js是easyui的所以只有jsp和js用法问题.看了官方用法,瞬 ...
- Word技巧:ALT+X快捷键
Word技巧:ALT+X快捷键 在Word中输入数字,然后使用键盘快捷键「ALT + X」,即可快速生成一个文字图形. 部分数字的对比参照: 2564 ╤ 2582 ▂ 2600 2618 ☘ 256 ...