深入淺出探索Java分布式鎖原理
什么是分布式鎖?它能干什么?
相信大家對(duì)于Java
提供的synchronized
關(guān)鍵字以及Lock
鎖都不陌生,在實(shí)際的項(xiàng)目中大家都使用過。如下圖所示,在同一個(gè)JVM
進(jìn)程中,Thread1
獲得鎖之后,對(duì)共享資源進(jìn)行操作,其他線程未獲得鎖的線程只能等待Thread1
釋放后才能進(jìn)行對(duì)應(yīng)的操作。
但是隨著業(yè)務(wù)的不斷發(fā)展,原先的單體應(yīng)用被拆分為多個(gè)微服務(wù),每個(gè)微服務(wù)又會(huì)部署多個(gè)實(shí)例,于是就形成了當(dāng)下的微服務(wù)架構(gòu)。處理共享資源的請(qǐng)求來自不同的服務(wù)實(shí)例,也就是在不同的JVM
進(jìn)程中。原先的單體服務(wù)中的加鎖方式在分布式場(chǎng)景下不能滿足共享資源的并發(fā)訪問要求。因此我們需要一種適用于分布式場(chǎng)景下的共享資源安全的處理機(jī)制,此時(shí)應(yīng)對(duì)這種問題的分布式鎖就應(yīng)運(yùn)而生了。
既然JVM
進(jìn)程管不到其他服務(wù)實(shí)例的線程,那么可以借助于外部組件能力來實(shí)現(xiàn)不同服務(wù)實(shí)例對(duì)于共享資源的統(tǒng)一管控,這種能力我們可以稱之為分布式鎖。因此分布式鎖的本質(zhì)就是在不同服務(wù)實(shí)例之外建立一種獲取鎖的機(jī)制,形成一種并發(fā)互斥能力來確保不同線程對(duì)于共享資源的并發(fā)安全,從而實(shí)現(xiàn)在微服務(wù)架構(gòu)中同一時(shí)刻只有一個(gè)線程可以對(duì)共享資源進(jìn)行操作。對(duì)于分布式鎖來說,實(shí)際就是需要一個(gè)外部的狀態(tài)存儲(chǔ)系統(tǒng)來實(shí)現(xiàn)原子化的排他性操作。
通過對(duì)于分布式鎖的需求分析,總結(jié)了如下的分布式鎖四大特性,分別是多節(jié)點(diǎn)、加鎖速度快、排他性以及鎖過期實(shí)現(xiàn)機(jī)制。
分布式鎖實(shí)現(xiàn)方案
基于數(shù)據(jù)庫的分布式鎖實(shí)現(xiàn)方案
實(shí)現(xiàn)原理
通過數(shù)據(jù)庫的方式實(shí)現(xiàn)分布式鎖的效果,實(shí)際就是借助于數(shù)據(jù)庫的唯一性約束特性或者for update
來實(shí)現(xiàn)。這里以唯一性約束來舉個(gè)栗子,在電商領(lǐng)域的庫存服務(wù)負(fù)責(zé)對(duì)商品的庫存進(jìn)行扣減,首先創(chuàng)建一張專門存放鎖信息的鎖表,那么庫存服務(wù)在進(jìn)行庫存操作之前,先向數(shù)據(jù)庫中的鎖表插入一條鎖資源數(shù)據(jù)。
create table ‘distributed_lock' ( ‘id' BIGINT NOT NULL AUTO_INCREMENT, ‘resource_lock_key‘ varchar(64) NOT NULL PRIMARY KEY(‘id'), UNIQUE KEY ‘uk_resource_lock_key‘ (‘resource_lock_key‘) USING BTREE )
大致的交互流程如下:
1、當(dāng)庫存服務(wù)進(jìn)行手機(jī)庫存扣減的時(shí)候,首先先向數(shù)據(jù)庫中的鎖表當(dāng)中插入一條資源鎖信息;
2、如果插入成功,則表示庫存服務(wù)1可以對(duì)手機(jī)庫存進(jìn)行庫存扣減操作;
3、此時(shí)庫存服務(wù)2也要對(duì)庫存進(jìn)行操作,于是同樣插入數(shù)據(jù)到鎖表中;
4、但是由于鎖表設(shè)置了唯一性約束,鎖信息插入失敗,庫存服務(wù)進(jìn)行等待;
5、庫存服務(wù)1執(zhí)行完庫存扣減之后,刪除鎖表的信息;
6、庫存服務(wù)2嘗試插入資源鎖信息,發(fā)現(xiàn)可以插入成功,繼續(xù)執(zhí)行后續(xù)操作。
方案分析
基于數(shù)據(jù)庫的實(shí)現(xiàn)方式,看起來還是比較容易理解的。但是實(shí)際上還是有一些問題存在的,我們一起來分析下。
1、性能問題:由于是插入數(shù)據(jù)數(shù)據(jù)需要落盤存儲(chǔ),如果平凡進(jìn)行讀寫的話會(huì)影響數(shù)據(jù)庫性能,另外由于使用唯一鍵進(jìn)行判斷也會(huì)一定程度上影響數(shù)據(jù)庫性能,因此數(shù)據(jù)庫方案適用于并發(fā)量不到的簡(jiǎn)單場(chǎng)景;
2、數(shù)據(jù)庫如果單點(diǎn)部署的話會(huì)存在單點(diǎn)故障問題,如果數(shù)據(jù)庫出現(xiàn)故障,可能會(huì)導(dǎo)致平臺(tái)中的業(yè)務(wù)異常;
3、死鎖問題:在上文介紹中,包含了插入數(shù)據(jù)庫的獲取鎖的步驟,還包含了刪除鎖信息的釋放鎖的過程,但是如果庫存服務(wù)1在加鎖之后掛掉了,無法進(jìn)行鎖的釋放,而其他服務(wù)又無法獲取到鎖就會(huì)造成死鎖的問題。當(dāng)然了我們可以通過一個(gè)定時(shí)任務(wù)去檢查鎖表中是不是有過時(shí)的鎖資源。但是這樣無疑增加了分布式鎖實(shí)現(xiàn)的復(fù)雜性。
4、不支持可重入:如果想要實(shí)現(xiàn)可重入鎖,還需要增加主機(jī)、線程名等字段來進(jìn)行標(biāo)注,通過這幾個(gè)字段來判斷和當(dāng)前信息是否一致,如果一致則認(rèn)為已經(jīng)獲取到了鎖。 鑒于以上的這些問題,有沒有其他的分布式實(shí)現(xiàn)方案可以避免上述存在的問題呢?我們?cè)偻聛砜础?/p>
基于Redis的分布式鎖實(shí)現(xiàn)方案
基于sentnx命令的實(shí)現(xiàn)原理
Redis
作為一塊高性能的數(shù)據(jù)庫中間件,經(jīng)常被當(dāng)做緩存在項(xiàng)目中使用。因此通過Redis
實(shí)現(xiàn)分布式鎖,也是比較常見的實(shí)現(xiàn)方案。 一樣的道理,通過Redis
實(shí)現(xiàn)分布式鎖也需要通過它實(shí)現(xiàn)鎖的互斥的能力。實(shí)際上就是利用了sentnx(set if not exists)
命令。同時(shí)該命令是否能夠設(shè)置成功,決定服務(wù)是否可以拿到對(duì)應(yīng)的分布式鎖。
127.0.0.1:6379> setnx stockLock 10.12.35.12_stockService
(integer) 1
如上圖所示,大致的加鎖以及釋放鎖的過程其實(shí)和數(shù)據(jù)庫的分布式鎖方案還是比較類似的。只不過將其中向數(shù)據(jù)庫插入數(shù)據(jù)的步驟替換成了向Redis
獲取鎖的步驟,由于Redis
是基于內(nèi)存進(jìn)行操作的,因此性能上比基于數(shù)據(jù)庫的分布式鎖方案更好一點(diǎn)。
方案分析
上述基于Redis
的方案的方案在性能上具有優(yōu)勢(shì),我們?cè)賮矸治鱿?,這個(gè)使用命令的方式有沒有什么問題。實(shí)際上和前面的數(shù)據(jù)庫方案類似,Redis
也會(huì)有死鎖問題,當(dāng)獲取鎖之后如果庫存服務(wù)1掛掉了,庫存服務(wù)2就獲取不到鎖了。因此我們要對(duì)其進(jìn)行優(yōu)化。那么問題的本質(zhì)是如何讓鎖可以釋放,因此我們需要在設(shè)置鎖的時(shí)候加上過期時(shí)間,這樣即使庫存服務(wù)1掛了,無法主動(dòng)釋放鎖,那么到了過期時(shí)間后鎖失效,庫存服務(wù)2依然可以獲取鎖,不會(huì)再造成死鎖問題。
另外還應(yīng)該注意的是,在我們?cè)O(shè)置鎖的時(shí)候,還需要帶有自身服務(wù)的業(yè)務(wù)屬性,否則容易造成錯(cuò)亂。為什么這么說呢?舉個(gè)栗子,庫存服務(wù)在加完鎖之后開始執(zhí)行扣減庫存的任務(wù),當(dāng)扣減庫存完成之后,服務(wù)掛了,原先需要?jiǎng)h除的鎖資源,等到過期之后被Redis
刪除,此時(shí)庫存服務(wù)2可以繼續(xù)申請(qǐng)鎖,如果此時(shí)庫存服務(wù)1恢復(fù)了,它并不知道鎖資源已經(jīng)釋放,起來后立馬刪除了庫存服務(wù)2加的鎖,那么此時(shí)就會(huì)出現(xiàn)兩個(gè)問題:
1、庫存服務(wù)執(zhí)行完庫存扣減之后,回頭來進(jìn)行鎖資源釋放的時(shí)候,發(fā)現(xiàn)鎖實(shí)際已經(jīng)不在了;
2、當(dāng)庫存服務(wù)1恢復(fù)后發(fā)現(xiàn)鎖還在,立馬刪除了該鎖,完成了它掛掉之前未完成的工作。但是實(shí)際上這個(gè)鎖是庫存服務(wù)2加的鎖,如果此時(shí)庫存服務(wù)3也要嘗試加鎖,發(fā)現(xiàn)可以加鎖成功,和庫存服務(wù)2一樣同樣對(duì)庫存進(jìn)行操作,那么此時(shí)就會(huì)出現(xiàn)線程安全問題。
經(jīng)過上文的分析,這個(gè)問題的根源就是在加鎖的時(shí)候沒有具體區(qū)分到底是哪個(gè)服務(wù)加的鎖。因此在執(zhí)行命令的時(shí)候,我們需要將帶有服務(wù)實(shí)例關(guān)聯(lián)屬性的設(shè)置為value
,這樣在進(jìn)行鎖獲取的時(shí)候檢查下當(dāng)前鎖的持有者是誰,如果不是服務(wù)實(shí)例自己則不能執(zhí)行刪除操作。
那這樣是不是就完美解決問題了呢?實(shí)際上還是有問題存在的,有同學(xué)會(huì)說,怎么這么多問題?實(shí)際上這種方案的實(shí)現(xiàn)就是在各種不完美的方案中逐漸找到相對(duì)完美的方案。
上文提到的獲取鎖判斷是不是自己方服務(wù)實(shí)例加的鎖,再執(zhí)行刪除鎖的過程實(shí)際并不是原子的。因此還是會(huì)出現(xiàn)并發(fā)安全問題,這個(gè)問題可以通過lua
腳本來解決,在lua
腳本中實(shí)現(xiàn)這個(gè)邏輯,而不是在客戶端中實(shí)現(xiàn)。 但是實(shí)際上還是有問題沒有解決,比如說我們?cè)诩渔i的時(shí)候會(huì)設(shè)置過期時(shí)間,但是過期時(shí)間應(yīng)該設(shè)置多長(zhǎng)時(shí)間呢?設(shè)置短了的話,出現(xiàn)網(wǎng)絡(luò)超時(shí)或者服務(wù)還沒有執(zhí)行完業(yè)務(wù),鎖就失效了。設(shè)置長(zhǎng)了話,其他服務(wù)節(jié)點(diǎn)等待獲取鎖的時(shí)間就會(huì)變長(zhǎng),降低了服務(wù)的性能。
基于Redisson實(shí)現(xiàn)
Redisson
實(shí)際上就是一個(gè)封裝了Redis
操作的客戶端,實(shí)現(xiàn)了對(duì)于常見的Redis
操作的封裝。如對(duì)于Redis
的設(shè)置鎖的步驟以及刪除鎖的步驟都進(jìn)行了封裝。在設(shè)置鎖的操作中,還引入了自動(dòng)給鎖續(xù)期的機(jī)制,SDK
檢測(cè)到業(yè)務(wù)未完成,但是鎖要到期后,執(zhí)行定續(xù)期。這樣并可以動(dòng)態(tài)的調(diào)節(jié)過期時(shí)間,避免鎖在業(yè)務(wù)未完成情況下被釋放的問題。
同時(shí)還封裝了刪除鎖的時(shí)候執(zhí)行的業(yè)務(wù)判斷后再刪除的邏輯,這樣我們?cè)谑褂?code>Redisson操作Redis
的時(shí)候,就和我們使用JDK
一樣。
RedLock
為了解決Redis作為分布式鎖存在的單點(diǎn)問題,Redis的作者又提出了Redlock的解決方案,該解決方案依賴多個(gè)Redis的Master節(jié)點(diǎn),官方推薦使用5個(gè)Master節(jié)點(diǎn),他們彼此之間是獨(dú)立的。大致的交互步驟如下所示:
1、首先獲取當(dāng)前節(jié)點(diǎn)的系統(tǒng)時(shí)間;
2、客戶端嘗試向所有的Redis
實(shí)例順序地發(fā)送加鎖的請(qǐng)求(官方推薦Redis
集群至少5個(gè)實(shí)例),在設(shè)置鎖的過程中,使用相同的key以及隨機(jī)值value,同時(shí)請(qǐng)求的超時(shí)時(shí)間需要遠(yuǎn)小于鎖的有效時(shí)間。這樣做的目的是為了防止節(jié)點(diǎn)不可用的時(shí)候?qū)е抡?qǐng)求鎖的時(shí)候被阻塞,當(dāng)實(shí)例沒響應(yīng)的時(shí)候可以快速跳過,向下一個(gè)節(jié)點(diǎn)繼續(xù)請(qǐng)求鎖。
3、假設(shè)Redis集群規(guī)模為5,那么如果客戶端在大多數(shù)實(shí)例中(超過3個(gè)實(shí)例)獲得了鎖,同時(shí)計(jì)算了當(dāng)前的時(shí)間減去步驟1中獲得的時(shí)間,這個(gè)事件差如果小于鎖的有效時(shí)間,那么此時(shí)可以認(rèn)為加鎖成功,可以操作執(zhí)行后續(xù)的業(yè)務(wù);
4、如果不滿足步驟3是條件,那么就表示加鎖失敗,客戶端需要向所有的Redis節(jié)點(diǎn)發(fā)起鎖釋放請(qǐng)求。
方案分析
為什么Redlock
要在集群中多個(gè)實(shí)例上加鎖呢?實(shí)際目的是通過鎖的冗余來實(shí)現(xiàn)分布式鎖的高容錯(cuò)性。試想一下如果只有一個(gè)Redis
實(shí)例,一旦它掛掉了,客戶端就無法進(jìn)行加鎖操作了或者鎖信息就會(huì)丟失,影響業(yè)務(wù)功能。通過在集群中多實(shí)例中冗余鎖信息,即使出現(xiàn)Redis
掛了的情況,其他節(jié)點(diǎn)中依然存在鎖信息,從而提升了分布式鎖的可用性。
那么為什么還要計(jì)算幾所時(shí)間呢?由于我們加鎖的時(shí)候,每個(gè)節(jié)點(diǎn)都設(shè)置了超時(shí)時(shí)間,如果整個(gè)加鎖的時(shí)間過長(zhǎng),整個(gè)過程的累加時(shí)間超過了鎖的有效時(shí)間,那么加鎖完成之后就會(huì)哦出現(xiàn)鎖失效的情況了,因此我們需要確保加鎖的事件盡可能的短,這也是為什么加鎖請(qǐng)求都有超時(shí)時(shí)間的原因了,發(fā)現(xiàn)超時(shí)立馬跳到下一個(gè)節(jié)點(diǎn),避免單個(gè)節(jié)點(diǎn)耗時(shí)過長(zhǎng)。
雖然Redlock看上去是比較完善的分布式解決方案,但是實(shí)際上這個(gè)方案是比較重的,需要維護(hù)一個(gè)Redis集群,另外過程中依賴系統(tǒng)時(shí)間,但是如果出現(xiàn)了時(shí)間跳變,那么對(duì)于整個(gè)分布式鎖都有非常大的影響。
基于Zookeeper的分布式鎖實(shí)現(xiàn)方案
實(shí)現(xiàn)原理
Zookeeper
是一個(gè)分布式的應(yīng)用協(xié)調(diào)服務(wù)中間件,通過它也可以實(shí)現(xiàn)分布式鎖的效果,這里介紹的是基于臨時(shí)有序的ZNode
分布式鎖實(shí)現(xiàn)方案。在介紹方案之前,先補(bǔ)充下Zookeeper
中和分布式鎖息息相關(guān)的特性。
我們來看下Zookeeper
的數(shù)據(jù)結(jié)構(gòu),實(shí)際上它是一種樹形模型,類似于Linux
的文件系統(tǒng)。Zookeeper
使用類似于文件目錄的層級(jí)目錄數(shù)據(jù)結(jié)構(gòu)來組織自身的數(shù)據(jù)存儲(chǔ)節(jié)點(diǎn),這些節(jié)點(diǎn)就被稱作為ZNode
,每個(gè)節(jié)點(diǎn)都用一個(gè)以斜杠(/)分隔的路徑來表示,而且每個(gè)節(jié)點(diǎn)都有父節(jié)點(diǎn)(根節(jié)點(diǎn)除外)。另外在Zookeeper
中,如果我們使用不同的創(chuàng)建參數(shù),可以創(chuàng)建不同類型的ZNode
。 1、持久化ZNode
:當(dāng)createMode
為PERSISTENT
會(huì)創(chuàng)建持久化ZNode
,節(jié)點(diǎn)存儲(chǔ)的數(shù)據(jù)會(huì)永久保存在Zookeeper
中,如果createMode
為PERSISTENT_SEQUENTIAL
,則會(huì)創(chuàng)建有序持久化ZNode
,和之前的持久化節(jié)點(diǎn)不通的是,有序持久化節(jié)點(diǎn)的節(jié)點(diǎn)名稱會(huì)附加上全局有序的遞增序號(hào); 2、臨時(shí)ZNode
:當(dāng)createMode
為EPHEMERAL
時(shí),創(chuàng)建的節(jié)點(diǎn)臨時(shí)節(jié)點(diǎn),在與客戶端的session
過期后,對(duì)應(yīng)的臨時(shí)節(jié)點(diǎn)也會(huì)被刪除。當(dāng)createMode
為EPHEMERAL_SEQUENTIAL
時(shí)創(chuàng)建出來的為有序的臨時(shí)節(jié)點(diǎn),當(dāng)session
過期之后,節(jié)點(diǎn)及其存儲(chǔ)的數(shù)據(jù)也是會(huì)被刪除的。
通過上述對(duì)于節(jié)點(diǎn)特性的描述,可以看出來它的全局遞增有序以及過期刪除的特性與分布式鎖實(shí)現(xiàn)的原理非常契合。因此通過Zookeeper
實(shí)現(xiàn)分布式鎖的大致可以分為以下幾個(gè)步驟:
1、首先創(chuàng)建一個(gè)持久化節(jié)點(diǎn)也就是父節(jié)點(diǎn),這個(gè)持久化節(jié)點(diǎn)代表著一個(gè)分布式鎖實(shí)例;
2、當(dāng)有線程想要申請(qǐng)分布式鎖的時(shí)候,則在該持久化節(jié)點(diǎn)下創(chuàng)建臨時(shí)有序節(jié)點(diǎn);
3、如果此時(shí)新建的臨時(shí)有序節(jié)點(diǎn)是該父節(jié)點(diǎn)小所有有序節(jié)點(diǎn)中序號(hào)最小的節(jié)點(diǎn),那么此時(shí)就表示申請(qǐng)到了分布式鎖;
4、如果新建的臨時(shí)節(jié)點(diǎn)當(dāng)前不是最小序號(hào)的節(jié)點(diǎn),則需要不斷檢查是否最小,知道最終獲取到鎖,或者節(jié)點(diǎn)超時(shí)。實(shí)際上這個(gè)是通過Zookeeper
的watch
機(jī)制實(shí)現(xiàn)的,在當(dāng)前節(jié)點(diǎn)的上一序號(hào)的節(jié)點(diǎn)設(shè)置監(jiān)聽器,檢查是否為最小節(jié)點(diǎn)的任務(wù)可以一直阻塞,直到收到上一節(jié)點(diǎn)被刪除的時(shí)間事件,則喚醒檢查事件,檢查當(dāng)前節(jié)點(diǎn)是不是最小序號(hào)節(jié)點(diǎn)。
5、當(dāng)線程執(zhí)行完業(yè)務(wù)之后,可以手動(dòng)刪除該臨時(shí)節(jié)點(diǎn)以便于釋放持有的鎖。另外即使服務(wù)掛掉,由于對(duì)應(yīng)的session失效,對(duì)應(yīng)的臨時(shí)節(jié)點(diǎn)也會(huì)被刪除,防止出現(xiàn)死鎖問題。
和Redisson
類似,我們?cè)趯?shí)際使用Zookeeper
作為分布式鎖的時(shí)候可以用Curator
來作為開發(fā)SDK
,它同樣封裝了很多實(shí)現(xiàn),包括可重入鎖的實(shí)現(xiàn),減輕了使用者的負(fù)擔(dān)。
方案分析
看上去通過Zookeeper
實(shí)現(xiàn)分布式鎖還是比較好的一種解決方案,但是它是完美的嗎?從上面的分布式鎖的流程可知,客戶端線程想要獲取鎖就需要?jiǎng)?chuàng)建臨時(shí)節(jié)點(diǎn),這個(gè)時(shí)候客戶端和Zookeeper
之間就會(huì)維護(hù)一個(gè)session
,來表示該客戶端還在排隊(duì)等待獲取鎖。因此這個(gè)方案的潛在問題就在于一旦出現(xiàn)網(wǎng)絡(luò)異常,或者客戶端發(fā)生STW GC
,那么就可能導(dǎo)致session
關(guān)閉,從而導(dǎo)致臨時(shí)節(jié)點(diǎn)被關(guān)閉,此時(shí)就會(huì)出現(xiàn)原來客戶端持有的鎖被刪除了,如果有另外的客戶端過來加鎖的話可以成功獲取,那么此時(shí)就出現(xiàn)并發(fā)安全問題了。因此在這種極端條件下,Zookeeper
的分布式鎖實(shí)現(xiàn)方案也不是100%保證安全的。
另外實(shí)際上還有基于etcd
的分布式鎖實(shí)現(xiàn)方案,其基本原理和Zookeeper
差不多,感興趣的同學(xué)可以再進(jìn)行了解下。
分布式鎖方案到底選哪個(gè)?
通過上述幾種分布式鎖方案原理的闡述以及問題分析,每個(gè)方案都有自己的長(zhǎng)處以及缺點(diǎn)。所以在實(shí)際項(xiàng)目落地的時(shí)候,我么需要結(jié)合實(shí)際來進(jìn)行分布式鎖方案的選擇。比如如果平臺(tái)中本身已經(jīng)有Redis
集群了,但是沒有Zookeeper
集群,那么我們就可以借助于現(xiàn)有的基礎(chǔ)實(shí)施來落地分布式鎖,不需要再去維護(hù)一套Zookeeper
集群。
另外根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景,如果并發(fā)量并不是很高,也可以通過簡(jiǎn)單的數(shù)據(jù)庫的分布式鎖方案來實(shí)現(xiàn)。
總結(jié)
本文首先對(duì)從單機(jī)時(shí)代到分布式場(chǎng)景下的分布式鎖的產(chǎn)生的背景進(jìn)行了分析,通過對(duì)分布式鎖的本質(zhì)問題的探究,引出了數(shù)據(jù)庫分布式鎖方案、Redis
分布式鎖方案以及Zookeeper
分布式鎖方案,并對(duì)每一種方案的優(yōu)點(diǎn)以及不足進(jìn)行了分析,相信大家可以在落地實(shí)現(xiàn)分布式鎖的時(shí)候可以按照自身的情況選擇合適的方案。
到此這篇關(guān)于深入淺出探索Java分布式鎖原理的文章就介紹到這了,更多相關(guān)Java 分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java正則表達(dá)式matcher.group()用法代碼
這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式matcher.group()用法的相關(guān)資料,最近在做一個(gè)項(xiàng)目,需要使用matcher.group()方法匹配出需要的內(nèi)容,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下2023-08-08詳解Java中Vector和ArrayList的區(qū)別
這篇文章主要為大家詳細(xì)介紹了Java中Vector和ArrayList的區(qū)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10idea中javaweb的jsp頁面圖片加載不出來問題及解決
這篇文章主要介紹了idea中javaweb的jsp頁面圖片加載不出來問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Redis Lettuce連接redis集群實(shí)現(xiàn)過程詳細(xì)講解
這篇文章主要介紹了Redis Lettuce連接redis集群實(shí)現(xiàn)過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01SpringBoot中的異常處理與參數(shù)校驗(yàn)的方法實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot中的異常處理與參數(shù)校驗(yàn)的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04如何在 Spring Boot 中配置和使用 CSRF 保護(hù)
CSRF是一種網(wǎng)絡(luò)攻擊,它利用已認(rèn)證用戶的身份來執(zhí)行未經(jīng)用戶同意的操作,Spring Boot 提供了內(nèi)置的 CSRF 保護(hù)機(jī)制,可以幫助您防止這種類型的攻擊,這篇文章主要介紹了Spring?Boot?中的?CSRF?保護(hù)配置的使用方法,需要的朋友可以參考下2023-09-09