分佈式鎖--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 單機與集羣的加鎖也不同,就是這麼簡單的道理。

相關文章
相關標籤/搜索