赞
踩
目录
4.由于启动时报错找不到对应的RedisLockRegistry bean,选择通过配置类手动注入,配置类代码如下
项目中有个用户根据二维码绑定身份的接口,由于用户在操作时,可能会因为网络延迟或者其他原因多次点击提交按钮,导致重复提交相同的请求,所以需要在一定时间内限制同一个用户相同操作的重复提交,避免重复绑定的情况发生
通过Spring 的aop 功能加上分布式锁实现,aop功能可以实现切面操作有关接口,再通过分布式锁实现同一个请求在一段时间内只执行一次,保证操作的幂等性,避免数据异常
分布式锁选择的是 RedisLockRegistry,下面是该锁的简单介绍
RedisLockRegistry
是 Spring Integration 提供的一个基于 Redis 实现的分布式锁实用程序。可以用于在分布式环境中实现对共享资源的互斥访问。
RedisLockRegistry
使用 Redis 的原子性操作和过期时间设置来实现分布式锁。通过在 Redis 中创建一个特定的键(key),并在获取锁时将该键设置为具有过期时间的值(value)。其他线程或进程通过尝试在同一键上执行相同操作,如果能够设置成功,则表示获取到了锁,可以执行操作;否则,表示锁被其他线程或进程占用,需要等待。
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-integration</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.integration</groupId>
- <artifactId>spring-integration-redis</artifactId>
- </dependency>
这里有两个方法,一个是提供获取锁可重试时常,另一个是获得指定的key
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- /** 防止重复提交,通过分布式锁,限制同一个api接口并发时多次重复提交
- * @author ben.huang
- */
- @Target({ElementType.TYPE,ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface AntiReplay {
-
- /**
- * 获取锁重试时间,默认0ms,也就是不许重试,加锁失败,立即返回
- * 产生竞争时,重试获取锁的最长等待时间,在改时间内如果没有获取到锁,则失败
- * @return
- */
- int tryLockTime() default 0;
-
-
- /**
- * 自定义的Key,不填的话默认“”,代码中可以自定义拼接
- * 需要自己提供默认的话,在注解使用时赋值即可
- * @return
- */
- String key() default "";
- }
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.integration.redis.util.RedisLockRegistry;
- import org.springframework.stereotype.Component;
-
- import javax.annotation.Resource;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Lock;
-
- /**
- * @author ben.huang
- */
- @Component
- @Aspect
- @Slf4j
- public class AntiReplayAspect {
- @Resource
- private RedisLockRegistry redisLockRegistry;
-
- @Pointcut("@annotation(antiReplay)")
- public void pointcut(AntiReplay antiReplay){
-
- }
-
- @Around(value = "pointcut(antiReplay)")
- public Object around(ProceedingJoinPoint proceedingJoinPoint,AntiReplay antiReplay) throws Throwable{
- int tryLockTime = antiReplay.tryLockTime();
- Object result = null;
- String name = "testRedisLock-";
- String path = antiReplay.key();
- //这里简化了,使用时可以使用用户唯一辨识(比如用户id)拼接key
- String key = name + path;
- Lock lock = redisLockRegistry.obtain(key);
- boolean isSuccess = lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS);
- if(isSuccess){
- log.info("获取锁 key = [{}]",key);
- try{
- result = proceedingJoinPoint.proceed();
- }
- finally{
- if(isSuccess){
- lock.unlock();
- log.info("释放锁 success, key = [{}]",key);
- }
- }
- }
- else{
- log.info("获取锁失败 fial ,key = [{}]",key);
- throw new Exception("error");
- }
- return result;
- }
- }
Description: A component required a bean of type 'org.springframework.integration.redis.util.RedisLockRegistry' that could not be found.
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.data.redis.connection.RedisConnectionFactory;
- import org.springframework.integration.redis.util.RedisLockRegistry;
-
- /**
- * @author ben.huang
- */
- @Configuration
- public class AntiReplayConfig {
-
- @Bean
- public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory){
- RedisLockRegistry redisLockRegistry = new RedisLockRegistry(redisConnectionFactory, "my-lock-key");
- return redisLockRegistry;
-
- }
-
- }
1.因为该场景是在并发时发生的,所以可以选择压测的方式模拟下并发场景,创建一个简单的测试接口,登录成功则在控制台打印信息,否则抛出异常
- @AntiReplay(key = "userLogin")
- @PostMapping(value = "/login")
- public BaseResult login(String username, String password) {
- UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username));
- if(user==null || !user.getPassword().equals(password)) {
- throw new RuntimeException("error while logining");
- }
- System.out.println(" login success!");
- return BaseResult.success();
- }
2.压测工具使用的是APIpost接口测试工具,不加防重复注解时启动项目调用接口的结果如下
可以看到在没有加锁的情况下,所有请求全部成功
3.加上注解后再次压测,结果如下 ,可以看到大部分请求都失败了,为什么还有这么多请求成功的?是因为获取到锁的线程会释放锁,后面的线程还可以接着抢,看控制台也会发现有很多次释放锁记录
文章到这里就结束了~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。