在互联网的很多场景下,会产生资源竞争,如果是单机环境,简单加个锁就能解决问题;但是在集群环境下(分布式环境),多个客户端在一个很短的时间内竞争同一服务端资源(如抢购场景),或者同一客户端重复提交请求,如果请求不具备幂等性,就需要用到分布式锁的解决方案。
关于分布式锁,可以看看我之前的文章《基于Spring boot 2.1 使用redisson实现分布式锁》,当时只是利用redisson的API实现了功能,现在,我们要利用自定义注解方式,让分布锁功能更方便使用。关于自定义注解,请移步《深入理解Java:注解(Annotation)自定义注解入门》
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </exclusion> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions></dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId></dependency> <!--spring2.X集成redis所需common-pool2,使用jedis必须依赖它 --><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId></dependency><dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.10.5</version></dependency>
public interface RedissonLocker { RLock lock(String lockKey); RLock lock(String lockKey, int timeout); RLock lock(String lockKey, TimeUnit unit, int timeout); boolean tryLock(String lockKey, TimeUnit unit, LockConstant lockTime); boolean fairLock(String lockKey, TimeUnit unit, LockConstant lockTime); void unlock(String lockKey); void unlock(RLock lock); }
/** * 基于Redisson实现分布式锁 * * @author * 2019年4月3日 * * {@link https://github.com/redisson/redisson/wiki} * */@Componentpublic class RedissonLockerImpl implements RedissonLocker { @Autowired private RedissonClient redissonClient; @Autowired private RedisTemplate<Object, Object> redisTemplate; /** * key 值是否存在 * * @param key * @return */ public boolean existKey(String key) { return redisTemplate.hasKey(key); } /************************** 可重入锁 **************************/ /** * 拿不到lock就不罢休,不然线程就一直block 没有超时时间,默认30s * * @param lockKey * @return */ @Override public RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } /** * 自己设置超时时间 * * @param lockKey 锁的key * @param timeout 秒 如果是-1,直到自己解锁,否则不会自动解锁 * @return */ @Override public RLock lock(String lockKey, int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, TimeUnit.SECONDS); return lock; } /** * 自己设置超时时间 * * @param lockKey 锁的key * @param unit 锁时间单位 * @param timeout 超时时间 * */ @Override public RLock lock(String lockKey, TimeUnit unit, int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } /** * 尝试加锁,最多等待waitTime,上锁以后leaseTime自动解锁 * * @param lockKey 锁key * @param unit 锁时间单位 * @param waitTime 等到最大时间,强制获取锁 * @param leaseTime 锁失效时间 * @return 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false */ @Override public boolean tryLock(String lockKey, TimeUnit unit, LockConstant lockTime) { RLock lock = redissonClient.getLock(lockKey); try { boolean existKey = existKey(lockKey); if (existKey) {// 已经存在了,就直接返回 return false; } return lock.tryLock(lockTime.getWaitTime(), lockTime.getLeaseTime(), unit); } catch (InterruptedException e) { e.printStackTrace(); } return false; } /************************** 公平锁 **************************/ /** * 尝试加锁,最多等待waitTime,上锁以后leaseTime自动解锁 * * @param lockKey 锁key * @param unit 锁时间单位 * @param waitTime 等到最大时间,强制获取锁 * @param leaseTime 锁失效时间 * @return 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false */ public boolean fairLock(String lockKey, TimeUnit unit, LockConstant lockTime) { RLock fairLock = redissonClient.getFairLock(lockKey); try { boolean existKey = existKey(lockKey); if (existKey) {// 已经存在了,就直接返回 return false; } return fairLock.tryLock(lockTime.getWaitTime(), lockTime.getLeaseTime(), unit); } catch (InterruptedException e) { e.printStackTrace(); } return false; } /** * 尝试加锁,最多等待waitTime,上锁以后leaseTime自动解锁 * * @param lockKey 锁key * @param unit 锁时间单位 * @param waitTime 等到最大时间,强制获取锁,默认是三秒钟 * @param leaseTime 锁失效时间 * @return 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false */ public boolean fairLock(String lockKey, TimeUnit unit, int leaseTime) { RLock fairLock = redissonClient.getFairLock(lockKey); try { boolean existKey = existKey(lockKey); if (existKey) {// 已经存在了,就直接返回 return false; } return fairLock.tryLock(3, leaseTime, unit); } catch (InterruptedException e) { e.printStackTrace(); } return false; } /** * 释放锁 * * @param lockKey 锁key */ @Override public void unlock(String lockKey) { try { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } catch (Exception e) { } } /** * 释放锁 */ @Override public void unlock(RLock lock) { try { lock.unlock(); } catch (Exception e) { } } }
/** * 防止重复提交的注解 * * @author * 2019年6月18日 * */@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })@Documentedpublic @interface RlockRepeatSubmit { /** * 分布式锁枚举类 * @return */ LockConstant lockConstant(); }
/** * 分布式锁枚举类 * */public enum LockConstant { CASHIER("cashierLock:", 3, 300, "请勿重复点击收银操作!!"), // 收银锁 SUBMIT_ORDER("submitOrderLock:", 3, 30, "请勿重复点击下单!!"), // 下单锁 ...... COMMON_LOCK("commonLock:", 3, 120, "请勿重复点击");// 通用锁常量 private String keyPrefix; // 分布式锁前缀 private int waitTime;// 等到最大时间,强制获取锁 private int leaseTime;// 锁失效时间 private String message;// 加锁提示 // 构造方法 private LockConstant(String keyPrefix, int waitTime, int leaseTime, String message) { this.keyPrefix = keyPrefix; this.waitTime = waitTime; this.leaseTime = leaseTime; this.message = message; } // 省略getter,setter }
/** * * 防止重复提交分布式锁拦截器 * * @author * 2019年6月18日 * */@Aspect@Componentpublic class RlockRepeatSubmitAspect { @Resource private RedissonLockerImpl redissonLocker; /*** * 定义controller切入点拦截规则,拦截RlockRepeatSubmit注解的业务方法 */ @Pointcut("@annotation(xx.xxx.RlockRepeatSubmit)") public void pointCut() { } /** * AOP分布式锁拦截 * * @param request * @param response * @param handler * @return * @throws Exception */ @Around("pointCut()") public Object rlockRepeatSubmit(ProceedingJoinPoint joinPoint) throws Throwable { // 获取类里面的方法 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method targetMethod = methodSignature.getMethod(); RlockRepeatSubmit repeatSubmit = targetMethod.getAnnotation(RlockRepeatSubmit.class); // 如果添加了RlockRepeatSubmit这个注解,需要添加分布式锁 if (Objects.nonNull(repeatSubmit)) { // 获取参数 Object[] args = joinPoint.getArgs(); // 进行一些参数的处理,比如获取订单号,操作人id等 ...... HttpServletRequest request = getRequest(); if (null != request && request.getParameterMap().containsKey("accessToken")) { StringBuffer lockKeyBuffer = new StringBuffer(); LockConstant lockConstant = repeatSubmit.lockConstant(); lockKeyBuffer.append(lockConstant.getKeyPrefix()); String accessToken = request.getParameterMap().get("accessToken")[0];// 当前用户的token String path = request.getServletPath(); // 使用用户的token和请求路径作为唯一的标识,如果有orderNo,加上orderNo作为唯一标识 lockKeyBuffer.append("RlockRepeat"); String token = accessToken + "." + path; if (null != orderNo) { lockKeyBuffer.append(token.hashCode()); lockKeyBuffer.append("."); lockKeyBuffer.append(orderNo); } else { lockKeyBuffer.append(token); } // 公平加锁,lockTime后锁自动释放 boolean isLocked = false; try { isLocked = redissonLocker.fairLock(lockKeyBuffer.toString(), TimeUnit.SECONDS, lockConstant); if (isLocked) { // 如果成功获取到锁就继续执行 // 执行进程 return joinPoint.proceed(); } else { // 未获取到锁 //异常处理 ...... } } catch (Exception e) { //异常处理 ...... } finally { if (isLocked) { // 如果锁还存在,在方法执行完成后,释放锁 redissonLocker.unlock(lockKeyBuffer.toString()); } } } } return joinPoint.proceed(); } public static HttpServletRequest getRequest() { ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return ra.getRequest(); } }
@RestController@Slf4j@RequestMapping("/order")public class OrderController { /** * 业务方法 * * @param requestForm * @return * @throws Throwable */ @RlockRepeatSubmit(lockConstant = LockConstant.SUBMIT_ORDER) @RequestMapping(value = "/xxx", method = RequestMethod.POST) public String submitOrder(Order order) throws Throwable { //业务方法 ...... } }
OK,大功告成,以后如果哪个业务方法需要加分布式锁,直接在方法上加上自定义注解,并在枚举里定义相关属性即可,是不是很简单?
PS:关于怎么利用JMeter模拟并发请求测试业务方法和分布式锁,下次再讲。
原网址: 访问
创建于: 2021-03-31 17:19:28
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论