web界面設(shè)計seo收費標準
分布式鎖
1 分布式鎖介紹
1.1 什么是分布式
一個大型的系統(tǒng)往往被分為幾個子系統(tǒng)來做,一個子系統(tǒng)可以部署在一臺機器的多個 JVM(java虛擬機) 上,也可以部署在多臺機器上。但是每一個系統(tǒng)不是獨立的,不是完全獨立的。需要相互通信,共同實現(xiàn)業(yè)務(wù)功能。
一句話來說:分布式就是通過計算機網(wǎng)絡(luò)將后端工作分布到多臺主機上,多個主機一起協(xié)同完成工作。
1.2 什么是鎖–作用安全
現(xiàn)實生活中,當我們需要保護一樣?xùn)|西的時候,就會使用鎖。例如門鎖,車鎖等等。很多時候可能許多人會共用這些資源,就會有很多個鑰匙。但是有些時候我們希望使用的時候是獨自不受打擾的,那么就會在使用的時候從里面反鎖,等使用完了再從里面解鎖。這樣其他人就可以繼續(xù)使用了。
- 鎖 單進程的系統(tǒng)中,存在多線程同時操作一個公共變量,此時需要加鎖對變量進行同步操作,保證多線程的操作線性執(zhí)行消除并發(fā)修改。解決的是單進程中的多線程并發(fā)問題。
1.4 什么是分布式鎖
任何一個分布式系統(tǒng)都無法同時滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition tolerance),最多只能同時滿足兩項。CAP
當在分布式模型下,數(shù)據(jù)只有一份(或有限制),此時需要利用鎖的技術(shù)控制某一時刻修改數(shù)據(jù)的進程數(shù)。
分布式鎖: 在分布式環(huán)境下,多個程序/線程都需要對某一份(或有限制)的數(shù)據(jù)進行修改時,針對程序進行控制,保證同一時間節(jié)點下,只有一個程序/線程對數(shù)據(jù)進行操作的技術(shù)。
1.5 分布式鎖的真實使用場景
場景一:
場景二:
1.5 分布式鎖的執(zhí)行流程
1.6 分布式鎖具備的條件
- 互斥性:同一時刻只能有一個服務(wù)(或應(yīng)用)訪問資源,特殊情況下有讀寫鎖
- 原子性:一致性要求保證加鎖和解鎖的行為是原子性的
- 安全性:鎖只能被持有該鎖的服務(wù)(或應(yīng)用)釋放
- 容錯性:在持有鎖的服務(wù)崩潰時,鎖仍能得到釋放避免死鎖
- 可重用性:同一個客戶端獲得鎖后可遞歸調(diào)用—重入鎖和不可重入鎖
- 公平性:看業(yè)務(wù)是否需要公平,避免餓死–公平鎖和非公平鎖
- 支持阻塞和非阻塞:和 ReentrantLock 一樣支持 lock 和 trylock 以及 tryLock(long timeOut)—阻塞鎖和非阻塞鎖PS:::自選鎖
- 高可用:獲取鎖和釋放鎖 要高可用
- 高性能:獲取鎖和釋放鎖的性能要好
- 持久性:鎖按業(yè)務(wù)需要自動續(xù)約/自動延期
2.分布式鎖的解決方案
2.1 數(shù)據(jù)庫實現(xiàn)分布式鎖
基于數(shù)據(jù)庫實現(xiàn)分布式鎖主要是利用數(shù)據(jù)庫的唯一索引來實現(xiàn),唯一索引天然具有排他性,這剛好符合我們對鎖的要求:同一時刻只能允許一個競爭者獲取鎖。加鎖時我們在數(shù)據(jù)庫中插入一條鎖記錄,利用業(yè)務(wù)id進行防重。當?shù)谝粋€競爭者加鎖成功后,第二個競爭者再來加鎖就會拋出唯一索引沖突,如果拋出這個異常,我們就判定當前競爭者加鎖失敗。防重業(yè)務(wù)id需要我們自己來定義,例如我們的鎖對象是一個方法,則我們的業(yè)務(wù)防重id就是這個方法的名字,如果鎖定的對象是一個類,則業(yè)務(wù)防重id就是這個類名。
2.1.1 基于數(shù)據(jù)庫表實現(xiàn)
準備工作:創(chuàng)建tb_program表,用于記錄當前哪個程序正在使用數(shù)據(jù)
CREATE TABLE `tb_program` (`program_no` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '程序的編號'PRIMARY KEY (`program_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
實現(xiàn)步驟:
- 程序訪問數(shù)據(jù)時,將程序的編號(insert)存入tb_program表;
- 當insert成功,代表該程序獲得了鎖,即可執(zhí)行邏輯;
- 當program_no相同的其他程序進行insert是,由于主鍵沖突會導(dǎo)致insert失敗,則代表獲取鎖失敗;
- 獲取鎖成功的程序在邏輯執(zhí)行完以后,刪除該數(shù)據(jù),代表釋放鎖。
2.1.2 基于數(shù)據(jù)庫的排他鎖實現(xiàn)
除了可以通過增刪操作數(shù)據(jù)表中的記錄以外,其實還可以借助數(shù)據(jù)中自帶的鎖來實現(xiàn)分布式的鎖。我們還用剛剛創(chuàng)建的那張數(shù)據(jù)庫表,基于MySql的InnoDB引擎(MYSQL的引擎種類)可以通過數(shù)據(jù)庫的排他鎖來實現(xiàn)分布式鎖。
實現(xiàn)步驟:
- 在查詢語句后面增加
for update
,數(shù)據(jù)庫會在查詢過程中給數(shù)據(jù)庫表增加排他鎖。當某條記錄被加上排他鎖之后,其他線程無法再在該行記錄上增加排他鎖 - 獲得排它鎖的線程即可獲得分布式鎖,執(zhí)行方法的業(yè)務(wù)邏輯
- 執(zhí)行完方法之后,再通過
connection.commit();
操作來釋放鎖。
實現(xiàn)代碼
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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.maweiqi</groupId><artifactId>mysql-demo</artifactId><version>1.0-SNAPSHOT</version><!--依賴包--><dependencies><!--核心包--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-core</artifactId><version>5.3.1</version></dependency><!--一般分詞器,適用于英文分詞--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-analyzers-common</artifactId><version>5.3.1</version></dependency><!--中文分詞器--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-analyzers-smartcn</artifactId><version>5.3.1</version></dependency><!--對分詞索引查詢解析--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-queryparser</artifactId><version>5.3.1</version></dependency><!--檢索關(guān)鍵字高亮顯示--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-highlighter</artifactId><version>5.3.1</version></dependency><!-- MySql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.32</version></dependency><!-- Test dependencies --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies>
</project>
Book
public class Book {// 圖書IDprivate Integer id;// 圖書名稱private String name;// 圖書價格private Float price;// 圖書圖片private String pic;// 圖書描述private String desc;
}
BookDao
public interface BookDao {/*** 查詢所有的book數(shù)據(jù)* @return*/List<Book> queryBookList(String name) throws Exception;
}
BookDaoImpl實現(xiàn)類
public class BookDaoImpl implements BookDao {/**** 查詢數(shù)據(jù)庫數(shù)據(jù)* @return* @throws Exception*/public List<Book> queryBookList(String name) throws Exception{// 數(shù)據(jù)庫鏈接Connection connection = null;// 預(yù)編譯statementPreparedStatement preparedStatement = null;// 結(jié)果集ResultSet resultSet = null;// 圖書列表List<Book> list = new ArrayList<Book>();try {// 加載數(shù)據(jù)庫驅(qū)動Class.forName("com.mysql.jdbc.Driver");// 連接數(shù)據(jù)庫connection = DriverManager.getConnection("jdbc:mysql://39.108.189.37:3306/lucene", "root", "root");//關(guān)閉自動提交connection.setAutoCommit(false);// SQL語句String sql = "SELECT * FROM book where id = 1 for update";// 創(chuàng)建preparedStatementpreparedStatement = connection.prepareStatement(sql);// 獲取結(jié)果集resultSet = preparedStatement.executeQuery();// 結(jié)果集解析while (resultSet.next()) {Book book = new Book();book.setId(resultSet.getInt("id"));book.setName(resultSet.getString("name"));list.add(book);}System.out.println(name + "執(zhí)行了for update");System.out.println("結(jié)果為:" + list);//鎖行后休眠5秒Thread.sleep(5000);//休眠結(jié)束釋放connection.commit();System.out.println(name + "結(jié)束");} catch (Exception e) {e.printStackTrace();}return list;}
}
測試類
public class Test {private BookDao bookDao = new BookDaoImpl();@org.junit.Testpublic void testLock() throws Exception {new Thread(new LockRunner("線程1")).start();new Thread(new LockRunner("線程2")).start();new Thread(new LockRunner("線程3")).start();new Thread(new LockRunner("線程4")).start();new Thread(new LockRunner("線程5")).start();Thread.sleep(200000L);}class LockRunner implements Runnable {private String name;public LockRunner(String name) {this.name = name;}public void run() {try {bookDao.queryBookList(name);}catch (Exception e){e.printStackTrace();}}}
}
執(zhí)行結(jié)果
2.1.3 優(yōu)點及缺點
優(yōu)點: 簡單,方便,快速實現(xiàn)
缺點: 基于數(shù)據(jù)庫,開銷比較大,對數(shù)據(jù)庫性能可能會存在影響
2.2 Redis實現(xiàn)分布式鎖
2.2.1 基于 REDIS 的 SETNX()、EXPIRE() 、GETSET()方法做分布式鎖
實現(xiàn)原理:
setnx():setnx 的含義就是 SET if Not Exists,其主要有兩個參數(shù) setnx(key, value)。該方法是原子的,如果 key 不存在,則設(shè)置當前 key 成功,返回 1;如果當前 key 已經(jīng)存在,則設(shè)置當前 key 失敗,返回 0
expire():expire 設(shè)置過期時間,要注意的是 setnx 命令不能設(shè)置 key 的超時時間,只能通過 expire() 來對 key 設(shè)置。
getset():這個命令主要有兩個參數(shù) getset(key,newValue)。該方法是原子的,對 key 設(shè)置 newValue 這個值,并且返回 key 原來的舊值。假設(shè) key 原來是不存在的,那么多次執(zhí)行這個命令,會出現(xiàn)下邊的效果:getset(key, “value1”) 返回 null 此時 key 的值會被設(shè)置為 value1
getset(key, “value2”) 返回 value1 此時 key 的值會被設(shè)置為 value2
實現(xiàn)流程:
- setnx(lockkey, 當前時間+過期超時時間),如果返回 1,則獲取鎖成功;如果返回 0 則沒有獲取到鎖。
- get(lockkey) 獲取值 oldExpireTime ,并將這個 value 值與當前的系統(tǒng)時間進行比較,如果小于當前系統(tǒng)時間,則認為這個鎖已經(jīng)超時,可以允許別的請求重新獲取。
- 計算 newExpireTime = 當前時間+過期超時時間,然后 getset(lockkey, newExpireTime) 會返回當前 lockkey 的值currentExpireTime。判斷 currentExpireTime 與 oldExpireTime 是否相等,如果相等,說明當前 getset 設(shè)置成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那么當前請求可以直接返回失敗,或者繼續(xù)重試。
- 在獲取到鎖之后,當前線程可以開始自己的業(yè)務(wù)處理,當處理完畢后,比較自己的處理時間和對于鎖設(shè)置的超時時間,如果小于鎖設(shè)置的超時時間,則直接執(zhí)行 delete 釋放鎖;如果大于鎖設(shè)置的超時時間,則不需要再鎖進行處理。
代碼實現(xiàn)
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>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.maweiqi</groupId><artifactId>redis</artifactId><version>0.0.1-SNAPSHOT</version><name>redis</name><description>redis實現(xiàn)分布式鎖測試</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><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><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
RedisUtil工具類
@Component
public class RedisUtil {//定義默認超時時間:單位毫秒private static final Integer LOCK_TIME_OUT = 10000;@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 外部調(diào)用加鎖方法*/public Boolean tryLock(String key, Long timeout) throws Exception{//獲取當前系統(tǒng)時間設(shè)置為開始時間Long startTime = System.currentTimeMillis();//設(shè)置返回默認值-false:加鎖失敗boolean flag = false;//死循環(huán)獲取鎖:1.獲取鎖成功退出 2.獲取鎖超時退出while(true){//判斷是否超時if((System.currentTimeMillis() - startTime) >= timeout){break;}else{//獲取鎖flag = lock(key);//判斷是否獲取成功if(flag){break;}else{//休息0.1秒重試,降低服務(wù)壓力Thread.sleep(100);}}}return flag;}/*** 加鎖實現(xiàn)* @param key* @return*/private Boolean lock(String key){return (Boolean) stringRedisTemplate.execute((RedisCallback) redisConnection -> {//獲取當前系統(tǒng)時間Long time = System.currentTimeMillis();//設(shè)置鎖超時時間Long timeout = time + LOCK_TIME_OUT + 1;//setnx加鎖并獲取解鎖結(jié)果Boolean result = redisConnection.setNX(key.getBytes(), String.valueOf(timeout).getBytes());//加鎖成功返回trueif(result){return true;}//加鎖失敗判斷鎖是否超時if(checkLock(key, timeout)){//getset設(shè)置值成功后,會返回舊的鎖有效時間byte[] newtime = redisConnection.getSet(key.getBytes(), String.valueOf(timeout).getBytes());if(time > Long.valueOf(new String(newtime))){return true;}}//默認加鎖失敗return false;});}/*** 釋放鎖*/public Boolean release(String key){return (Boolean) stringRedisTemplate.execute((RedisCallback) redisConnection -> {Long del = redisConnection.del(key.getBytes());if (del > 0){return true;}return false;});}/*** 判斷鎖是否超時*/private Boolean checkLock(String key, Long timeout){return (Boolean) stringRedisTemplate.execute((RedisCallback) redisConnection -> {//獲取鎖的超時時間byte[] bytes = redisConnection.get(key.getBytes());try {//判斷鎖的有效時間是否大與當前時間if(timeout > Long.valueOf(new String(bytes))){return true;}}catch (Exception e){e.printStackTrace();return false;}return false;});}
}
RedisController測試類
@RestController
@RequestMapping(value = "/redis")
public class RedisController {@Autowiredprivate RedisUtil redisUtil;/*** 獲取鎖* @return*/@GetMapping(value = "/lock/{name}")public String lock(@PathVariable(value = "name")String name) throws Exception{Boolean result = redisUtil.tryLock(name, 3000L);if(result){return "獲取鎖成功";}return "獲取鎖失敗";}/*** 釋放鎖* @param name*/@GetMapping(value = "/unlock/{name}")public String unlock(@PathVariable(value = "name")String name){Boolean result = redisUtil.release(name);if(result){return "釋放鎖成功";}return "釋放鎖失敗";}
}
2.2.2 優(yōu)點及缺點
優(yōu)點:性能極高
缺點:失效時間設(shè)置沒有定值。設(shè)置的失效時間太短,方法沒等執(zhí)行完鎖就自動釋放了,那么就會產(chǎn)生并發(fā)問題。如果設(shè)置的時間太長,其他獲取鎖的線程就可能要平白的多等一段時間,用戶體驗會降低。
2.3 zookeeper實現(xiàn)分布式鎖
2.3.1 zookeeper 鎖相關(guān)基礎(chǔ)知識
- zookeeper 一般由多個節(jié)點構(gòu)成(單數(shù)),采用 zab 一致性協(xié)議。因此可以將 zk 看成一個單點結(jié)構(gòu),對其修改數(shù)據(jù)其內(nèi)部自動將所有節(jié)點數(shù)據(jù)進行修改而后才提供查詢服務(wù)。
- zookeeper 的數(shù)據(jù)以目錄樹的形式,每個目錄稱為 znode, znode 中可存儲數(shù)據(jù)(一般不超過 1M),還可以在其中增加子節(jié)點。
- 子節(jié)點有三種類型。序列化節(jié)點,每在該節(jié)點下增加一個節(jié)點自動給該節(jié)點的名稱上自增。臨時節(jié)點,一旦創(chuàng)建這個 znode 的客戶端與服務(wù)器失去聯(lián)系,這個 znode 也將自動刪除。最后就是普通節(jié)點。
- Watch 機制,client 可以監(jiān)控每個節(jié)點的變化,當產(chǎn)生變化會給 client 產(chǎn)生一個事件。
2.3.2 zookeeper 分布式鎖的原理
- 獲取和釋放鎖原理:利用臨時節(jié)點與 watch 機制。每個鎖占用一個普通節(jié)點 /lock,當需要獲取鎖時在 /lock 目錄下創(chuàng)建一個臨時節(jié)點,創(chuàng)建成功則表示獲取鎖成功,失敗則 watch/lock 節(jié)點,有刪除操作后再去爭鎖。臨時節(jié)點好處在于當進程掛掉后能自動上鎖的節(jié)點自動刪除即取消鎖。
- 獲取鎖的順序原理:上鎖為創(chuàng)建臨時有序節(jié)點,每個上鎖的節(jié)點均能創(chuàng)建節(jié)點成功,只是其序號不同。只有序號最小的可以擁有鎖,如果這個節(jié)點序號不是最小的則 watch 序號比本身小的前一個節(jié)點 (公平鎖)。
2.3.2 zookeeper實現(xiàn)分布式鎖流程
簡易流程
獲取鎖流程:
- 先有一個鎖根節(jié)點,lockRootNode,這可以是一個永久的節(jié)點
- 客戶端獲取鎖,先在 lockRootNode 下創(chuàng)建一個順序的臨時節(jié)點,保證客戶端斷開連接,節(jié)點也自動刪除
- 調(diào)用 lockRootNode 父節(jié)點的 getChildren() 方法,獲取所有的節(jié)點,并從小到大排序,如果創(chuàng)建的最小的節(jié)點是當前節(jié)點,則返回 true,獲取鎖成功,否則,關(guān)注比自己序號小的節(jié)點的釋放動作(exist watch),這樣可以保證每一個客戶端只需要關(guān)注一個節(jié)點,不需要關(guān)注所有的節(jié)點,避免羊群效應(yīng)。
- 如果有節(jié)點釋放操作,重復(fù)步驟 3
釋放鎖流程:
只需要刪除步驟 2 中創(chuàng)建的節(jié)點即可
2.3.2 優(yōu)點及缺點
優(yōu)點:
- 客戶端如果出現(xiàn)宕機故障的話,鎖可以馬上釋放
- 可以實現(xiàn)阻塞式鎖,通過 watcher 監(jiān)聽,實現(xiàn)起來也比較簡單
- 集群模式,穩(wěn)定性比較高
缺點:
- 一旦網(wǎng)絡(luò)有任何的抖動,Zookeeper 就會認為客戶端已經(jīng)宕機,就會斷掉連接,其他客戶端就可以獲取到鎖。
- 性能不高,因為每次在創(chuàng)建鎖和釋放鎖的過程中,都要動態(tài)創(chuàng)建、銷毀臨時節(jié)點來實現(xiàn)鎖功能。ZK 中創(chuàng)建和刪除節(jié)點只能通過 Leader 服務(wù)器來執(zhí)行,然后將數(shù)據(jù)同步到所有的 Follower 機器上。(zookeeper對外提供服務(wù)的只有l(wèi)eader)
2.4 consul實現(xiàn)分布式鎖(eureka/Register:保存服務(wù)的IP 端口 服務(wù)列表)
2.4.1 實現(xiàn)原理及流程
基于Consul注冊中心的Key/Value存儲來實現(xiàn)分布式鎖以及信號量的方法主要利用Key/Value存儲API中的acquire和release操作來實現(xiàn)。acquire和release操作是類似Check-And-Set的操作:
acquire操作只有當鎖不存在持有者時才會返回true,并且set設(shè)置的Value值,同時執(zhí)行操作的session會持有對該Key的鎖,否則就返回falserelease操作則是使用指定的session來釋放某個Key的鎖,如果指定的session無效,那么會返回false,否則就會set設(shè)置Value值,并返回true
實現(xiàn)流程
實現(xiàn)步驟:
- 客戶端創(chuàng)建會話session,得到sessionId;
- 使用acquire設(shè)置value的值,若acquire結(jié)果為false,代表獲取鎖失敗;
- acquire結(jié)果為true,代表獲取鎖成功,客戶端執(zhí)行業(yè)務(wù)邏輯;
- 客戶端業(yè)務(wù)邏輯執(zhí)行完成后,執(zhí)行release操作釋放鎖;
- 銷毀當前session,客戶端連接斷開。
代碼:
下載consul
啟動consul命令: consul agent -dev
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>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.5.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.maweiqi</groupId><artifactId>demo-consul</artifactId><version>0.0.1-SNAPSHOT</version><name>demo-consul</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><spring-cloud.version>Hoxton.SR3</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-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></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>
public class ConsulUtil {private ConsulClient consulClient;private String sessionId = null;/*** 構(gòu)造函數(shù)*/public ConsulUtil(ConsulClient consulClient) {this.consulClient = consulClient;}/*** 創(chuàng)建session*/private String createSession(String name, Integer ttl){NewSession newSession = new NewSession();//設(shè)置鎖有效時長newSession.setTtl(ttl + "s");//設(shè)置鎖名字newSession.setName(name);String value = consulClient.sessionCreate(newSession, null).getValue();return value;}/*** 獲取鎖*/public Boolean lock(String name, Integer ttl){//定義獲取標識Boolean flag = false;//創(chuàng)建sessionsessionId = createSession(name, ttl);//死循環(huán)獲取鎖while (true){//執(zhí)行acquire操作PutParams putParams = new PutParams();putParams.setAcquireSession(sessionId);flag = consulClient.setKVValue(name, "local" + System.currentTimeMillis(), putParams).getValue();if(flag){break;}}return flag;}/*** 釋放鎖*/public Boolean release(String name){//執(zhí)行acquire操作PutParams putParams = new PutParams();putParams.setReleaseSession(sessionId);Boolean value = consulClient.setKVValue(name, "local" + System.currentTimeMillis(), putParams).getValue();return value;}
測試代碼:
@SpringBootTest
class DemoApplicationTests {@Testpublic void testLock() throws Exception {new Thread(new LockRunner("線程1")).start();new Thread(new LockRunner("線程2")).start();new Thread(new LockRunner("線程3")).start();new Thread(new LockRunner("線程4")).start();new Thread(new LockRunner("線程5")).start();Thread.sleep(200000L);}class LockRunner implements Runnable {private String name;public LockRunner(String name) {this.name = name;}@Overridepublic void run() {ConsulUtil lock = new ConsulUtil(new ConsulClient());try {if (lock.lock("test", 10)) {System.out.println(name + "獲取到了鎖");//持有鎖5秒Thread.sleep(5000);//釋放鎖lock.release("test");System.out.println(name + "釋放了鎖");}} catch (Exception e) {e.printStackTrace();}}}}
結(jié)果
2.4.2 優(yōu)點及缺點
**優(yōu)點:**基于consul注冊中心即可實現(xiàn)分布式鎖,實現(xiàn)簡單、方便、快捷
缺點:
- lock delay:consul實現(xiàn)分布式鎖存在延遲,一個節(jié)點釋放鎖了,另一個節(jié)點不能立馬拿到鎖。需要等待lock delay時間后才可以拿到鎖。
- 高負載的場景下,不能及時的續(xù)約,導(dǎo)致session timeout, 其他節(jié)點拿到鎖。