分布式锁--redis(集群)

原文连接:http://www.javashuo.com/article/p-omsqlapu-gy.htmljava

redis 集群作分布式锁,咱们使用 Redisson。git

框架 版本
Spring Boot 2.0.3.RELEASE
Spring Cloud Finchley.RELEASE
redis redis-4.0.11
JDK 1.8.x

maven配置github

<parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>2.0.3.RELEASE</version>

        <relativePath/> <!-- lookup parent from repository -->

    </parent>

<dependencyManagement>

        <dependencies>

            <dependency>

                <groupId>org.springframework.cloud</groupId>

                <artifactId>spring-cloud-dependencies</artifactId>

                <version>${spring-cloud.version}</version>

                <type>pom</type>

                <scope>import</scope>

            </dependency>

        </dependencies>

    </dependencyManagement>

<dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-test</artifactId>

            <scope>test</scope>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis</artifactId>

<dependency>

            <groupId>org.redisson</groupId>

            <artifactId>redisson</artifactId>

            <version>3.5.4</version>

        </dependency>

Redisson概述  Redisson是一个基于java编程框架netty进行扩展了的redis。web

    Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优点,基于Java实用工具包中经常使用接口,为使用者提供了一系列具备分布式特性的经常使用工具类。使得本来做为协调单机多线程并发程序的工具包得到了协调分布式多机多线程并发系统的能力,大大下降了设计和研发大规模分布式系统的难度。同时结合各富特点的分布式服务,更进一步简化了分布式环境中程序相互之间的协做。  redis

地址:https://github.com/redisson/redissonspring

Redisson 适用于:分布式应用,分布式缓存,分布式回话管理,分布式服务(任务,延迟任务,执行器),分布式redis客户端。数据库

还有一个重要的点须要说明编程

使用 Redisson 使用除了 上面父pom 中的依赖,还须要进行 Redisson 配置、链接、设置参数等等,这是必须的,比如使用 Jedis 你要配置一个 redisPool 的Bean同样。api

目前操做 Redisson 有三种方式缓存

第一种:纯java操做,本文就是使用这种,全部的配置都写在一个 Class 里。 第二种:spring集成操做,编写一个 xml,配置一个bean,启动还需读取这个文件,一堆很原始的操做。使用这种 xml 配置我看着都烦,强烈不推荐。 第三种:文件方式配置,是把全部配置的参数放到配置文件声明,而后在 Class 中读取。  

核心代码示例  

咱们要使用 Redisson

Redisson管理类
import org.redisson.Redisson;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
 
 
 
