來吧,展示!Redis的分布式鎖及其實現(xiàn)Redisson的全過程

前言

分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。
在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動作。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,這個時候,便需要使用到分布式鎖。

什么是分布式鎖

1.在分布式環(huán)境中使用到的鎖就是分布式鎖
2.在分布式環(huán)境中對不同應(yīng)用程序操作的共享資源進(jìn)行加鎖就是分布式鎖

分布式環(huán)境

1.同一個應(yīng)用下的子應(yīng)用采用單獨部署的方式運行就是分布式
2.只要應(yīng)用存在跨JVM就是分布式環(huán)境

為什么要有分布式鎖

JDK中原生鎖(Synchronized、Lock)只針對是同一個JVM實例上操作資源而言,對于不同JVM的操作是沒辦法進(jìn)行加鎖的。

分布式鎖的應(yīng)用場景

只要存在跨JVM操作,并且存在共享資源競爭問題,就是必須使用到分布式鎖的場景。

分布式鎖有哪些

常見的分布式鎖有以下幾種:
1.基于數(shù)據(jù)庫(樂觀鎖)實現(xiàn)分布式鎖
2.基于Redis的分布式鎖Redisson
3.基于Zookeeper實現(xiàn)分布式鎖

基于redis分布式鎖原理

獲取鎖

通過Redis創(chuàng)建一個唯一的key,如果當(dāng)前線程能創(chuàng)建這個唯一的key,則表示當(dāng)前線程獲取到鎖。

釋放鎖

當(dāng)刪除Redis中的代表鎖的唯一key,則表示釋放鎖。

什么是死鎖

在釋放鎖時出現(xiàn)異常,Redis中代表鎖的唯一key未被刪除,而其他線程一直在自旋等待并希望能夠獲取鎖,但事實上所有線程都沒能獲取到鎖的情況稱為死鎖。

如何解決死鎖

我們可以通過對Redis中代表鎖的唯一Key設(shè)置過期時間來避免死鎖的發(fā)生。

如何避免鎖被其他線程釋放

創(chuàng)建鎖時記錄線程ID,自己的鎖只能自己釋放。

如何保證鎖的可重入性

當(dāng)線程獲取到鎖后在Redis中把當(dāng)前線程的ID做為key的值進(jìn)行存儲,加鎖時判斷當(dāng)前線程與Redis鎖的值是否一致。

基于redis分布式鎖Redisson

配置pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>site.yangpan</groupId>
        <artifactId>yangpan-spring-boot</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>site.yangpan.redission</groupId>
    <artifactId>yangpan-spring-boot-redission</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>yangpan-spring-boot-redission</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--下面這兩個依賴就是集成redission的依賴-->
        <!--第一個依賴與spring boot版本有關(guān)系,參考官方文檔-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-21</artifactId>
            <version>3.13.3</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.13.3</version>
        </dependency>
    </dependencies>

</project>

注意:網(wǎng)上很多都是直接引入redission依賴,但是我這里是通過Spring Boot Starter的方式引入

配置application.properties

# 公共spring boot配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
#spring.redis.cluster.nodes=
#spring.redis.sentinel.master=
#spring.redis.sentinel.nodes=

## Redisson 配置
spring.redis.redisson.config=classpath:redisson.yaml

配置redisson.yaml

singleServerConfig:
  idleConnectionTimeout: 10000
  pingTimeout: 1000
#  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
#  reconnectionTimeout: 3000
#  failedAttempts: 3
  password:
  subscriptionsPerConnection: 5
  clientName:
  address: "redis://127.0.0.1:6379"
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 50
  connectionMinimumIdleSize: 32
  connectionPoolSize: 64
  database: 0
#  dnsMonitoring: false
  dnsMonitoringInterval: 5000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
#"transportMode":"NIO"

編寫RedissonSpringDataConfig

接下來我們注冊 RedissonConnectionFactory 到 Spring context

package site.yangpan.redission.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;

import java.io.IOException;

/**
 * 注冊 RedissonConnectionFactory 到 Spring context
 * https://github.com/redisson/redisson/tree/master/redisson-spring-data#spring-data-redis-integration
 * Created by yangpan on 2020-08-29 19:15.
 */
@Configuration
public class RedissonSpringDataConfig {

    @Bean
    public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
        return new RedissonConnectionFactory(redisson);
    }

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
        Config config = Config.fromYAML(configFile.getInputStream());
        return Redisson.create(config);
    }

}

使用Redisson

這里我們直接編寫一個controller,然后使用Redisson

package site.yangpan.redission.reentrantLock;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 基于Redis的Redisson分布式可重入鎖RLock
 * Java對象實現(xiàn)了java.util.concurrent.locks.Lock接口。
 * 同時還提供了異步(Async)、反射式(Reactive)和RxJava2標(biāo)準(zhǔn)的接口。
 * Created by yangpan on 2020-08-29 19:40.
 */
@Controller
@RestController
@RequestMapping("/redissionReentrantLock")
public class RedissonReentrantLock {

    private Integer stock = 100;

    @Autowired
    private RedissonClient redissonClient;

    @GetMapping("/test")
    public void test(){
        //使用線程池模擬并發(fā),看分布式鎖有沒有問題
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        for(int i=0;i<=1000;i++){
            executorService.execute(() -> {
                try {
                    Thread.sleep(3000);
                    //調(diào)用加鎖方法
                    reduceStockRessionLock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    /**
     * 加鎖情況
     */
    private void reduceStockRessionLock() {
        //獲取鎖(可重入鎖)
        RLock lock = redissonClient.getLock("anyLock");

        //加鎖
        lock.lock();

        //業(yè)務(wù)操作
        if(stock > 0){
            stock--;
            System.out.println("當(dāng)前庫存剩余:" + stock);
        }

        //釋放鎖
        lock.unlock();
    }

    /**
     * 不加鎖情況
     */
    private void reduceStock() throws InterruptedException {
        //業(yè)務(wù)操作
        if(stock > 0){
            stock--;
            System.out.println("當(dāng)前庫存剩余:" + stock);
        }
    }

}

啟動測試

接下來我們啟動項目,訪問接口,查看日志,觀察日志分析可得Redisson分布式鎖確實起到了作用

curl http://localhost:8080/redissionReentrantLock/test

最后

感謝你看到這里,看完有什么的不懂的可以在評論區(qū)問我,覺得文章對你有幫助的話記得給我點個贊,每天都會分享java相關(guān)技術(shù)文章或行業(yè)資訊,歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容