利用consul在spring boot中實現(xiàn)分布式鎖場景分析
因為在項目實際過程中所采用的是微服務架構(gòu),考慮到承載量基本每個相同業(yè)務的服務都是多節(jié)點部署,所以針對某些資源的訪問就不得不用到用到分布式鎖了。
這里列舉一個最簡單的場景,假如有一個智能售貨機,由于機器本身的原因不能同一臺機器不能同時出兩個商品,這就要求在在出貨流程前針對同一臺機器在同一時刻出現(xiàn)并發(fā)創(chuàng)建訂單時只能有一筆訂單創(chuàng)建成功,但是訂單服務是多節(jié)點部署的,所以就不得不用到分布式鎖了。
以上只是一種簡單的業(yè)務場景,在各種大型互聯(lián)網(wǎng)實際應用中,需要分布式鎖的業(yè)務場景會更多,綜合比較了業(yè)界基于各種中間件來實現(xiàn)的分布式鎖方案,然后結(jié)合實際業(yè)務最終決定采用consul來實現(xiàn),因為我們的項目中采用了consul做注冊中心,并且consul天生可以保證一致性(這點類似zk),當然zk也能實現(xiàn)分布式鎖,但是這里不對這點做過多討論。
redis雖然也能實現(xiàn)分布式鎖,但是可能因為場景比較復雜,如果redis采用cluster部署的話,如果某一主節(jié)點出現(xiàn)故障的話,有一定幾率會出現(xiàn)腦裂現(xiàn)象,這樣就可能會讓競爭者在并發(fā)時同時獲得到鎖,這樣可能會破壞掉后面的業(yè)務,當然出現(xiàn)這種情況的概率很低,但是也不能完全排除,因為redis的根本不能保證強一致性導致的。
好了,這里說的最簡單的分布式鎖的意思是,多個競爭者同一時間并發(fā)去獲得鎖時,獲取失敗的就直接返回了,獲取成功的繼續(xù)后續(xù)的流程,然后在合適的時間釋放鎖,并且為鎖加了超時時間,防止獲得到鎖的進程或線程在未來得及釋放鎖時自己掛掉了,導致資源處于一直被鎖定的狀態(tài)無法得到釋放。主要的實現(xiàn)邏輯就是這樣,如果有人想實現(xiàn)獲得鎖失
敗的競爭者一直繼續(xù)嘗試獲得,可以基于該示例進行修改,加上自旋邏輯就OK。
以下是鎖實現(xiàn)代碼:
package com.lyb.consullock;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.agent.model.NewCheck;
import com.ecwid.consul.v1.kv.model.PutParams;
import com.ecwid.consul.v1.session.model.NewSession;
import com.ecwid.consul.v1.session.model.Session;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class DistributedLock{
private ConsulClient consulClient;
/**
* 構(gòu)造函數(shù)
* @param consulHost 注冊consul的client或服務端的Ip或主機名,或域名
* @param consulPort 端口號
*/
public DistributedLock(String consulHost,int consulPort){
consulClient = new ConsulClient(consulHost,consulPort);
}
/**
* 獲得鎖的方法
* @param lockName 競爭的資源名
* @param ttlSeconds 鎖的超時時間,超過該時間自動釋放
* @return
*/
public LockContext getLock(String lockName,int ttlSeconds){
LockContext lockContext = new LockContext();
if(ttlSeconds<10 || ttlSeconds > 86400) ttlSeconds = 60;
String sessionId = createSession(lockName,ttlSeconds);
boolean success = lock(lockName,sessionId);
if(success == false){
consulClient.sessionDestroy(sessionId,null);
lockContext.setGetLock(false);
return lockContext;
}
lockContext.setSession(sessionId);
lockContext.setGetLock(true);
return lockContext;
}
/**
* 釋放鎖
* @param sessionID
*/
public void releaseLock(String sessionID){
consulClient.sessionDestroy(sessionID,null);
}
private String createSession(String lockName,int ttlSeconds){
NewCheck check = new NewCheck();
check.setId("check "+lockName);
check.setName(check.getId());
check.setTtl(ttlSeconds+"s"); //該值和session ttl共同決定決定鎖定時長
check.setTimeout("10s");
consulClient.agentCheckRegister(check);
consulClient.agentCheckPass(check.getId());
NewSession session = new NewSession();
session.setBehavior(Session.Behavior.RELEASE);
session.setName("session "+lockName);
session.setLockDelay(1);
session.setTtl(ttlSeconds + "s"); //和check ttl共同決定鎖時長
List<String> checks = new ArrayList<>();
checks.add(check.getId());
session.setChecks(checks);
String sessionId = consulClient.sessionCreate(session,null).getValue();
return sessionId;
}
private boolean lock(String lockName,String sessionId){
PutParams putParams = new PutParams();
putParams.setAcquireSession(sessionId);
boolean isSuccess = consulClient.setKVValue(lockName,"lock:"+ LocalDateTime.now(),putParams).getValue();
return isSuccess;
}
/**
* 競爭鎖時返回的對象
*/
@Data
public class LockContext{
/**
* 獲得鎖成功返回該值,比便后面用該值來釋放鎖
*/
private String session;
/**
* 是否獲得到鎖
*/
private boolean isGetLock;
}
}
pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lyb</groupId>
<artifactId>consul-lock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consul-lock</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
測試代碼:
package com.lyb.consullock;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsulLockApplicationTests {
@Autowired
private ServiceConfig serviceConfig;
@Test
public void lockSameResourer() {
//針對相同資源在同一時刻只有一個線程會獲得鎖
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int a=0;a<20;a++){
threadPool.submit(
() -> {
for (int i = 0;i < 100; i++) {
DistributedLock lock = new DistributedLock(
serviceConfig.getConsulRegisterHost(),
serviceConfig.getConsulRegisterPort());
DistributedLock.LockContext lockContext = lock.getLock("test lock", 10);
if (lockContext.isGetLock()) {
System.out.println(Thread.currentThread().getName() + "獲得了鎖");
try {
TimeUnit.SECONDS.sleep(1);
lock.releaseLock(lockContext.getSession());
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//System.out.println(Thread.currentThread().getName() + "沒有獲得鎖");
}
}
});
}
try {
TimeUnit.MINUTES.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void lockDiffResource(){
//針對不通的資源所有線程都應該能獲得鎖
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int a=0;a<20;a++){
threadPool.submit(
() -> {
for (int i = 0;i < 100; i++) {
DistributedLock lock = new DistributedLock(
serviceConfig.getConsulRegisterHost(),
serviceConfig.getConsulRegisterPort());
DistributedLock.LockContext lockContext = lock.getLock("test lock"+Thread.currentThread().getName(), 10);
if (lockContext.isGetLock()) {
System.out.println(Thread.currentThread().getName() + "獲得了鎖");
try {
TimeUnit.SECONDS.sleep(1);
lock.releaseLock(lockContext.getSession());
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//System.out.println(Thread.currentThread().getName() + "沒有獲得鎖");
Assert.assertTrue(lockContext.isGetLock());
}
}
});
}
try {
TimeUnit.MINUTES.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
項目路徑:
https://github.com/wenwuxianren/consul-lock
到此這篇關(guān)于利用consul在spring boot中實現(xiàn)最簡單的分布式鎖的文章就介紹到這了,更多相關(guān)spring boot分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot 集成 ShedLock 分布式鎖的示例詳解
- SpringBoot之使用Redis實現(xiàn)分布式鎖(秒殺系統(tǒng))
- Redis分布式鎖升級版RedLock及SpringBoot實現(xiàn)方法
- SpringBoot中使用redis做分布式鎖的方法
- SpringBoot整合Redis正確的實現(xiàn)分布式鎖的示例代碼
- SpringBoot使用Redis實現(xiàn)分布式鎖
- SpringBoot + Spring Cloud Consul 服務注冊和發(fā)現(xiàn)詳細解析
- Spring boot2X Consul如何使用Feign實現(xiàn)服務調(diào)用
- Spring boot2X Consul如何通過RestTemplate實現(xiàn)服務調(diào)用
相關(guān)文章
執(zhí)行java請求時導致在腳本執(zhí)行結(jié)束時JVM無法退出
這篇文章主要介紹了執(zhí)行java請求,導致在腳本執(zhí)行結(jié)束時JVM無法退出問題,本文通過原因分析給出解決方案,需要的朋友可以參考下2020-02-02
Springboot配置suffix指定mvc視圖的后綴方法
這篇文章主要介紹了Springboot配置suffix指定mvc視圖的后綴方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
java 網(wǎng)絡編程之TCP通信和簡單的文件上傳功能實例
下面小編就為大家分享一篇java 網(wǎng)絡編程之TCP通信和簡單的文件上傳功能實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01

