最近项目中有的模块功能涉及到并发,为了提高项目中分布式锁的易用性,在之前的一个思路基础上进行了迭代改进。
https://blog.youkuaiyun.com/yuruixin_china/article/details/79441260
这是之前写的一篇关于分布式锁的文章。
最近测试了一把之前的这个设计思路,发现有逻辑漏洞
如上图中,当finally代码块中释放锁的逻辑执行时,该处切入的方法实际还没有完全执行完成,还未提交事务。原因是事务的执行顺序是int的最大值,即最后执行。
尝试了将切面的执行顺序和事务的执行顺序进行配置,未能成功,看到有文章提到事务的织入方式与Aspectj的不同,所以执行顺序无法通过配置Order来控制
进一步考量后,其实我的需求就是在当前事务提交之后,执行我的释放锁操作
于是,再次百度觅之,无果。
遂,换上google,第一页就找到了解决方案。
还是谷歌大发好啊!!!
spring的事务模块,提供了一个TransactionSynchronizationManager类,代码如下:
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
public TransactionSynchronizationManager() {
}
public static Map<Object, Object> getResourceMap() {
Map<Object, Object> map = (Map)resources.get();
return map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap();
}
public static boolean hasResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
return value != null;
}
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
return null;
} else {
Object value = map.get(actualKey);
if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
map.remove(actualKey);
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
}
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
map = new HashMap();
resources.set(map);
}
Object oldValue = ((Map)map).put(actualKey, value);
if (oldValue instanceof ResourceHolder && ((ResourceHolder)oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
} else {
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]");
}
}
}
public static Object unbindResource(Object key) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doUnbindResource(actualKey);
if (value == null) {
throw new IllegalStateException("No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
} else {
return value;
}
}
@Nullable
public static Object unbindResourceIfPossible(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
return doUnbindResource(actualKey);
}
@Nullable
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
return null;
} else {
Object value = map.remove(actualKey);
if (map.isEmpty()) {
resources.remove();
}
if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
value = null;
}
if (value != null && logger.isTraceEnabled()) {
logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
}
public static boolean isSynchronizationActive() {
return synchronizations.get() != null;
}
public static void initSynchronization() throws IllegalStateException {
if (isSynchronizationActive()) {
throw new IllegalStateException("Cannot activate transaction synchronization - already active");
} else {
logger.trace("Initializing transaction synchronization");
synchronizations.set(new LinkedHashSet());
}
}
public static void registerSynchronization(TransactionSynchronization synchronization) throws IllegalStateException {
Assert.notNull(synchronization, "TransactionSynchronization must not be null");
if (!isSynchronizationActive()) {
throw new IllegalStateException("Transaction synchronization is not active");
} else {
((Set)synchronizations.get()).add(synchronization);
}
}
public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException {
Set<TransactionSynchronization> synchs = (Set)synchronizations.get();
if (synchs == null) {
throw new IllegalStateException("Transaction synchronization is not active");
} else if (synchs.isEmpty()) {
return Collections.emptyList();
} else {
List<TransactionSynchronization> sortedSynchs = new ArrayList(synchs);
AnnotationAwareOrderComparator.sort(sortedSynchs);
return Collections.unmodifiableList(sortedSynchs);
}
}
public static void clearSynchronization() throws IllegalStateException {
if (!isSynchronizationActive()) {
throw new IllegalStateException("Cannot deactivate transaction synchronization - not active");
} else {
logger.trace("Clearing transaction synchronization");
synchronizations.remove();
}
}
public static void setCurrentTransactionName(@Nullable String name) {
currentTransactionName.set(name);
}
@Nullable
public static String getCurrentTransactionName() {
return (String)currentTransactionName.get();
}
public static void setCurrentTransactionReadOnly(boolean readOnly) {
currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null);
}
public static boolean isCurrentTransactionReadOnly() {
return currentTransactionReadOnly.get() != null;
}
public static void setCurrentTransactionIsolationLevel(@Nullable Integer isolationLevel) {
currentTransactionIsolationLevel.set(isolationLevel);
}
@Nullable
public static Integer getCurrentTransactionIsolationLevel() {
return (Integer)currentTransactionIsolationLevel.get();
}
public static void setActualTransactionActive(boolean active) {
actualTransactionActive.set(active ? Boolean.TRUE : null);
}
public static boolean isActualTransactionActive() {
return actualTransactionActive.get() != null;
}
public static void clear() {
synchronizations.remove();
currentTransactionName.remove();
currentTransactionReadOnly.remove();
currentTransactionIsolationLevel.remove();
actualTransactionActive.remove();
}
}
其中有两个方法可以解决之前设计漏洞:
isActualTransactionActive
registerSynchronization
具体应用如下(优化后的代码):
Lock注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
/**
* 分布式锁的key前缀
*/
LockKeyPrefixEnum lockKeyPrefix();
String description();
/**
* 等待超时单位:秒
*/
int timeOut() default 5;
/**
* 过期时长单位:秒
*/
int expireTime() default 3;
}
锁的key前缀枚举LockKeyPrefixEnum
/**
* @ClassName: LockKeyPrefixEnum
* @Description: lockkey前缀管理
* @Auther: RuiXin Yu
* @Date: 2018/12/26 10:53
*/
public enum LockKeyPrefixEnum {
A_B_C("a_b_c_");
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
LockKeyPrefixEnum(String info) {
this.info = info;
}
}
切面核心处理类LockService
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.lang.reflect.Method;
/**
* @ClassName: LockService
* @Description: 分布式锁切面服务
*
* 使用前必读
* 使用该锁时,应注意以下几点:
* 1. 为了将加锁粒度控制到最小,建议在使用时须传一个lockKeyPrefix作为额外加锁条件(如id)
* 2. 确实不需要额外控制粒度的,显式将lockKeyPrefix置为空串
* 3. description同为必填项,作为获取锁失败时,日志打印使用,为该方法的行为描述
* 4. timeOut等待超时时间,默认为5秒
* 5. expireTime锁的有效时长(即过期时间),默认为3秒
* 6.使用该锁,请确认使用场景正确、粒度控制适度、参数传递正确,并进行并发测试来确认锁是否达到期望效果
*
* @Auther: RuiXin Yu
* @Date: 2018/12/26 9:11
*/
@Component
@Aspect
@Order
public class LockService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
protected Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("@annotation(com.choice.cloud.smp.basic.lock.Lock)")
public void lockPointcut() {
}
@Around("lockPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Lock lock = getMethodAnnotation(point);
String lockKeySuffix = getLockKeySuffix(point);
// 获得锁对象实例
String lockKey = lock.lockKeyPrefix().getInfo() + lockKeySuffix;
RedisLock redisLock = new RedisLock(stringRedisTemplate, lockKey, String.valueOf(lock.expireTime()), lock.timeOut()*1000*100);
try {
if (redisLock.tryLock()) {
return point.proceed(point.getArgs());
} else {
logger.error("{}:获取redis锁失败,锁的key是 = {}", lock.description(), lockKey);
throw new CustomException(lock.description()+"获取锁失败");
}
} finally {
if(TransactionSynchronizationManager.isActualTransactionActive()){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
redisLock.unlock();
super.afterCommit();
}
});
}else{
redisLock.unlock();
}
}
}
/**
* 获取分布式锁后缀
* @param point
* @return
*/
private String getLockKeySuffix(ProceedingJoinPoint point) {
Object[] methodParam = point.getArgs();
String lockKeySuffix;
try {
lockKeySuffix = (String) methodParam[0];
}catch (ClassCastException e){
logger.error("加锁方法参数不合法:{}", methodParam[0]);
throw new CustomException("加锁方法参数不合法");
}
return lockKeySuffix;
}
/**
* 获取分布式锁注解中的参数
* @param joinPoint
* @return
*/
private Lock getMethodAnnotation(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
return method.getAnnotation(Lock.class);
}
}
改进后的finally代码块中,不再直接释放锁,而是将释放锁的操作注册到事务提交后执行
若该切面不在事务中运行,则直接释放锁。
2018年就要结束了
温故而知新
做一个有追求的技术人