一 引入
1.1 分布式锁的概念
在单进程(启动一个jvm)的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁;
很多时候我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,通过 Java 提供的并发 API 我们可以解决,但是在分布式环境下,就没有那么简单啦。
- 分布式与单机情况下最大的不同在于其不是多线程而是多进程。
- 多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。
如果是在集群或分布式环境中要保证多进程中的多线程的线程安全就要使用分布式锁,分布式锁的目的就是在分布式/集群环境中使用加锁手段保证多个服务节点对同一个数据进行顺序操作,保证数据的安全性
分布式锁:需要得有一把唯一且共享的锁,多个服务同时去获取锁,但是只有一个服务才能获取到锁,其他没有获取到锁的服务需要等待或者自旋,等获取到锁的服务业务执行完成释放锁,其他的服务就可以再次尝试获取锁。
1.2 基于redission实现分布式锁逻辑
Redisson是一个实现的Java操作Redis的工具包,它不仅提供了一系列常用的操作Redis的API,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法,Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
二 整体代码实现
核心思想:将整个分布式锁的底层实现逻辑抽离出来,通过aop实现分布锁,这种方式实现的分布式锁粒度比较粗会对整个方法进行加锁,如果要在要在更加细粒度的代码块中实现分布式锁可以直接代码中增加基于redission实现的分布式锁,抽离出来是为了更好的解耦.
定义分布式锁注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface XlcpLock {
/**
* 所名称 支持spel表达式
* @return
*/
String name() default "";
/**
* 锁的类型 默认为可重入锁
* @return
*/
LockType lockType() default LockType.REENTRANT_LOCK;
/**
* 获取锁等待时间
* @return
*/
long waitTime() default Long.MIN_VALUE;
/**
* 锁超时时间
* @return
*/
long leaseTime() default Long.MIN_VALUE;
/**
* 获取锁失败处理策略
*/
LockTimeOutStrategy lockTimeOutStrategy() default LockTimeOutStrategy.NO_OPERATION;
/**
* 自定义业务key
* @return
*/
String[] keys() default "";
/**
* 锁释放失败处理策略
* @return
*/
LockReleaseFailStrategy lockReleaseFailStrategy() default LockReleaseFailStrategy.NO_OPERATION;
}
定义prop系统自定义核心参数
@ConfigurationProperties(prefix = XlcpLockProperties.PRE_FIX)
@Data
public class XlcpLockProperties {
public static final String PRE_FIX = "config.lock";
/**
* 获取锁超时时间
*/
private Long waitTime = 60L;
/**
* 锁过期时间
*/
private Long leaseTime = 60L;
/**
* 使用的redis数据库
*/
private Integer dataBase = 1;
/**
* 编码器
*/
private String codec = "org.redisson.codec.JsonJacksonCodec";
}
申明一个接口 用于申明锁的获取和释放
public interface Lock {
/**
* 获取锁
* @param lockInfo
* @return
*/
Boolean acquire(LockInfo lockInfo);
/**
* 释放锁
* @param lockInfo
* @return
*/
Boolean release(LockInfo lockInfo);
}
通过工厂模式+策略模式提供对外可访问的锁类型(这里只申明了一种可重入的锁)
public enum LockType {
// 可重入锁
REENTRANT_LOCK;
}
@Component
@RequiredArgsConstructor
public class ReentrantLock implements Lock, InitializingBean {
private final RedissonClient redissonClient;
private RLock rLock;
@Override
public Boolean acquire(LockInfo lockInfo) {
rLock = redissonClient.getLock(lockInfo.getLockName());
try {
return rLock.tryLock(lockInfo.getWaitTime(),lockInfo.getLeaseTime(), TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public Boolean release(LockInfo lockInfo) {
if (rLock.isHeldByCurrentThread()){
try {
return rLock.forceUnlockAsync().get();
} catch (InterruptedException e) {
return false;
} catch (ExecutionException e) {
return false;
}
}
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
LockFactory.register(LockType.REENTRANT_LOCK,this);
}
}
public class LockFactory {
public static Map<LockType,Lock> LOCKS = new ConcurrentHashMap<LockType,Lock>();
public static void