springboot2本地锁实践一文中提到用Guava Cache实现锁机制,但在集群中就行不通了,所以我们还一般要借助类似Redis、ZooKeeper 之类的中间件实现分布式锁,下面我们将利用自定义注解Spring AopRedis Cache 实现分布式锁。

项目代码结构整体图

一、导入依赖

在 pom.xml 中添加上 starter-webstarter-aopstarter-data-redis 的依赖

 <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>

二、属性配置

在 application.properites 资源文件中添加 redis 相关的配置项

spring.redis.host=192.168.68.110
spring.redis.port=6379
spring.redis.password=123456

三、注解

1、创建一个 CacheLock 注解,属性配置如下

  • prefix: 缓存中 key 的前缀
  • expire: 过期时间,此处默认为 5 秒
  • timeUnit: 超时单位,此处默认为秒
  • delimiter: key 的分隔符,将不同参数值分割开来
 import java.lang.annotation.*;
import java.util.concurrent.TimeUnit; /**
* 锁的注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheLock { /**
* redis 锁key的前缀
*
* @return redis 锁key的前缀
*/
String prefix() default ""; /**
* 过期秒数,默认为5秒
*
* @return 轮询锁的时间
*/
int expire() default 5; /**
* 超时时间单位
*
* @return 秒
*/
TimeUnit timeUnit() default TimeUnit.SECONDS; /**
* <p>Key的分隔符(默认 :)</p>
* <p>生成的Key:N:SO1008:500</p>
*
* @return String
*/
String delimiter() default ":";
}

2、 key 的生成规则是自己定义的,如果通过表达式语法自己得去写解析规则还是比较麻烦的,所以依旧是用注解的方式

 import java.lang.annotation.*;

 /**
* 锁的参数
*
*/
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParam { /**
* 字段名称
*
* @return String
*/
String name() default "";
}

四、Key生成策略

1、接口

 import org.aspectj.lang.ProceedingJoinPoint;

 /**
* key生成器
*/
public interface CacheKeyGenerator { /**
* 获取AOP参数,生成指定缓存Key
*
* @param pjp PJP
* @return 缓存KEY
*/
String getLockKey(ProceedingJoinPoint pjp);
}

2、接口实现

import com.carry.annotation.CacheLock;
import com.carry.annotation.CacheParam;
import com.carry.common.CacheKeyGenerator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import java.lang.annotation.Annotation;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import java.lang.reflect.Parameter; /**
  • 通过接口注入的方式去写不同的生成规则;

    */

    public class LockKeyGenerator implements CacheKeyGenerator { @Override

    public String getLockKey(ProceedingJoinPoint pjp) {

    MethodSignature signature = (MethodSignature) pjp.getSignature();

    Method method = signature.getMethod();

    CacheLock lockAnnotation = method.getAnnotation(CacheLock.class);

    final Object[] args = pjp.getArgs();

    final Parameter[] parameters = method.getParameters();

    StringBuilder builder = new StringBuilder();

    //默认解析方法里面带 CacheParam 注解的属性,如果没有尝试着解析实体对象中的

    for (int i = 0; i < parameters.length; i++) {

    final CacheParam annotation = parameters[i].getAnnotation(CacheParam.class);

    if (annotation == null) {

    continue;

    }

    builder.append(lockAnnotation.delimiter()).append(args[i]);

    }

    if (StringUtils.isEmpty(builder.toString())) {

    final Annotation[][] parameterAnnotations = method.getParameterAnnotations();

    for (int i = 0; i < parameterAnnotations.length; i++) {

    final Object object = args[i];

    final Field[] fields = object.getClass().getDeclaredFields();

    for (Field field : fields) {

    final CacheParam annotation = field.getAnnotation(CacheParam.class);

    if (annotation == null) {

    continue;

    }

    field.setAccessible(true);

    builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object));

    }

    }

    }

    return lockAnnotation.prefix() + builder.toString();

    }

    }

五、Lock拦截器(AOP)

