分布式环境中的业务开发,Synchronized、ReentrantLock等本地锁已经不能再防止并发冲突,分布式锁应运而生。Redis分布式锁是目前最火热的锁工具之一,但是项目中对于并发的控制加锁解锁非常频繁,冗余代码较多,锁管理分散。
本文基于Redisson通过两种方式实现代理分布式锁:
1、ThreadLocal线程缓存 + AOP切面
2、AOP切面 + 入参固定
未使用代理组建的情况下,一旦需要加锁都会进行以下编码,除了业务处理其他代码在业务功能中随处可见,冗余了许多非必要代码。
String lockKey = RedisConsts.ORDER_ID_LOCK + orderId;
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(RedisConsts.WAIT, RedisConsts.EXECUTE, RedisConsts.TIME_UNIT)) {
//订单处理
} else {
log.error("加锁失败:{}",lockKey);
}
} catch (InterruptedException e) {
log.error("获取锁异常:{}", e);
throw new BusinessException(ErrorCodeEnum.FAILED_TO_ACQUIRE_LOCK);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
String key();
long waitOut() default 60;
long executeOut() default 60;
TimeUnit timeUnit() default TimeUnit.SECONDS;
//是否自动清除LockUtil
boolean atuoRemove() default false;
String suffixKeyTypeEnum() default RedisLockCommonUtil.THREAD_LOCAL;
String objectName() default "";
String[] paramName() default {};
}
public class LockUtil {
static ThreadLocal LOCK_KEY = new ThreadLocal();
public static void set(String key) {
LOCK_KEY.set(key);
}
public static String get() {
return LOCK_KEY.get();
}
public static void remove() {
LOCK_KEY.remove();
}
}
个人更喜欢使用ThreadLocal。
使用固定入参需要解析参数,会增加一些性能消耗,虽然对于越来越快的计算性能,这点消耗可以忽略。
@Slf4j
@Aspect
@Component
public class RedisLockAspect {
@Resource
private RedissonClient redissonClient;
@Pointcut("@annotation(***.aspect.redislock.annotation.RedisLock)")
public void lockPointCut() {
}
@Around("lockPointCut() && @annotation(redisLock)")
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
String lockKey = redisLock.key();
if (Objects.nonNull(LockUtil.get())) {
//获取线程缓存中的锁后缀
lockKey += LockUtil.get();
}
log.info("开始代理加锁:{}",lockKey);
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(redisLock.waitOut(), redisLock.executeOut(), redisLock.timeUnit())) {
log.info("代理加锁成功:{}",lockKey);
return joinPoint.proceed();
} else {
log.warn("代理加锁失败:{}",lockKey);
}
} catch (InterruptedException e) {
log.error("获取代理锁异常:{}", e);
throw new BusinessException(ErrorCodeEnum.FAILED_TO_ACQUIRE_LOCK);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("代理解锁:{}",lockKey);
}
//如果方法注解中开启自动清除,就去除
if (redisLock.atuoRemove()) {
LockUtil.remove();
log.info("自动清除LockUtil:{}",lockKey);
}
}
return null;
}
}
@Slf4j
@Aspect
@Component
public class RedisLockAspect {
@Resource
private RedissonClient redissonClient;
@Pointcut("@annotation(***.aspect.redislock.annotation.RedisLock)")
public void lockPointCut() {
}
//定义固定入参名,业务使用时需要定义相同的参数名并传入下游方法
private final static String REDIS_KEY = "redisKey";
@Around("lockPointCut() && @annotation(redisLock)")
public void around(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
String objectName = redisLock.objectName();
if (StringUtil.isBlank(objectName)) {
throw new BusinessException("objectName为空");
}
String[] paramName = redisLock.paramName();
Object[] args = joinPoint.getArgs();
String[] objectNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map objectHashMap = new HashMap<>();
for (int i = 0; i < objectNames.length; i++) {
objectHashMap.put(objectNames[i], args[i]);
}
if (!objectHashMap.containsKey(objectName)) {
throw new BusinessException("入参不包含该对象" + objectName);
}
Object o = objectHashMap.get(objectName);
if (paramName == null || paramName.length == 0) {
return redisLock.key() + o;
}
String lockKey = redisLock.key();
for (int i = 0; i < paramName.length; i++) {
lockKey += RedisLockCommonUtil.getFieldValueByName(paramName[i], o);
}
log.info("开始代理加锁:{}",lockKey);
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(redisLock.waitOut(), redisLock.executeOut(), redisLock.timeUnit())) {
log.info("代理加锁成功:{}",lockKey);
joinPoint.proceed();
}
} catch (InterruptedException e) {
log.error("获取代理锁异常:{}", e);
throw new BusinessException(ErrorCodeEnum.FAILED_TO_ACQUIRE_LOCK);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("代理解锁:{}",lockKey);
}
}
}
}
1、模拟并发,两个可以同时进行,第三个线程有锁争抢
Thread lock1 = new Thread(
new Runnable() {
@Override
public void run() {
snChainDTO1.setSn("666sn");
LockUtil.set(snChainDTO1.getSn());
lockProcess.process(snChainDTO1);
}
}
);
lock1.start();
log.info("lock1 start");
MacSnChainDTO snChainDTO2 = new MacSnChainDTO();
Thread lock2 = new Thread(
new Runnable() {
@Override
public void run() {
snChainDTO2.setSn("888sn");
LockUtil.set(snChainDTO2.getSn());
lockProcess.process(snChainDTO2);
}
}
);
lock2.start();
log.info("lock2 start");
MacSnChainDTO snChainDTO3 = new MacSnChainDTO();
Thread lock3 = new Thread(
new Runnable() {
@Override
public void run() {
snChainDTO3.setSn("666sn");
LockUtil.set(snChainDTO3.getSn());
lockProcess.process(snChainDTO3);
}
}
);
lock3.start();
log.info("lock3 start");
2、测试业务方法
@RedisLock(key = RedisPrefixConstant.DAMAGE_SN_LOCK_KEY, atuoRemove = true)
public void process(MacSnChainDTO macSnChainDTO) {
for (int i = 0; i < 6; i++) {
log.info("测试加锁:{}", LockUtil.get() + i);
}
}
3、测试结果
2022-04-11 16:02:58.027 bit [http-nio-8189-exec-1] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.lockTogether:58 - lock1 start
2022-04-11 16:02:58.028 bit [http-nio-8189-exec-1] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.lockTogether:74 - lock2 start
2022-04-11 16:02:58.029 bit [Thread-98] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:36 - 开始代理加锁:BIT:LOCK:DAMAGE:SN:888sn
2022-04-11 16:02:58.029 bit [Thread-97] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:36 - 开始代理加锁:BIT:LOCK:DAMAGE:SN:666sn
2022-04-11 16:02:58.030 bit [http-nio-8189-exec-1] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.lockTogether:90 - lock3 start
2022-04-11 16:02:58.031 bit [Thread-99] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:36 - 开始代理加锁:BIT:LOCK:DAMAGE:SN:666sn
2022-04-11 16:02:58.033 bit [http-nio-8189-exec-1] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.logAround:52 - method: lockTogether, result: {"success":true}, span: 23
2022-04-11 16:02:58.063 bit [Thread-97] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:40 - 代理加锁成功:BIT:LOCK:DAMAGE:SN:666sn
2022-04-11 16:02:58.066 bit [Thread-98] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:40 - 代理加锁成功:BIT:LOCK:DAMAGE:SN:888sn
2022-04-11 16:02:58.067 bit [Thread-98] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:888sn0
2022-04-11 16:02:58.067 bit [Thread-97] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn0
2022-04-11 16:02:58.068 bit [Thread-98] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:888sn1
2022-04-11 16:02:58.068 bit [Thread-97] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn1
2022-04-11 16:02:58.068 bit [Thread-98] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:888sn2
2022-04-11 16:02:58.068 bit [Thread-97] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn2
2022-04-11 16:02:58.068 bit [Thread-98] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:888sn3
2022-04-11 16:02:58.068 bit [Thread-97] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn3
2022-04-11 16:02:58.068 bit [Thread-97] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn4
2022-04-11 16:02:58.068 bit [Thread-98] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:888sn4
2022-04-11 16:02:58.068 bit [Thread-97] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn5
2022-04-11 16:02:58.068 bit [Thread-98] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:888sn5
2022-04-11 16:02:58.084 bit [Thread-97] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:48 - 代理解锁:BIT:LOCK:DAMAGE:SN:666sn
2022-04-11 16:02:58.084 bit [Thread-97] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.run:51 - lock1-LockUtil:666sn
2022-04-11 16:02:58.084 bit [Thread-97] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.run:53 - 自动清除LockUti:BIT:LOCK:DAMAGE:SN:666sn
2022-04-11 16:02:58.090 bit [Thread-98] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:48 - 代理解锁:BIT:LOCK:DAMAGE:SN:888sn
2022-04-11 16:02:58.091 bit [Thread-98] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.run:67 - lock2-LockUtil:888sn
2022-04-11 16:02:58.091 bit [Thread-98] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.run:69 - l自动清除LockUti:BIT:LOCK:DAMAGE:SN:888sn
2022-04-11 16:02:58.121 bit [Thread-99] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:40 - 代理加锁成功:BIT:LOCK:DAMAGE:SN:666sn
2022-04-11 16:02:58.122 bit [Thread-99] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn0
2022-04-11 16:02:58.122 bit [Thread-99] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn1
2022-04-11 16:02:58.122 bit [Thread-99] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn2
2022-04-11 16:02:58.122 bit [Thread-99] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn3
2022-04-11 16:02:58.122 bit [Thread-99] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn4
2022-04-11 16:02:58.122 bit [Thread-99] INFO c.e.bit.controller.testlock.LockProcessImpl.process:25 - 测试加锁:666sn5
2022-04-11 16:02:58.142 bit [Thread-99] INFO c.e.bit.config.redislock.aspect.RedisLockAspect.around:48 - 代理解锁:BIT:LOCK:DAMAGE:SN:666sn
2022-04-11 16:02:58.142 bit [Thread-99] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.run:83 - lock3-LockUtil:666sn
2022-04-11 16:02:58.142 bit [Thread-99] INFO c.enmonster.bit.controller.testlock.LockEnterImpl.run:85 - 自动清除LockUti:BIT:LOCK:DAMAGE:SN:666sn
线程缓存是java提供的绑定当前线程的存储空间,因此在任意方法进行ThreadLocal 的设置,后续只要属于当前线程的方法都可以取到这个值。
首先通过它的set方法分析
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程为键对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//将当前ThreadLocal对象作为基础,业务值设置到ThreadLocalMap维护的数组中
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
//创建当前线程的ThreadLocalMap
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
//ThreadLocalMap维护了一个数组,这是因为线程的缓存值可能有多个,许多第三方框架都在使用
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//通过hasn值与数组长度取&获取下标
int i = key.threadLocalHashCode & (len-1);
//将业务值设置到数组中
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
接着通过get方法分析
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程为键对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//将当前ThreaadLocal对象传入getEntry方法,获取线程缓存的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal> key) {
//根据对象的hash值与存储数组进行&,获取存储值的下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
用完记得remove!由于ThreadLocalMap的Entry是虚引用,线程如果不销毁不会被回收,用完就进行remove会避免内存泄漏风险。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//将虚引用置为null
e.clear();
//清理数组
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//将该下标对象置为null,便于gc回收
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//遍历该下标之后所有不为空的ThreadLocal对象
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal> k = e.get();
if (k == null) {
//如果该ThreadLocal实例的虚引用已经被销毁,将该位置的ThreadLocal置为null
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
对于动态代理可以说是老生常谈了,这里就不进行描述了,做个字节码反编译给大家看看代理类的实际模样吧。
可以看到代理类继承自Proxy,并且将LockProcessImpl 的方法编织成了接口方法,再由代理类实现接口方法。最终调用invoke方法,invoke方法在会通过反射获取到原始方法要执行的代码,并且进行方法增强。
总结起来就是AOP将注解下的类方法进行包装,切面的代码将业务方法包在中间进行增强执行。
import com.enmonster.bit.controller.testlock.LockProcessImpl;
import com.enmonster.bit.entity.responsibility.MacSnChainDTO;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class LockProcessImpl extends Proxy implements LockProcessImpl {
private static Method m1;
private static Method m8;
private static Method m3;
private static Method m2;
private static Method m6;
private static Method m5;
private static Method m7;
private static Method m9;
private static Method m0;
private static Method m4;
public LockProcessImpl(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void notify() {
try {
this.h.invoke(this, m8, null);
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void process(MacSnChainDTO paramMacSnChainDTO) {
try {
this.h.invoke(this, m3, new Object[] { paramMacSnChainDTO });
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void wait(long paramLong) throws InterruptedException {
try {
this.h.invoke(this, m6, new Object[] { Long.valueOf(paramLong) });
return;
} catch (Error|RuntimeException|InterruptedException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void wait(long paramLong, int paramInt) throws InterruptedException {
try {
this.h.invoke(this, m5, new Object[] { Long.valueOf(paramLong), Integer.valueOf(paramInt) });
return;
} catch (Error|RuntimeException|InterruptedException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final Class getClass() {
try {
return (Class)this.h.invoke(this, m7, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void notifyAll() {
try {
this.h.invoke(this, m9, null);
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void wait() throws InterruptedException {
try {
this.h.invoke(this, m4, null);
return;
} catch (Error|RuntimeException|InterruptedException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m8 = Class.forName("com.enmonster.bit.controller.testlock.LockProcessImpl").getMethod("notify", new Class[0]);
m3 = Class.forName("com.enmonster.bit.controller.testlock.LockProcessImpl").getMethod("process", new Class[] { Class.forName("com.enmonster.bit.entity.responsibility.MacSnChainDTO") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m6 = Class.forName("com.enmonster.bit.controller.testlock.LockProcessImpl").getMethod("wait", new Class[] { long.class });
m5 = Class.forName("com.enmonster.bit.controller.testlock.LockProcessImpl").getMethod("wait", new Class[] { long.class, int.class });
m7 = Class.forName("com.enmonster.bit.controller.testlock.LockProcessImpl").getMethod("getClass", new Class[0]);
m9 = Class.forName("com.enmonster.bit.controller.testlock.LockProcessImpl").getMethod("notifyAll", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m4 = Class.forName("com.enmonster.bit.controller.testlock.LockProcessImpl").getMethod("wait", new Class[0]);
return;
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}