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);
            }

使用的是springboot2,采用代码配置式。

RedisConfig.java


import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * json序列化
     * 
     * @return
     */
    @Bean
    public RedisSerializer<Object> jackson2JsonRedisSerializer() {
        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        return serializer;
    }

    /**
     * 配置缓存管理器
     * 
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 设置缓存的默认过期时间,也是使用Duration设置
        config = config.entryTtl(Duration.ofMinutes(1))
                // 设置 key为string序列化
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 设置value为json序列化
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
                // 不缓存空值
                .disableCachingNullValues();

        // 设置一个初始化的缓存空间set集合
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("timeGroup");
        cacheNames.add("user");

        // 对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("timeGroup", config);
        configMap.put("user", config.entryTtl(Duration.ofSeconds(120)));

        // 使用自定义的缓存配置初始化一个cacheManager
        RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                // 一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
                .initialCacheNames(cacheNames).withInitialCacheConfigurations(configMap).build();
        return cacheManager;
    }

    @Bean
    public RedisTemplate<String, String> StringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
    
     /**
     * @return lua脚本===================重点
     */
    @Bean
    public DefaultRedisScript<Long> redisScript() {
      DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
      defaultRedisScript.setResultType(Long.class);
      defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
      return defaultRedisScript;
    }

}
原文地址:https://www.cnblogs.com/a393060727/p/14884899.html