最近项目中用到比较多的redis分布式锁
每个方法都类似于这样
String key = "";
//尝试加锁
if (! jedisManager.tryLock(key)) {
throw new BizException("请稍后重试");
}
try {
//do your biz
}
catch (Exception e) {
throw e;
}
finally {
//释放锁
jedisManager.release(key);
}
非常的麻烦,而且每个人有每个人的写法。所以,决定将分布式锁与业务进行分离,便于我们以后后续开发
我们需要定义一个分布式锁注解(RedisLock),分布式锁aop,分布式锁对象基类(LockDomian)
RedisLock
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RedisLock {
RedisLocKeyEnum bizKey();
/**
* 默认毫秒
* @return
*/
int expire() default 15000;
/**
* 默认给前端的提示
* @return
*/
String errorMsg() default "请稍后重试";
}
LockDomian
@Slf4j
public class LockDomain {
public static String KEY = "model = [%s], logKey = [%s] : [%s]";
public String redisKey() {
throw new BizException("请重写你的分布式锁对象的redisKey方法");
}
/**
* 建议继承的类重写这个方法 方便日志查找
* @return
*/
public String logKey() {
return String.valueOf(this.hashCode());
}
public void tryLockSucLog(MutexModelEnum model) {
log.info(String.format(KEY, model.getCode(), this.logKey(), "获取锁成功"));
}
public void tryLockFaildLog(MutexModelEnum model) {
log.info(String.format(KEY, model.getCode(), this.logKey(), "获取锁失败"));
}
public void releaseLog(MutexModelEnum model) {
log.info(String.format(KEY, model.getCode(), this.logKey(), "释放锁成功"));
}
public void bizError(MutexModelEnum model) {
log.info(String.format(KEY, model.getCode(), this.logKey(), "业务异常"));
}
}
RedisLockAspect
@Slf4j
@Order(1)
@Aspect
@Component
public class RedisLockAspect {
@Autowired
private JedisComponent jedisComponent;
@Resource
private Validator validator;
@Around("@annotation(com.csy.core.aop.RedisLock)")
public Result around(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Method targetMethod = AopUtils.getMostSpecificMethod(method, joinPoint.getTarget().getClass());
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
String message = BeanValidators.validateWithErrorMessage(validator, arg);
if (StringUtils.isNotBlank(message)) {
return Result.wrapErrorResult(message);
}
}
RedisLock redisLock = AnnotationUtils.findAnnotation(targetMethod, RedisLock.class);
if (redisLock == null) {
return Data.wrapErrorResult("框架异常");
}
if (! (args[0] instanceof LockDomain)) {
return Data.wrapErrorResult("请继承LockDomain");
}
LockDomain lockObj = (LockDomain) args[0];
String key = redisLock.bizKey().getCode() + "_" + lockObj.redisKey();
try {
if (! jedisComponent.tryLock(key, redisLock.expire())) {
lockObj.tryLockFaildLog(redisLock.bizKey());
return Result.wrapErrorResult(redisLock.errorMsg());
}
lockObj.tryLockSucLog(redisLock.bizKey());
return joinPoint.proceed();
}
catch (Throwable e) {
lockObj.bizError(redisLock.bizKey());
//参数异常捕获
if (e instanceof ParamException) {
log.error("ParamException:", e);
ParamException paramException = (ParamException) e;
return Data.wrapErrorResult(paramException.getError().getErrorCode(), paramException.getError().getErrorMsg());
}
//自定义异常捕获
if (e instanceof BizException) {
log.error("BizException", e);
BizException bizException = (BizException) e;
return Data.wrapErrorResult(bizException.getError().getErrorCode(), bizException.getError().getErrorMsg());
}
log.error("系统异常:", e);
return Result.wrapErrorResult(ErrorCode.SERVER_ERROR);
}
finally {
lockObj.releaseLog(redisLock.bizKey());
jedisComponent.delKey(key);
}
}
}
分布式锁业务实现
public class RedisLockDemo {
@RedisLock(bizKey = MutexModelEnum.TEST)
public Result<Boolean> RedisLockTest(RedisLockTestRequest request) {
//do your biz
return Result.success(true);
}
@Getter
@Setter
public class RedisLockTestRequest extends LockDomain {
private Long userId;
private String userName;
/**
* 以userId作为分布式锁的key
* @return
*/
@Override
public String redisKey() {
return String.valueOf(this.userId);
}
@Override
public String logKey() {
return String.valueOf(this.userId);
}
}
}
可以看到。我们只要在方法上加上@RedisLock,指定锁的Model,再对入参继承LockDomain,指定redisKey和logKey就行。
注意事项
aop相关问题
有的同学可能想降低锁的粒度或者单纯的想抽出一个方法。比如:
public void a(RedisLockTestRequest request) {
this.RedisLockTest(request);
}
@RedisLock(bizKey = MutexModelEnum.TEST)
public Result<Boolean> RedisLockTest(RedisLockTestRequest request) {
//do your biz
return Result.success(true);
}
这种情况下,外部调用a方法,aop是不起作用的
因为aop是运行时织入,获取调用的目标方法(也就是a),判断是否是切点,再匹配切面。内部方法(不是目标方法)不进行织入。
如何解决上述情况?
我们使用AopContext
public void a(RedisLockTestRequest request) {
RedisLockDemo currentProxy = (RedisLockDemo) AopContext.currentProxy();
currentProxy.RedisLockTest(request);
}
@RedisLock(bizKey = MutexModelEnum.TEST)
public Result<Boolean> RedisLockTest(RedisLockTestRequest request) {
//do your biz
return Result.success(true);
}
通过AopContext获取到当前的代理类,然后调用。
上述涉及到的aop原理我会后续出个专门的aop浅析。
分布式锁相关问题
可能网上有一些分布式锁它是先setnx 然后 expire。其实是有问题的。因为它不是一个原子操作。
我们应该使用Jedis类下的set方法。一步设置值和过期时间
public String set(final String key, final String value, final String nxxx, final String expx, final long time) {
checkIsInMulti();
client.set(key, value, nxxx, expx, time);
return client.getStatusCodeReply();
}