当前位置:   article > 正文

【接口防重复提交】⭐️基于RedisLockRegistry 分布式锁管理器实现

【接口防重复提交】⭐️基于RedisLockRegistry 分布式锁管理器实现

目录

前言

思路

实现方式

实践

        1.引入相关依赖

        2.aop注解

        3.切面类代码

        4.由于启动时报错找不到对应的RedisLockRegistry bean,选择通过配置类手动注入,配置类代码如下

 测试

 章末


前言

        项目中有个用户根据二维码绑定身份的接口,由于用户在操作时,可能会因为网络延迟或者其他原因多次点击提交按钮,导致重复提交相同的请求,所以需要在一定时间内限制同一个用户相同操作的重复提交,避免重复绑定的情况发生

思路

        通过Spring 的aop 功能加上分布式锁实现,aop功能可以实现切面操作有关接口,再通过分布式锁实现同一个请求在一段时间内只执行一次,保证操作的幂等性,避免数据异常

实现方式

        分布式锁选择的是 RedisLockRegistry,下面是该锁的简单介绍

RedisLockRegistry 是 Spring Integration 提供的一个基于 Redis 实现的分布式锁实用程序。可以用于在分布式环境中实现对共享资源的互斥访问。

RedisLockRegistry 使用 Redis 的原子性操作和过期时间设置来实现分布式锁。通过在 Redis 中创建一个特定的键(key),并在获取锁时将该键设置为具有过期时间的值(value)。其他线程或进程通过尝试在同一键上执行相同操作,如果能够设置成功,则表示获取到了锁,可以执行操作;否则,表示锁被其他线程或进程占用,需要等待。

实践

        1.引入相关依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-integration</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.integration</groupId>
  7. <artifactId>spring-integration-redis</artifactId>
  8. </dependency>

        2.aop注解

        这里有两个方法,一个是提供获取锁可重试时常,另一个是获得指定的key

  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5. /** 防止重复提交,通过分布式锁,限制同一个api接口并发时多次重复提交
  6. * @author ben.huang
  7. */
  8. @Target({ElementType.TYPE,ElementType.METHOD})
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @interface AntiReplay {
  11. /**
  12. * 获取锁重试时间,默认0ms,也就是不许重试,加锁失败,立即返回
  13. * 产生竞争时,重试获取锁的最长等待时间,在改时间内如果没有获取到锁,则失败
  14. * @return
  15. */
  16. int tryLockTime() default 0;
  17. /**
  18. * 自定义的Key,不填的话默认“”,代码中可以自定义拼接
  19. * 需要自己提供默认的话,在注解使用时赋值即可
  20. * @return
  21. */
  22. String key() default "";
  23. }

        3.切面类代码

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.Around;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Pointcut;
  6. import org.springframework.integration.redis.util.RedisLockRegistry;
  7. import org.springframework.stereotype.Component;
  8. import javax.annotation.Resource;
  9. import java.util.concurrent.TimeUnit;
  10. import java.util.concurrent.locks.Lock;
  11. /**
  12. * @author ben.huang
  13. */
  14. @Component
  15. @Aspect
  16. @Slf4j
  17. public class AntiReplayAspect {
  18. @Resource
  19. private RedisLockRegistry redisLockRegistry;
  20. @Pointcut("@annotation(antiReplay)")
  21. public void pointcut(AntiReplay antiReplay){
  22. }
  23. @Around(value = "pointcut(antiReplay)")
  24. public Object around(ProceedingJoinPoint proceedingJoinPoint,AntiReplay antiReplay) throws Throwable{
  25. int tryLockTime = antiReplay.tryLockTime();
  26. Object result = null;
  27. String name = "testRedisLock-";
  28. String path = antiReplay.key();
  29. //这里简化了,使用时可以使用用户唯一辨识(比如用户id)拼接key
  30. String key = name + path;
  31. Lock lock = redisLockRegistry.obtain(key);
  32. boolean isSuccess = lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS);
  33. if(isSuccess){
  34. log.info("获取锁 key = [{}]",key);
  35. try{
  36. result = proceedingJoinPoint.proceed();
  37. }
  38. finally{
  39. if(isSuccess){
  40. lock.unlock();
  41. log.info("释放锁 success, key = [{}]",key);
  42. }
  43. }
  44. }
  45. else{
  46. log.info("获取锁失败 fial ,key = [{}]",key);
  47. throw new Exception("error");
  48. }
  49. return result;
  50. }
  51. }

        4.由于启动时报错找不到对应的RedisLockRegistry bean,选择通过配置类手动注入,配置类代码如下

Description: A component required a bean of type 'org.springframework.integration.redis.util.RedisLockRegistry' that could not be found.

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.data.redis.connection.RedisConnectionFactory;
  4. import org.springframework.integration.redis.util.RedisLockRegistry;
  5. /**
  6. * @author ben.huang
  7. */
  8. @Configuration
  9. public class AntiReplayConfig {
  10. @Bean
  11. public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory){
  12. RedisLockRegistry redisLockRegistry = new RedisLockRegistry(redisConnectionFactory, "my-lock-key");
  13. return redisLockRegistry;
  14. }
  15. }

 测试

        1.因为该场景是在并发时发生的,所以可以选择压测的方式模拟下并发场景,创建一个简单的测试接口,登录成功则在控制台打印信息,否则抛出异常

  1. @AntiReplay(key = "userLogin")
  2. @PostMapping(value = "/login")
  3. public BaseResult login(String username, String password) {
  4. UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username));
  5. if(user==null || !user.getPassword().equals(password)) {
  6. throw new RuntimeException("error while logining");
  7. }
  8. System.out.println(" login success!");
  9. return BaseResult.success();
  10. }

         2.压测工具使用的是APIpost接口测试工具,不加防重复注解时启动项目调用接口的结果如下

可以看到在没有加锁的情况下,所有请求全部成功

        3.加上注解后再次压测,结果如下 ,可以看到大部分请求都失败了,为什么还有这么多请求成功的?是因为获取到锁的线程会释放锁,后面的线程还可以接着抢,看控制台也会发现有很多次释放锁记录

 章末

        文章到这里就结束了~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/288877
推荐阅读
相关标签
  

闽ICP备14008679号