今天给大家带来我们的企业级应用组件设计之Redisson分布式锁。
对于redis一定绝大部分Java同学都不陌生,我们的缓存中间件,拥有多路IO单路复用的特性,最显著的特点就是快!我们大多会使用它作用缓冲作用,也就是为我们的MySQL进行读写责任的分担,流量的分担,以及缓存使用,对比计算机本身的 cpu > cache > 磁盘 访问速度的差异,我们也不难推断出 应用本身的JVM堆存 > redis > http等远程端口访问存储 ,我们的数据库MYSQL说白了也是一台部署在3306端口的单体应用,我们可以通过访问IP加此端口进行数据库的权限校验,数据读写操作。那么说回来,redis能作为缓存是因为它的特性快而存储类型合适。其外它还自备了命令setnx命令与expire命令,大部分单体应用可以使用这个命令进行加锁,但是这并不能以分布式的方式被大家都感知到。也就是说当多个主机进行访问同一把锁去做一件事情的时候,我们的redist命令级别很可能导致脏数据与且无法正常的上锁(不过多赘述,仅为大家展示如何设计分布式锁)。
@Configuration
public class RedissonConfiguration {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.password}")
private String password;
@Bean(destroyMethod = "shutdown",name = "redisson")
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public RedissonClient redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://"+redisHost+":"+port).setPassword(password);
config.setCodec(new JsonJacksonCodec());
return Redisson.create(config);
}
}
@Component("RedissonLockService")
@ConditionalOnBean(RedissonConfiguration.class)
@Slf4j
public class RedissonLockService {
@Resource
private RedissonClient redisson;
public Boolean lock(String key) {
return lock(key, 10L);
}
public T lock(String key,long expireTime,LockExecutor executor){
this.lock(key,expireTime);
T var;
try{
var = executor.execute();
}finally {
this.unlock(key);
}
return var;
}
public Boolean redLock(String key) {
return redLock(key, 10L);
}
public T redLock(String key,long expireTime,LockExecutor executor){
this.redLock(key, expireTime);
T var;
try{
var = executor.execute();
}finally {
this.redUnlock(key);
}
return var;
}
public Boolean lock(String key, Long expireTime) {
if (Objects.isNull(redisson)) {
log.warn("redisson客户端对象为null");
return Boolean.FALSE;
}
try {
RLock lock = redisson.getLock(key);
if (lock.isLocked()) {
log.info("锁:{}已被获取,当前请求阻塞",key);
return Boolean.TRUE;
}
lock.lock(expireTime, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
log.warn("获取锁异常");
return Boolean.TRUE;
}
}
public Boolean redLock(String key, Long expireTime) {
if (Objects.isNull(redisson)) {
log.warn("redisson客户端对象为null");
return Boolean.FALSE;
}
try {
RLock lock = redisson.getLock(key);
RedissonRedLock redLock = new RedissonRedLock(lock);
return redLock.tryLock(expireTime, TimeUnit.SECONDS);
} catch (Exception e) {
log.warn("获取红锁异常");
return Boolean.FALSE;
}
}
public Boolean tryLock(String key,Long waitTime,long timeout, TimeUnit unit){
if (Objects.isNull(redisson)) {
log.warn("redisson客户端对象为null");
return Boolean.FALSE;
}
try{
RLock lock = redisson.getLock(key);
if (lock.isLocked()) {
log.info("锁:{}已被获取,当前请求阻塞",key);
return Boolean.TRUE;
}
return lock.tryLock(waitTime,timeout,unit);
}catch(Exception e){
log.warn("重入锁异常");
return Boolean.FALSE;
}
}
public Boolean unlock(String key) {
if (Objects.isNull(redisson)) {
log.warn("redisson客户端对象为null");
return Boolean.FALSE;
}
try {
RLock lock = redisson.getLock(key);
if (lock.isLocked()) {
log.info("锁:{}释放",key);
lock.unlock();
}
return Boolean.TRUE;
}catch (Exception e){
log.warn("释放锁异常");
return Boolean.FALSE;
}
}
public Boolean redUnlock(String key) {
if (Objects.isNull(redisson)) {
log.warn("redisson客户端对象为null");
return Boolean.FALSE;
}
try {
RLock lock = redisson.getLock(key);
RedissonRedLock redLock = new RedissonRedLock(lock);
redLock.unlock();
return Boolean.TRUE;
}catch (Exception e){
log.warn("释放红锁异常");
return Boolean.FALSE;
}
}
}
众所周知,上锁可以上方法级别也可以上代码段级别,就和事务差不多,颗粒度越小。数据的安全性就越好,所以这里先提供方法级别的锁注解,通过AOP切面逻辑解读注解,获取注解内部属性的值去上锁:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface RedissonAutoLock {
long id();
long waitTime() default 3L;
long expireTime() default 10L;
String description() default "";
boolean assembleUserInfo() default false;
}
那么解读注解的切面类:
@Aspect
@Component
@DependsOn({"RedissonLockService"})
public class RedissonLockAspect {
@Resource
private RedissonLockService redissonLockService;
@Pointcut("@annotation(com.runjing.learn_runjing.redis.redisson.RedissonAutoLock)")
public void pointcut() {
}
@Around("pointcut()")
public void lock(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RedissonAutoLock redissonAutoLock = method.getAnnotation(RedissonAutoLock.class);
long waitTime = redissonAutoLock.waitTime();
long expireTime = redissonAutoLock.expireTime();
long id = redissonAutoLock.id();
String key = id + method.getName();
try {
redissonLockService.lock(key, expireTime);
try {
joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
redissonLockService.unlock(key);
}
} catch (Exception e) {
Boolean tryLock = redissonLockService.tryLock(key, waitTime, expireTime, TimeUnit.SECONDS);
if (tryLock) {
try {
joinPoint.proceed();
} catch (Throwable ex) {
throw new RuntimeException(ex);
} finally {
redissonLockService.unlock(key);
}
}
} finally {
redissonLockService.unlock(key);
}
}
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint joinPoint) {
}
@AfterThrowing("pointcut()")
public void afterThrowing(JoinPoint joinPoint) {
}
}
真正使用时直接在方法上加注解就行:
@RedissonAutoLock
public BaseResponse getErpInventoryCoreById(Long id) {
if (Objects.isNull(id)){
throw new RuntimeException("id为空");
}
return BaseResponse.success("", Optional.ofNullable(erpInventoryCoreMapper.getErpInventoryCore(id)).orElse(null));
}
那么到此为止!