前言
分布式鎖是控制分布式系統(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ā)文章!