• 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;
        }
    
    }
  • 相关阅读:
    mysql环境搭建
    php基础:查看上次插入的主键和影响的行数及关闭mysql连接
    php基础:文件包含与引用 require和include的区别
    php基础:echo和print_r和var_dump的区别
    php基础:变量检测
    php基础:动态变量名
    php基础:代码的短路特性和运算符优先级
    php基础:三元运算符及比较3个数的大小
    php基础:字符串基本函数
    php基础:数组的定义和遍历
  • 原文地址:https://www.cnblogs.com/a393060727/p/14884899.html
Copyright © 2020-2023  润新知