熟悉 Redis 的朋友都知道它是线程安全的,我们利用它的特性可以很轻松的实现一个分布式锁,如 opsForValue().setIfAbsent(key,value)它的作用就是如果缓存中没有当前 Key 则进行缓存同时返回 true 反之亦然;当缓存后给 key 在设置个过期时间,防止因为系统崩溃而导致锁迟迟不释放形成死锁; 那么我们是不是可以这样认为当返回 true 我们认为它获取到锁了,在锁未释放的时候我们进行异常的抛出….

 import com.carry.annotation.CacheLock;
import com.carry.common.CacheKeyGenerator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils; import java.lang.reflect.Method; /**
* redis 方案
*/
@Aspect
@Configuration
public class LockMethodInterceptor { @Autowired
public LockMethodInterceptor(StringRedisTemplate lockRedisTemplate, CacheKeyGenerator cacheKeyGenerator) {
this.lockRedisTemplate = lockRedisTemplate;
this.cacheKeyGenerator = cacheKeyGenerator;
} private final StringRedisTemplate lockRedisTemplate;
private final CacheKeyGenerator cacheKeyGenerator; @Around("execution(public * *(..)) && @annotation(com.carry.annotation.CacheLock)")
public Object interceptor(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
CacheLock lock = method.getAnnotation(CacheLock.class);
if (StringUtils.isEmpty(lock.prefix())) {
throw new RuntimeException("lock key can't be null...");
}
final String lockKey = cacheKeyGenerator.getLockKey(pjp);
try {
//key不存在才能设置成功
final Boolean success = lockRedisTemplate.opsForValue().setIfAbsent(lockKey, "");
if (success) {
lockRedisTemplate.expire(lockKey, lock.expire(), lock.timeUnit());
} else {
//按理来说 我们应该抛出一个自定义的 CacheLockException 异常;
throw new RuntimeException("请勿重复请求");
}
try {
return pjp.proceed();
} catch (Throwable throwable) {
throw new RuntimeException("系统异常");
}
} finally {
//如果演示的话需要注释该代码;实际应该放开
// lockRedisTemplate.delete(lockKey);
}
}
}

六、控制层

在接口方法上添加 @CacheLock(prefix = "test"),然后动态的值可以加上@CacheParam;生成后的新 key 将被缓存起来;(如:该接口 token = 1,那么最终的 key 值为 test:1,如果多个条件则依次类推)

 import com.carry.annotation.CacheLock;
import com.carry.annotation.CacheParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; @RestController
public class LockController { @CacheLock(prefix = "test")
@GetMapping("/test")
public String query(@CacheParam(name = "token") @RequestParam String token) {
return "success - " + token;
} }

七、主函数

需要注入前面定义好的 CacheKeyGenerator 接口具体实现…

 import com.carry.common.CacheKeyGenerator;
import com.carry.common.LockKeyGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean; @SpringBootApplication
public class SpringbootCacheRedislockApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootCacheRedislockApplication.class, args);
} @Bean
public CacheKeyGenerator cacheKeyGenerator() {
return new LockKeyGenerator();
}
}

八、测试

启动项目,在postman中输入url:localhost:8080/test?token=1

第一次请求结果:

第二次请求结果:

等key过期了请求又恢复正常。

