其实我们在前面已经演示过使用redisson来实现分布式锁了,详情见:《Spring Cloud微服务项目模板系列(十)Redisson分布式锁》。由于最近在编写秒杀的演示项目,里面也会使用到分布式锁,这里我们想着使用简单一点,因此这里我们使用注解的方式实现一个分布式锁的案例,这样子使用起来比较简单。
利用redisson来实现分布式锁的时候,其实主要有几个核心的类,分别是:
声明注解 实现注解逻辑 redisson分布式锁
所以基于这几个主类,我们来演示下代码:
一、声明一个注解
这里我们声明注解的时候比较简单,示例代码如下:
package com.redisson.aop; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface RedisDistributedLock { String key() default ""; int leaseTime() default 10; boolean autoRelease() default true; String errorDesc() default "系统正在处理,请稍后重试"; int waitTime() default 1; }
二、实现注解逻辑
接着我们就来实现注解的逻辑,这个逻辑比较简单,就是我们日常使用redisson加锁解锁的逻辑,示例代码如下:
package com.redisson.aop; import java.lang.reflect.Method; 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.core.DefaultParameterNameDiscoverer; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.redisson.redis.RedissonLock; import lombok.extern.slf4j.Slf4j; @Aspect @Component @Slf4j public class RedisDistributedLockHandler { @Autowired RedissonLock redissonLock; public RedisDistributedLockHandler() { } @Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, RedisDistributedLock distributedLock) throws Throwable { String lockName = this.getRedisKey(joinPoint, distributedLock); int leaseTime = distributedLock.leaseTime(); String errorDesc = distributedLock.errorDesc(); int waitTime = distributedLock.waitTime(); Object var8; try { boolean lock = this.redissonLock.tryLock(lockName, (long)leaseTime, (long)waitTime); if (!lock) { throw new RuntimeException(errorDesc); } var8 = joinPoint.proceed(); } catch (Throwable var12) { log.error("执行业务方法异常", var12); throw var12; } finally { this.redissonLock.isHeldByCurrentThread(lockName); } return var8; } /** * 获取加锁的key * @param joinPoint * @param distributedLock * @return */ private String getRedisKey(ProceedingJoinPoint joinPoint, RedisDistributedLock distributedLock) { String key = distributedLock.key(); Object[] parameterValues = joinPoint.getArgs(); MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Method method = signature.getMethod(); DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = nameDiscoverer.getParameterNames(method); if (StringUtils.isEmpty(key)) { if (parameterNames != null && parameterNames.length > 0) { StringBuffer sb = new StringBuffer(); int i = 0; for(int len = parameterNames.length; i < len; ++i) { sb.append(parameterNames[i]).append(" = ").append(parameterValues[i]); } key = sb.toString(); } else { key = "redissionLock"; } return key; } else { SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(key); if (parameterNames != null && parameterNames.length != 0) { StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); for(int i = 0; i < parameterNames.length; ++i) { evaluationContext.setVariable(parameterNames[i], parameterValues[i]); } try { Object expressionValue = expression.getValue(evaluationContext); return expressionValue != null && !"".equals(expressionValue.toString()) ? expressionValue.toString() : key; } catch (Exception var13) { return key; } } else { return key; } } } }
三、实现redisson的锁
最后就是redisson工具类加锁,示例代码如下:
package com.redisson.redis; import java.util.concurrent.TimeUnit; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Component @Slf4j public class RedissonLock { @Autowired private RedissonClient redissonClient; public boolean tryLock(String lockName, long leaseTime, long waitTime) { try { RLock rLock = redissonClient.getLock(lockName); boolean isLockSuccess = rLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); log.info("{} get lock result:{}", lockName, isLockSuccess); return isLockSuccess; } catch (Exception e) { log.error(e.getMessage(),e); } return false; } /** * 释放锁 * * @param lockName */ public void isHeldByCurrentThread(String lockName) { RLock lock = redissonClient.getLock(lockName); if (lock.isLocked()) { // 是否还是锁定状态 if (lock.isHeldByCurrentThread()) { // 时候是当前执行线程的锁 lock.unlock(); // 释放锁 } } } }
最后我们在使用的时候,直接添加上对应的@RedisDistributedLock注解即可,示例代码如下:
@PostMapping("/reduceStock") @RedisDistributedLock(key = "'lock_good_'+#request.goodId", errorDesc = "当前请求频繁,请稍后重试") public String reduceStock(@RequestBody GoodRequest request) { Integer stock = goodDao.getGoodStock(request.getGoodId()); if (stock > 0) { goodDao.reduceGoodStock(request.getGoodId()); } return "成功"; }
到这里我们的代码就编写完了,接着我们把项目打包放到服务器上去:
然后我们使用nginx来做负载均衡:
备注:
1、这里我们部署多个,主要是根据实际的环境来进行并发测试,验证并发测试下这个分布式锁有没有问题。
接着我们使用apipost进行并发测试,来看看效果,请求示例如下:
我们在这里进行一键压测
首先我们把数据库的库存设置为10
这里我们的并发数设置为100,压测10轮次,看看最后这个库存会不会存在超卖:
然后再看看数据库的库存:
可以看到库存为0了,也就是差不多1000个请求,成功买到的只有10个人。说明咱们这个分布式锁的编写是没问题的。
备注:
1、在并发测试里面成功请求数比较多,这是因为我们如果没抢到的话,返回的http statu code还是200,apipost会认为是成功的。 2、如果大家编写多线程代码,在日志中输出返回结果就可以看到提示库存不足或者其他的提示了。 3、这里失败请求数有一些,是由于当前服务器的配置较低,CPU撑满了,导致请求直接错误了。 4、在实际的环境中如果使用redis做分布式锁的载体的时候要注意下:使用redis单机是没问题的,使用redis单分片(无副本)集群没有问题,但是涉及到主从的话做分布式锁可能会出现问题,主要是由于主从同步可能出现问题。
最后按照惯例,附上本案例的源码,登录后即可下载。
还没有评论,来说两句吧...