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实现分布式锁的更多相关文章

  1. Java使用Redis实现分布式锁来防止重复提交问题

    如何用消息系统避免分布式事务? - 少年阿宾 - BlogJavahttp://www.blogjava.net/stevenjohn/archive/2018/01/04/433004.html [ ...

  2. java中redis的分布式锁工具类

    使用方式 try { if(PublicLock.getLock(lockKey)){ //这里写代码逻辑,执行完后需要释放锁 PublicLock.freeLock(lockKey); } } ca ...

  3. Java基于redis实现分布式锁(SpringBoot)

    前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...

  4. Java基于Redis的分布式锁

    分布式锁,其实最终还是要保证锁(数据)的一致性,说到数据一致性,基于ZK,ETCD数据一致性中间件做分数是锁,才是王道.但是Redis也能满足最基本的需求. 参考: https://www.cnblo ...

  5. 基于redis的 分布式锁 Java实现

    package com.hs.services.lock; import java.util.concurrent.TimeUnit; import javax.annotation.Resource ...

  6. Redis实现分布式锁的正确使用方式(java版本)

    Redis实现分布式锁的正确使用方式(java版本) 本文使用第三方开源组件Jedis实现Redis客户端,且只考虑Redis服务端单机部署的场景. 分布式锁一般有三种实现方式: 1. 数据库乐观锁: ...

  7. (java 实现开箱即用基于 redis 的分布式锁

    项目简介 lock 为 java 设计的分布式锁,开箱即用,纵享丝滑. 开源地址:https://github.com/houbb/lock 目的 开箱即用,支持注解式和过程式调用 基于 redis ...

  8. 用Redis构建分布式锁-RedLock(真分布)

    在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...

  9. 用Redis实现分布式锁 与 实现任务队列(转)

    这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说分享思路比分享代码更重要(貌似大概是这个意 ...

  10. Redis实现分布式锁

    http://redis.io/topics/distlock 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但 ...

随机推荐

  1. mysql数据增量及备份操作

    注:当数据库出现异常的时候,我们可以先恢复最近一次的全量备份,接着将增量备份的文件一个一个按顺序恢复即可实现原来数据库的恢复. 还可以使用innobackupex 备份工具. 备份 # 1,开启 bi ...

  2. 常用软件版本记录 lisoaring

    操作系统 windows XP(2014年4月8日) windows 7(2020年1月14日终止支持) wiindows 10 Internet Explorer 1995-20220507 Fla ...

  3. vue3.0使用富文本编辑器VueQuill

    1. npm install @vueup/vue-quill@alpha --save 2. 在main.js中全局引入 import { QuillEditor } from '@vueup/vu ...

  4. 89、des加密算法

    import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpe ...

  5. drf从入门到飞升仙界 09

    接口文档 # 1.前后端分离 - 后端:写接口 - 前端:根据接口写app,小程序,pc端 # 2.作为后端开发 - 我们应该清楚: ---> /api/v1/login/ ---> 登录 ...

  6. JS学习-给Canvas上下文设置样式

    给Canvas上下文设置样式 <canvas class="myCanvas" width="700" height="500"> ...

  7. (linux笔记)开放防火墙端口

    关闭防火墙 CentOS 7.RedHat 7 之前的 Linux 发行版防火墙开启和关闭( iptables ): 即时生效,重启失效 #开启 service iptables start #关闭 ...

  8. linux搭建FastDFS文件服务器,安装nginx

    本文主要介绍在linux服务器如何搭建FastDFS文件服务器.大概分为9个步骤,由于内容较为繁琐.下面带你入坑! 首先简单介绍一下FastDFS是淘宝资深架构师余庆老师主导开源的一个分布式文件系统, ...

  9. 完整的javaweb文档

    1.index.jsp <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8&qu ...

  10. Pytest之参数化

    在unittest测试中,有参数化的概念,那么在pytest中也有. 如何理解参数化: 当对一个测试函数进行测试时,通常会给函数传递多组参数.比如测试账号登陆,我们需要模拟各种千奇百怪的账号密码.可以 ...