zoukankan      html  css  js  c++  java
  • Redission锁的设计原理和应用

    Redission锁的设计原理和应用

    一:基本使用方法

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.8.2</version>
    </dependency>
    
    @Test
    public void tt() {
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6399").setDatabase(0);
        // 构造RedissonClient
        RedissonClient redissonClient = Redisson.create(config);
        // 设置锁定资源名称
        RLock disLock = redissonClient.getLock("helloRedissionLock");
        boolean isLock;
        try {
            //尝试获取分布式锁
            isLock = disLock.tryLock(500, 15000, TimeUnit.MILLISECONDS);
            if (isLock) {
                //TODO if get lock success, do something;
                Thread.sleep(15000);
            }
        } catch (Exception e) {
        } finally {
            // 无论如何, 最后都要解锁
            disLock.unlock();
        }
    }
    

    二:通过源码解读设计原理

    2.1获取锁(关键代码如下)
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
              "if (redis.call('exists', KEYS[1]) == 0) then " +
                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              "return redis.call('pttl', KEYS[1]);",
                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    

    解读:这是一段lua的脚本

    第一个if:判断key = helloRedissionLock是否存在?若不存(==0)在则通过hset命令设置 key = helloRedissionLock的hash对象(key=当前线程ID:1 value=1),并且设置过期时间

    第二个if:判断key = helloRedissionLock和hash对象(key=当前线程ID:1 value=1)?若存在(==1)则:1.设置hash对象的value+1,2.重新设置过期时间

    return:key = helloRedissionLock的过期时间

    备注:Hincrby 命令用于为哈希表中的字段值加上指定增量值,该点是reentrant lock的关键

    2.2 解锁
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; " +
            "end;" +
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
            "end; " +
            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
            "if (counter > 0) then " +
                "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                "return 0; " +
            "else " +
                "redis.call('del', KEYS[1]); " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; "+
            "end; " +
            "return nil;",
            Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
    

    解读:

    第一个if:判断key=入参是否存在?若不存在(==0),广播机制,然后返回1。

    第二个if:判断key和value是否存在?若不存在(==0 返回ni l。

    脚本(存在):local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);获取value

    第三个if:判断counter是否大于0?true-->value-1:

    else:删除key并广播

    2.3 等待
    while (true) {
        long currentTime = System.currentTimeMillis();
        ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return true;
        }
    		......
    }
    

    解读:

    通过while(true)实现线程等待

    三:应用实例(Springboot)

    3.1 简介:在分布式或者多线程场景下,为了让一个数据或方法(数据来源数据库)在同一时刻只能让一个人处理(即串性),我们可以利用Redission锁让只有拿到锁的人才能编辑该数据。

    实现原理:利用切面编程+注解(RedissionLock)编程的方式实现对方法上加入了RedissionLock注解的方法进行全局加锁

    3.2 定义注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RedissionLock {
        /**
         * 锁住方法中的第几个参数(-1全部参数)
         *
         * @return
         */
        int lockIndexParam() default -1;
    
        /**
         * 锁的时间
         *
         * @return
         */
        int leaseTime() default 10;
    
        /**
         * 等待时间
         */
        int waitTime() default 5;
    }
    

    3.3 注册切面

    @Aspect
    @Component
    public class RedissionLockAspect {
        @Resource
        private RedissonClient redissonClient;
    
        /**
         * 环绕通知:灵活自由的在目标方法中切入代码
         */
        @Around("@annotation(redissionLock)")
        public Object around(ProceedingJoinPoint joinPoint, RedissionLock redissionLock) throws Throwable {
            // 获取目标方法的名称
            String methodName = joinPoint.getSignature().getName();
            // 获取方法传入参数
            Object[] params = joinPoint.getArgs();
            System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);
            RLock lock = redissonClient.getLock(params[redissionLock.lockIndexParam()-1].toString());
            boolean b = lock.tryLock(redissionLock.waitTime(), redissionLock.leaseTime(), TimeUnit.MINUTES);
            if (b) {
                lock.unlock();
                return joinPoint.proceed();
            } else {
                return null;
            }
    
        }
     }
    

    3.4 使用方式

    @RedissionLock(lockIndexParam = 1)
    public void tt(String id){
        System.out.println("进入了方法::" + id);
    }
    
  • 相关阅读:
    我国自主研发的先进辅助驾驶系统(ADAS)控制器产品实现量产配套
    国务院印发《新一代人工智能发展规划》
    如何删除svn标签
    【转载】DHCP流程
    1-1 如何进行自我规划?
    人要像鹰一样重生、蜕变、成长
    人的一生好像乘坐北京地铁一号线
    【转载】接触Matlab10年后的一个总结,随时使用Matlab要掌握的一些要点
    【转载】BAT 批处理脚本教程
    SNAT 和 DNAT
  • 原文地址:https://www.cnblogs.com/jinliang374003909/p/15139452.html
Copyright © 2011-2022 走看看