自定义注解+redis实现简单易用的分布式锁

分布式锁优化实践

最近项目中有的模块功能涉及到并发,为了提高项目中分布式锁的易用性,在之前的一个思路基础上进行了迭代改进。
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年就要结束了
温故而知新
做一个有追求的技术人

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值