在互联网的很多场景下,会产生资源竞争,如果是单机环境,简单加个锁就能解决问题;但是在集群环境下(分布式环境),多个客户端在一个很短的时间内竞争同一服务端资源(如抢购场景),或者同一客户端重复提交请求,如果请求不具备幂等性,就需要用到分布式锁的解决方案。
关于分布式锁,可以看看我之前的文章《基于Spring boot 2.1 使用redisson实现分布式锁》,当时只是利用redisson的API实现了功能,现在,我们要利用自定义注解方式,让分布锁功能更方便使用。关于自定义注解,请移步《深入理解Java:注解(Annotation)自定义注解入门》
org.springframework.boot
spring-boot-starter-data-redis
redis.clients
jedis
io.lettuce
lettuce-core
redis.clients
jedis
org.apache.commons
commons-pool2
org.redisson
redisson
3.10.5
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}
*
*/
@Component
public class RedissonLockerImpl implements RedissonLocker {
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisTemplate
/**
* 防止重复提交的注解
*
* @author
* 2019年6月18日
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Documented
public @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
@Component
public 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模拟并发请求测试业务方法和分布式锁,下次再讲。