zoukankan      html  css  js  c++  java
  • redis 分布式锁

    最近抽空优化了之前已有的redis分布式锁,主要用于解决高并发的问题,比如抢红包,多个人同时操作红包库存,当在库存只剩下1个的时候,一个人的减库存的操作事务没提交,另一个人的查库存操作刚好同步执行,这样就会出现很尴尬的事情,1个红包会被2个人抢走,这个时候,我们就要依托锁,将请求入口锁住,当然锁有很多种方式,这边就记录一下比较好用的redis分布式锁。 

    方式有很多setNX 、set、incr等等,setNX只要通过逻辑防止死锁就可以了

    直接上代码:

    public boolean keyLock(final String key, final long keepMin) {
            boolean obj = false;
            try {
                obj = (boolean) redisTemplateSerializable.execute(new RedisCallback<Object>() {
                    
                    @Override
                    public Object doInRedis(RedisConnection connection)
                            throws DataAccessException {
                        try{
                            Long incr = connection.incr(key.getBytes());
                            if(incr == 1){
                                connection.setEx(key.getBytes(), keepMin, incr.toString().getBytes());
                                return true;
                            }else{
                                Long ttl = connection.ttl(key.getBytes());
                                if(ttl == -1){
                                    //设置失败,重新设置过期时间
                                    connection.setEx(key.getBytes(), keepMin, incr.toString().getBytes());
                                    return true;
                                }
                            }
                        }catch (Exception e) {
                            logger.error("加锁异常", e);
                            connection.del(key.getBytes());
                            return true;
                        }
                        
                        return false;
                    }
        
                });
            }catch (Exception e) {
                 logger.error(e.getMessage());
            }
            
             return obj;
        }

    注解

    package com.tp.soft.common.interceptor;
    
    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;
    import java.util.concurrent.TimeUnit;
    
    /**
     * redis锁注解
     * 
     * @author taop
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    @Documented
    public @interface RedisLock {
        String lockName() default ""; // 锁名
        int retryTimes() default 0; // 重试次数
        long retryWait() default 200; // 重试等待时间,单位 : ms
        int keeyMinTime() default 1; //锁自动失效时间 1秒
    }

    aop

    package com.tp.soft.aop.redis;
    
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.annotation.Resource;
    
    import org.apache.commons.lang.StringUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    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.core.LocalVariableTableParameterNameDiscoverer;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    import org.springframework.stereotype.Component;
    
    import cn.hutool.core.lang.Assert;
    
    import com.tp.soft.common.interceptor.Cacheable;
    import com.tp.soft.common.interceptor.RedisLock;
    import com.tp.soft.redis.RedisCacheSvc;
    
    @Aspect
    @Component
    public class RedisLockAop {
    
        private static final Logger log = LoggerFactory.getLogger(RedisLockAop.class);
        
        private static final String LOCK_NAME = "lockName";
        private static final String RETRY_TIMES = "retryTimes";
        private static final String RETRY_WAIT = "retryWait";
        private static final String KEEP_MIN_TIME = "keepMinTime";
    
        @Resource
        private RedisCacheSvc redisCacheSvc;
    
        @Pointcut("@annotation(com.tp.soft.common.interceptor.RedisLock)")
        public void redisLockAspect() {
        }
    
        @Around("redisLockAspect()")
        public Object lockAroundAction(ProceedingJoinPoint pjp) throws Throwable {
            Method method = returnMethod(pjp);
            
            Map<String, Object> annotationArgs = this.getAnnotationArgs(pjp);
            String lockPrefix = (String) annotationArgs.get(LOCK_NAME);
            Assert.notNull(lockPrefix, "分布式,锁名不能为空");
            
            int retryTimes = (int) annotationArgs.get(RETRY_TIMES);
            long retryWait = (long) annotationArgs.get(RETRY_WAIT);
            int keepMinTime = (int) annotationArgs.get(KEEP_MIN_TIME);
            String keyName = parseKey(lockPrefix, method, pjp.getArgs());
            
            // 获取redis锁,防止死锁
            boolean keyLock = redisCacheSvc.keyLock(keyName, keepMinTime);
            if(keyLock){
                //执行主程序
                return pjp.proceed();
            }else{
                if(retryTimes <= 0){
                     log.info(String.format("{%s}已经被锁, 不重试", keyName));
                     throw new RuntimeException(String.format("{%s}已经被锁, 不重试", keyName));
                }
                
                int failCount = 1;
                while (failCount <= retryTimes) {
                    // 等待指定时间ms
                    try {
                        Thread.sleep(retryWait);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (redisCacheSvc.keyLock(keyName, keepMinTime)) {
                        // 执行主逻辑
                        return  pjp.proceed();
                    } else {
                        log.info(String.format("{%s}已经被锁, 正在重试[ %s/%s ],重试间隔{%s}毫秒", keyName, failCount, retryTimes, retryWait));
                        failCount++;
                    }
                }
                
                throw new RuntimeException("系统繁忙, 请稍等再试");
    
            }
            
    
        }
    
    
        /**
         * 获取锁参数
         * 
         * @param proceeding
         * @return
         */
        private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
            Class target = proceeding.getTarget().getClass();
            Method[] methods = target.getMethods();
            String methodName = proceeding.getSignature().getName();
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Map<String, Object> result = new HashMap<String, Object>();
                    RedisLock redisLock = method.getAnnotation(RedisLock.class);
                    result.put(LOCK_NAME, redisLock.lockName());
                    result.put(RETRY_TIMES, redisLock.retryTimes());
                    result.put(RETRY_WAIT, redisLock.retryWait());
                    result.put(KEEP_MIN_TIME, redisLock.keeyMinTime());
                    return result;
                }
            }
            return null;
        }
    
        private Method returnMethod(ProceedingJoinPoint pjp)
                throws NoSuchMethodException {
            Signature signature = pjp.getSignature();
            Class<? extends Object> cls = pjp.getTarget().getClass();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method targetMethod = methodSignature.getMethod();
            Method method = cls.getDeclaredMethod(signature.getName(),
                    targetMethod.getParameterTypes());
            return method;
        }
    
        /**
         * 获取缓存的key key 定义在注解上,支持SPEL表达式
         * 
         * @param pjp
         * @return
         */
        private String parseKey(String key, Method method, Object[] args) {
    
            // 获取被拦截方法参数名列表(使用Spring支持类库)
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paraNameArr = u.getParameterNames(method);
    
            // 使用SPEL进行key的解析
            ExpressionParser parser = new SpelExpressionParser();
            // SPEL上下文
            StandardEvaluationContext context = new StandardEvaluationContext();
            // 把方法参数放入SPEL上下文中
            for (int i = 0; i < paraNameArr.length; i++) {
                context.setVariable(paraNameArr[i], args[i]);
            }
            return parser.parseExpression(key).getValue(context, String.class);
        }
    }

    搭建完成后直接在需要锁住的接口上注解

    @RedisLock(lockName="'lock_'+#tbbId",retryTimes=5)

    模拟高并发测试

    for (int i = 0; i < 2; i++) {
                threadPoolTaskExecutor.execute(new StartTaskThread(redisCacheSvc, i, threadPoolTaskExecutor));
            }

    效果就是这样了

    菜鸟的思路,各位大神有好的思路请留言...

  • 相关阅读:
    R tips
    向量化与并行计算
    cf relevent R package
    一篇关于相似性解释的文章,写得非常的仔细
    lsa cosine R
    install lsa package for R on ubuntu 10.04 lts lucid
    updatealternatives error no alternatives for xulrunner1.9javaplugin.so 问题解决
    《如何在windows程序中读取bios内容》
    php数组根据某一个键值,把相同键值的合并生成一个新的二维数组
    阿里云域名备案之如何填写真实性核验单
  • 原文地址:https://www.cnblogs.com/tplovejava/p/10141580.html
Copyright © 2011-2022 走看看