redis 实现 分布式锁,排队等待取得锁

Posted a393060727

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis 实现 分布式锁,排队等待取得锁相关的知识,希望对你有一定的参考价值。

分布式锁:锁了,就只有锁定的线程才能操作。

与java中的锁类似,只是我们是否锁定是依托与第三方redis中的一个key标识判断是否可以操作。

现在场景是:一个订单来了,必须处理,等待上个线程处理完后,竞争取得锁,否则就处理超时,业务处理失败。

下面是锁的工具类:

很奇怪的是,取不到锁时,等待期间不能休眠,否则还是锁不住。。所以就不休眠,作死了遍历查询。

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import lombok.extern.slf4j.Slf4j;

/**
 * @Desc : 分布式锁
 * @Company : 晨创科技
 * @author : Chenweixian 陈惟鲜
 * @Date : 2018年6月5日 下午2:41:03
 */
@Slf4j
public class RedisLockUtil {

    private static final Long RELEASE_SUCCESS = 1L;

    private static final StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) SpringUtils.getBean("stringRedisTemplate");

    private static final DefaultRedisScript<Long> redisScript = (DefaultRedisScript<Long>) SpringUtils.getBean("redisScript");

    private static long timeout = 65 * 1000L; // 锁定65S
    /**
     * 上锁
     * @param key 锁标识
     * @param value 线程标识
     * @return 上锁状态
     */
    public static boolean lock(String key, String value, long mytimeout) {
        long start = System.currentTimeMillis();
        // 检测是否超时
        if (System.currentTimeMillis() - start > mytimeout) {
            return false;
        }
        // 执行set命令
        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, value, mytimeout, TimeUnit.MILLISECONDS);// 毫秒
        // 是否成功获取锁
        if (absent) {
            return true;
        }
        return false;
    }
    
    /**
     * 上锁
     * @param key 锁标识
     * @param value 线程标识
     * @return 上锁状态
     */
    public static boolean lock(String key, String value) {
        long start = System.currentTimeMillis();
        while (true) {
            // 检测是否超时
            if (System.currentTimeMillis() - start > timeout) {
                return false;
            }
            // 执行set命令
            Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);// 毫秒
            // 是否成功获取锁
            if (absent) {
                return true;
            }
            // 不能休眠
//            try {
//                Thread.sleep(5);// 等待50毫秒
//            } catch (InterruptedException e) {
//            }
        }
    }

    /**
     * 解锁
     *  @param key 锁标识
     * @param value 线程标识
     * @return 解锁状态
     */
    public static boolean unlock(String key, String value) {
        // 使用Lua脚本:先判断是否是自己设置的锁,再执行删除
        Long result = stringRedisTemplate.execute(redisScript, Arrays.asList(key, value));
        // 返回最终结果
        return RELEASE_SUCCESS.equals(result);
    }
}

 

调用方法:

做了个注解@RedisLock,意味着,加上这个注解后,都会取锁后才能执行。

这里是针对加上注解的类方法锁定

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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


import lombok.extern.slf4j.Slf4j;

/**
 * @Desc : 分布式锁切面拦截,方法上加上@RedisLock即可
 * @author : Chenweixian 陈惟鲜
 * @Date : 2018年5月7日 下午7:47:56
 */
@Slf4j
@Aspect
@Component
@Order(7) // @Order(1)数字越小优先级越高
public class RedisLockAspect {
    
    /**拦截所有controller包下的方法*/
    @Pointcut("@annotation(com.ccjr.commons.annotation.RedisLock)")
    private void lockMethod(){
    }
    
    /** 
     *  日志打印
     * @author : 陈惟鲜 chenweixian
     * @Date : 2018年8月8日 下午5:29:47
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("lockMethod() && @annotation(redisLock)")  
    public Object doAround(ProceedingJoinPoint point, RedisLock redisLock) throws Throwable {
        String class_name = point.getTarget().getClass().getName();
        String method_name = point.getSignature().getName();
        String threadUUID = UUIDUtils.generateUUID64();
        String redisKey = RedisConstants.keyRedisLock(class_name + "." + method_name);
        Object result = null;
        try {
            RedisLockUtil.lock(redisKey, threadUUID);
                log.info("lock aop rediskey :"+redisKey);
                result = point.proceed();// result的值就是被拦截方法的返回值  
        } finally {
            RedisLockUtil.unlock(redisKey, threadUUID);
            log.info("unlock aop rediskey :"+redisKey);
        }
        return result;
    }
}

 

注解类RedisLock.java

/**
 * @Desc : redisLock注解标签,使用该标签后,并且在app中配置RedisLockAop后,项目将实现注解分布式锁,
 * key为当前类+方法名
 * @author : Chenweixian 陈惟鲜
 * @Date : 2018年5月7日 下午7:44:56
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    
}

 

调用时就简单了:

    @Override
    @RedisLock
    public BaseResponse<String> genHisTask(BaseRequest<GenHisAssetsAnalyzeTaskReq> baseRequest) {
        String msg = "生成历史数据";
        // 业务处理过程。。。。。。return baseApiService.setResultSuccess();
    }

 

当然如果针对某块来锁定的,比如锁定某个客户的库存,

客户买入时冻结钱,商家卖出时冻结商品,针对一个商品号goodsId锁定

       String threadUUID = UUIDUtils.generateUUID64();
            String redisKey = RedisConstants.keyRedisLockCash(goodsId);
            try {
                if (!RedisLockUtil.lock(redisKey, threadUUID)) {
                    throw new BusinessBizException(ApiErrorEnum.TRADE_CASH_21000, goodsId);
                }
                // 执行业务过程。。。。。。
                results.add(result);
            } finally {
                RedisLockUtil.unlock(redisKey, threadUUID);
            }

 

以上是关于redis 实现 分布式锁,排队等待取得锁的主要内容,如果未能解决你的问题,请参考以下文章

Redis实现分布式锁(设计模式应用实战)

Redis实现分布式锁(设计模式应用实战)

单实例redis分布式锁的简单实现

redis 分布式锁的简单使用

redis 分布式锁的简单使用

什么是分布式锁及正确使用redis实现分布式锁