其实我们在前面已经演示过使用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单分片(无副本)集群没有问题,但是涉及到主从的话做分布式锁可能会出现问题,主要是由于主从同步可能出现问题。
最后按照惯例,附上本案例的源码,登录后即可下载。
















还没有评论,来说两句吧...