Springboot分布式锁实践(redis)的更多相关文章

  1. SpringBoot集成Redis分布式锁以及Redis缓存

    https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...

  2. 解析分布式锁之Redis实现(二)

    摘要:在前文中提及了实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,本文主要阐述基于Redis的分布式锁,分布式架构设计如今在企业中被大量的应用,而在不同的分布 ...

  3. 分布式锁tair redis zookeeper,安全性

    tair分布式锁实现:https://yq.aliyun.com/articles/58928 redis分布式锁:https://www.cnblogs.com/jianwei-dai/p/6137 ...

  4. 从分布式锁来看redis和zookpeer!

    从分布式锁来看redis和zookpeer! 目前网上大部分的基于zookpeer,和redis的分布式锁的文章都不够全面.要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫.坦白说, ...

  5. Java分布式:分布式锁之Redis实现

    Java分布式:分布式锁之Redis实现 分布式锁系列教程重点分享锁实现原理 Redis锁原理 核心命令 Redis分布式锁的原理是基于其SETNX命令,我们来看SETNX的解释. 实现过程 使用SE ...

  6. [Java复习] 分布式锁 Zookeeper Redis

    一般实现分布式锁都有哪些方式? 使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗? 这两种分布式锁的实现方式哪种效率比较高? 1. Zookeeper 都有哪些使用场 ...

  7. 分布式锁用Redis还是ZooKeeper?(转载)

    文章系网络转载,侵删. 来源:https://zhuanlan.zhihu.com/p/73807097 为什么用分布式锁?在讨论这个问题之前,我们先来看一个业务场景. 图片来自 Pexels 为什么 ...

  8. 分布式锁(redis/mysql)

    单台机器所能承载的量是有限的,用户的量级上万,基本上服务都会做分布式集群部署.很多时候,会遇到对同一资源的方法.这时候就需要锁,如果是单机版的,可以利用java等语言自带的并发同步处理.如果是多台机器 ...

  9. 【分布式锁】Redis实现可重入的分布式锁

    一.前言 之前写的一篇文章<细说分布式锁>介绍了分布式锁的三种实现方式,但是Redis实现分布式锁关于Lua脚本实现.自定义分布式锁注解以及需要注意的问题都没描述.本文就是详细说明如何利用 ...

随机推荐

  1. 有一种蓝叫 APEC 蓝

    有如是解释 APEC 者--Air Pollution Eventually Controlled. 有说此次是继零八后的重新万国来朝.丝路大略明白了,西域必通. 站在历史的远处回眸,这是继零八年后重 ...

  2. [ Javascript ] 内存泄露以及循环引用解析

    内存泄露 在javascript中,我们非常少去关注内存的管理. 我们创建变量,使用变量,浏览器关注这些底层的细节都显得非常正常. 可是当应用程序变得越来越复杂而且ajax化之后,或者用户在一个页面停 ...

  3. Running the app on your device

    So far, you've run the app on the Simulator. That's nice and all but probably notwhy you're learning ...

  4. Linux中的默认权限与隐藏权限(文件、文件夹)

    一个文件(或文件夹)拥有若干个属性.包含(r/w/x)等基本属性,以及是否为文件夹(d)与文件(-)或连接文件(l)等属性.此外,Linux还能够设置其它系统安全属性.使用chattr来设置.以lsa ...

  5. luogu3197 [HNOI2008] 越狱

    题目大意 已知序列$P$满足$|P|=N$,(以下所有$i,i\in[1,N]$)$\forall i, P_i\in [1,M]$.求$|\{P|\exists i, P_i =P_{i+1}\}| ...

  6. 关于ShapeDrawable应用的一些介绍(上)

    在Android中, 很多时候系统原生的控件的格式并不能满足我们的需求,我们想要更加好看点的样式,像什么圆角矩形啊,颜色渐变啊,阴影效果啊等等的,这个时候就是我们的 ShapeDrawable发挥效果 ...

  7. 现代英特尔® 架构上的 TensorFlow* 优化——正如去年参加Intel AI会议一样,Intel自己提供了对接自己AI CPU优化版本的Tensorflow,下载链接见后,同时可以基于谷歌官方的tf版本直接编译生成安装包

    现代英特尔® 架构上的 TensorFlow* 优化 转自:https://software.intel.com/zh-cn/articles/tensorflow-optimizations-on- ...

  8. JS——BOM操作(点击按钮返回顶部案例:scrollTop的用法)

    点击按钮返回顶部案例: 代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta chars ...

  9. GObject调用父类函数

    最近在分析Gstreamer的代码时,发现GstPipeline中有如下代码: result = GST_ELEMENT_CLASS (parent_class)->change_state ( ...

  10. BZOJ 2794 DP

    思路: 考虑把询问离线 按照m排序 物品按照a排序 f[i]表示c[j]的和到i b的最大值 背包就好 O(nk)竟然能过-- //By SiriusRen #include <cstdio&g ...