public class RedissonManager {
    private static Config config = new Config();
    private static RedissonClient redisson = null;
    private static final String RAtomicName = "genId_";
    public static void init(){
        try{
            config.useClusterServers()
                    .setScanInterval(200000)//设置集群状态扫描间隔
                    .setMasterConnectionPoolSize(10000)//设置对于master节点的链接池中链接数最大为10000
                    .setSlaveConnectionPoolSize(10000)//设置对于slave节点的链接池中链接数最大为500
                    .setIdleConnectionTimeout(10000)//若是当前链接池里的链接数量超过了最小空闲链接数,而同时有链接空闲时间超过了该数值,那么这些链接将会自动被关闭,并从链接池里去掉。时间单位是毫秒。
                    .setConnectTimeout(30000)//同任何节点创建链接时的等待超时。时间单位是毫秒。
                    .setTimeout(3000)//等待节点回复命令的时间。该时间从命令发送成功时开始计时。
                    .setRetryInterval(3000)//当与某个节点的链接断开时,等待与其从新创建链接的时间间隔。时间单位是毫秒。
                    .addNodeAddress("redis://127.0.0.1:7000","redis://127.0.0.1:7001","redis://127.0.0.1:7002","redis://127.0.0.1:7003","redis://127.0.0.1:7004","redis://127.0.0.1:7005");
            redisson = Redisson.create(config);
 
            RAtomicLong atomicLong = redisson.getAtomicLong(RAtomicName);
            atomicLong.set(0);//自增设置为从0开始
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static RedissonClient getRedisson(){
        if(redisson == null){
            RedissonManager.init(); //初始化
        }
        return redisson;
    }

代码解释

咱们配置了不少参数,其实一共有十来种参数,咱们只是设置几个比较重要的而已。

getRedisson  方法是使用者初始化 Redisson。

nextID 方法返回一共为 RAtomicName 变量操做了多少次,也就是我成功使用分布式锁的次数。

 

分布式锁操做类

import com.config.RedissonManager;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
 
@Component
public class RedissonLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedissonLock.class);
 
    private static RedissonClient redissonClient = RedissonManager.getRedisson();
 
 
    public void lock(String lockName) {
        String key = lockName;
        RLock myLock = redissonClient.getLock(key);
        //lock提供带timeout参数,timeout结束强制解锁,防止死锁
        myLock.lock(2, TimeUnit.SECONDS);
        // 1. 最多见的使用方法
        //lock.lock();
        // 2. 支持过时解锁功能,10秒之后自动解锁, 无需调用unlock方法手动解锁
        //lock.lock(10, TimeUnit.SECONDS);
        // 3. 尝试加锁,最多等待3秒,上锁之后10秒自动解锁
//        try {
//            boolean res = mylock.tryLock(3, 10, TimeUnit.SECONDS);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.err.println("======lock======" + Thread.currentThread().getName());
    }
 
    public void unLock(String lockName) {
        String key = lockName;
        RLock myLock = redissonClient.getLock(key);
        myLock.unlock();
        System.err.println("======unlock======" + Thread.currentThread().getName());
    }
}

测试

在咱们的 eureka 客户端启动类编辑

@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan(value = {"com.annotaion", "cn.springcloud", "com.config", "com.redislock"})
 
public class Ch34EurekaClientApplication implements ApplicationRunner {
    private static final ExecutorService executorService = Executors.newFixedThreadPool(5);
    private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
   
 
    @Autowired
    RedissonLock redissonLock;
 
    public static void main(String[] args) {
        SpringApplication.run(Ch34EurekaClientApplication.class, args);
 
    }
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
 
//******* Redis集群测试方法*********
 
        for (int i = 0; i < 5; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + "开始等待其余线程");
                        cyclicBarrier.await();
                        System.out.println(Thread.currentThread().getName() + "线程就位,即将同时执行");
                        String key = "test123";
                        redissonLock.lock(key);
                        Thread.sleep(1000); //得到锁以后能够进行相应的处理
                        System.out.println(Thread.currentThread().getName() + "获取成功,并开始执行业务逻辑");
                        redissonLock.unLock(key);
                        System.out.println(Thread.currentThread().getName() + "释放成功");
 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
 
                }
            });
        }
        executorService.shutdown();
        Long result = RedissonManager.nextID();
        System.out.print("获取redis中的原子ID" + result);
 
    }
}

输出以下:

img

我5个线程均已获取到了锁,并成功释放了。

 

总结

咱们使用 redis 单机实现分布式锁时比较简单,大多数时候能知足需求;由于是单机单实例部署,若是redis服务宕机,那么全部须要获取分布式锁的地方均没法获取锁,将所有阻塞,须要作好降级处理。 为了防止锁由于自动过时已经解锁,执行任务的进程尚未执行完,可能被其它进程从新加锁,这就形成多个进程同时获取到了锁,这须要额外的方案来解决这种问题,或者把自动释放时间加长。 redis 集群下部分节点宕机,依然能够保证锁的可用性。 当某个节点宕机后,又当即重启了,可能会出现两个客户端同时持有同一把锁,若是节点设置了持久化,出现这种状况的概率会下降。 为何使用Redisson, 由于 Redisson 是 redis 分布式方向落地的产品,应用程序单机与集群加锁的方式不同,那么redis 单机与集群的加锁也不同,就是这么简单的道理。

相关文章
相关标签/搜索