• SpringBoot:Redis分布式锁


    概述

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

    在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

    基于数据库实现分布式锁; 
    基于缓存(Redis等)实现分布式锁; 
    基于Zookeeper实现分布式锁;

    尽管有这三种方案,但是不同的业务也要根据自己的情况进行选型,他们之间没有最好只有更适合!但由于操作数据库需要一定的开销,会照成一定的性能问题。并且使用数据库行级锁并不一定靠谱,尤其是当我们锁表并不大的时候,所以实际开发过程中使用Redis或Zookeeper的比较多。

    分布式锁特性

    当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。

    1、互斥

    在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。

    2、防死锁

    在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。所以分布式非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。

    3、性能

    高并发分布式系统中,线程互斥等待会成为性能瓶颈,需要好的中间件和实现来保证性能。

    4、重入

    我们知道ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。关于这点之后会做演示。针对以上Redisson都能很好的满足,下面就来分析下它。

    基于Redisson的实现

    为了更好的理解分布式锁的原理,我这边自己画张图通过这张图来分析。

    加锁机制:

    线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。

    线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。

    watch dog看门狗:

    在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生。但在实际开发中会有下面一种情况:

        //设置锁1秒过去
            redissonLock.lock("redisson", 1);
            /**
             * 业务逻辑需要咨询2秒
             */
            redissonLock.release("redisson");
    
          /**
           * 线程1 进来获得锁后,线程一切正常并没有宕机,但它的业务逻辑需要执行2秒,这就会有个问题,在 线程1 执行1秒后,这个锁就自动过期了,
           * 那么这个时候 线程2 进来了。那么就存在 线程1和线程2 同时在这段业务逻辑里执行代码,这当然是不合理的。
           * 而且如果是这种情况,那么在解锁时系统会抛异常,因为解锁和加锁已经不是同一线程了,具体后面代码演示。
           */
    

    所以这个时候看门狗就出现了,它的作用就是 线程1 业务还没有执行完,时间就过了,线程1 还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间。注意 正常这个看门狗线程是不启动的,还有就是这个看门狗启动后对整体性能也会有一定影响,所以不建议开启看门狗。

     代码实现

    首先在pom里依赖Redisson

                <!-- redisson -->
                <dependency>
                    <groupId>org.redisson</groupId>
                    <artifactId>redisson</artifactId>
                    <version>${org-redisson.version}</version>
                </dependency>    

       在公共模块配置Redisson

    import org.redisson.Redisson;
    import org.redisson.config.Config;
    import org.redisson.config.SingleServerConfig;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import java.util.ResourceBundle;
    
    /**
     * <p>实例Redisson类</p>
     *
     * @author lft
     * @version 1.0
     * @date 2019/6/28 0028
     * @since jdk1.8
     */
    @Component
    public class RedissonManager {
    
        private static Logger logger = LoggerFactory.getLogger(RedissonManager.class);
        private static String REDIS_IP;
        private static String REDIS_PORT;
        private static String REDIS_PASSWD;
        private static String PROJECT_ENV;
    
        @Value("${spring.profiles.active}")
        public void setProjectEnv(String projectEnv) {
            PROJECT_ENV = projectEnv;
        }
        private static Config config = new Config();
        private static Redisson redisson = null;
    
        private static void init() {
            ResourceBundle properties = ResourceBundle.getBundle("application-"+PROJECT_ENV);
            REDIS_IP = properties.getString("spring.redis.host").trim();
            REDIS_PORT = properties.getString("spring.redis.port").trim();
            if(properties.containsKey("spring.redis.password")){
                REDIS_PASSWD = properties.getString("spring.redis.password");
            }
            SingleServerConfig singleServerConfig = config.useSingleServer();
            singleServerConfig.setAddress("redis://"+REDIS_IP+":"+REDIS_PORT);
            if(null != REDIS_PASSWD && !"".equals(REDIS_PASSWD)){
                singleServerConfig.setPassword(REDIS_PASSWD);
            }
            //得到redisson对象
            redisson = (Redisson) Redisson.create(config);
        }
        //实例化redisson
        public static Redisson getInstance(){
            init();
            return redisson;
        }
    }
    

      加锁和释放锁的方法

    import org.redisson.Redisson;
    import org.redisson.api.RLock;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * <p>Redisson的使用</p>
     *
     * @author lft
     * @version 1.0
     * @date 2019/6/28 0028
     * @since jdk1.8
     */
    public class DistributedRedisLock {
    
        private static Logger logger = LoggerFactory.getLogger(DistributedRedisLock.class);
        private static Redisson redisson = RedissonManager.getInstance();
        private static final String LOCK_TITLE = "redis_lock_";
        //加锁
        public static boolean lockup(String lockName){
            //声明key对象
            String key = LOCK_TITLE + lockName;
            //获取锁对象
            RLock mylock = redisson.getLock(key);
            //设置过期时间,防止死锁
            mylock.lock(60, TimeUnit.SECONDS);
            logger.info("======lock======"+Thread.currentThread().getName());
            return true;
        }
        //锁的释放
        public static void release(String lockName){
            String key = LOCK_TITLE + lockName;
            RLock mylock = redisson.getLock(key);
            //释放锁(解锁)
            mylock.unlock();
            logger.info("======unlock======"+Thread.currentThread().getName());
        }
    }
    

      业务逻辑使用锁

    /**
         * 测试Redis锁
         * @param request
         * @return
         * @throws Exception
         */
        @PostMapping("/lockTest")
        public Object lockTest(HttpServletRequest request) throws Exception{
            String key = "test";
            //加锁
            DistributedRedisLock.lockup(key);
            //TODO 业务代码
            LOGGER.info("=========================执行测试任务");
            //释放锁
            DistributedRedisLock.release(key);
            return ResultUtils.success("锁测试结束!");
        }
    

      

    zookeeper分布式锁参考:https://cloud.tencent.com/developer/article/1462302

  • 相关阅读:
    document.load 和document.ready 两个事件的区别
    undefined和null的区别
    点击返回上一个界面事件
    APP上点击事件时 取消点击的阴影
    MUI控制安卓手机自带的返回键 禁止返回
    css过滤镜实现颜色渐变
    CSS3 Media Queries模板:max-width和min-width
    什么是响应式网页设计?
    实用的CSS3-渐变背景色
    Oracle EBS 报表日期格式问题
  • 原文地址:https://www.cnblogs.com/mengY/p/12196980.html
Copyright © 2020-2023  润新知