SpringBoot:Redis分布式锁

概述

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

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

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

尽管有这三种方案,可是不一样的业务也要根据本身的状况进行选型,他们之间没有最好只有更适合!但因为操做数据库须要必定的开销,会照成必定的性能问题。而且使用数据库行级锁并不必定靠谱,尤为是当咱们锁表并不大的时候,因此实际开发过程当中使用Redis或Zookeeper的比较多。数据库

分布式锁特性

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

一、互斥

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

二、防死锁

在分布式高并发的条件下,好比有个线程得到锁的同时,尚未来得及去释放锁,就由于系统故障或者其它缘由使它没法执行释放锁的命令,致使其它线程都没法得到锁,形成死锁。因此分布式很是有必要设置锁的有效时间,确保系统出现故障后,在必定时间内可以主动去释放锁,避免形成死锁的状况。服务器

三、性能

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

四、重入

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

基于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

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息