SpringBoot integration實(shí)現(xiàn)分布式鎖的示例詳解
常規(guī)項(xiàng)目都是采用Redission來(lái)實(shí)現(xiàn)分布式鎖,進(jìn)行分布式系統(tǒng)中資源競(jìng)爭(zhēng)加鎖操作。需要單獨(dú)引入Jar包,偶然發(fā)現(xiàn)SpringBoot中的integration也實(shí)現(xiàn)多種載體的分布式鎖控制。
代碼集成
引入
// 分布式鎖 implementation 'org.springframework.boot:spring-boot-starter-integration' implementation('org.springframework.integration:spring-integration-redis')
采用最常見(jiàn)的redis來(lái)作為分布式鎖的底層載體。
鎖注冊(cè)
在@Configuration
配置類(lèi)中,添加分布式鎖注冊(cè)信息。
@Bean open fun redisLockRegistry(redisConnectionFactory: RedisConnectionFactory): RedisLockRegistry { return RedisLockRegistry(redisConnectionFactory, "fcDistroLock", 20000L) }
有兩個(gè)核心參數(shù),第一個(gè)指定的是分布式鎖的前綴,第二個(gè)是指定分布式鎖的過(guò)期時(shí)間。過(guò)期時(shí)間建議不要指定到過(guò)長(zhǎng),防止拖慢整體的業(yè)務(wù)響應(yīng)速度。
加鎖
在使用之前需要知道加鎖的三個(gè)核心方法。
lock | 直接加鎖,一直等待 |
---|---|
tryLock(無(wú)參數(shù)) | 嘗試加鎖,未獲取到鎖,直接返回失敗 |
tryLock(long time, TimeUnit unit) | 嘗試加鎖,等待一定時(shí)間后未獲取到鎖,直接返回失敗 |
建議使用帶參數(shù)的嘗試加鎖,設(shè)置一個(gè)合適的超時(shí)時(shí)間。建議使用模式如下
Lock lock = ...; if (lock.tryLock(time)) { try { // manipulate protected state } finally { lock.unlock(); } } else { // perform alternative actions }}
有一個(gè)點(diǎn)需要注意,當(dāng)加鎖失敗時(shí),需要考慮補(bǔ)償機(jī)制。例如用戶(hù)余額扣減失敗,需要重新進(jìn)行推送;或者加鎖失敗,拋出異常回滾本地事務(wù)等。
使用上非常簡(jiǎn)單。
細(xì)粒度加鎖
可以通過(guò)上圖可以看到,我們加鎖的對(duì)象是用戶(hù)id,并不是所有用戶(hù)。代表不同用戶(hù)之間操作是不受分布式事務(wù)限制。這里同步會(huì)衍生另外一個(gè)問(wèn)題,如果用戶(hù)id特別多,就會(huì)占用非常多的資源。這里就需要定時(shí)手動(dòng)清除加鎖對(duì)象,或者加鎖成功后直接清除。個(gè)人建議使用定時(shí)清除,有助于減少對(duì)象的創(chuàng)建,提高系統(tǒng)吞吐量。
@Scheduled(cron = "0 0 0/1 * * ?") fun scheduleRemoveRedisLock() { redisLockRegistry.expireUnusedOlderThan(1000 * 60 * 60) }
RedisLockRegistry其實(shí)已經(jīng)提供清除的方法,我們只需要指定清除的有效期即可。項(xiàng)目中指定的是清除1個(gè)小時(shí)之前的加鎖對(duì)象。
核心邏輯
打開(kāi)tryLock的實(shí)現(xiàn)類(lèi)RedisLock很容易發(fā)現(xiàn),每個(gè)加鎖id都對(duì)應(yīng)1個(gè)RedisLock,1個(gè)RedisLock中包含1個(gè)ReentrantLock,用來(lái)進(jìn)行本地資源互斥。
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { long now = System.currentTimeMillis(); if (!this.localLock.tryLock(time, unit)) { // 獲取本地互斥鎖 return false; } try { long expire = now + TimeUnit.MILLISECONDS.convert(time, unit); boolean acquired; while (!(acquired = obtainLock()) && System.currentTimeMillis() < expire) { //NOSONAR Thread.sleep(100); //NOSONAR 防止請(qǐng)求過(guò)快,進(jìn)行100Ms的休眠 } if (!acquired) { this.localLock.unlock(); } return acquired; } catch (Exception e) { this.localLock.unlock(); rethrowAsLockException(e); } return false; }
兩個(gè)條件跳出循環(huán)獲取鎖的過(guò)程。
- 超過(guò)等待時(shí)間
- redis返回是否獲取到鎖
Redis鎖邏輯判斷
private static final String OBTAIN_LOCK_SCRIPT = "local lockClientId = redis.call('GET', KEYS[1])\n" + "if lockClientId == ARGV[1] then\n" + " redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" + " return true\n" + "elseif not lockClientId then\n" + " redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" + " return true\n" + "end\n" + "return false";
利用Redis的原子性進(jìn)行鎖資源判斷,通過(guò)是否相同應(yīng)用id來(lái)支持重入鎖。
整體使用
使用上非常簡(jiǎn)單,沒(méi)有鎖續(xù)期,沒(méi)有讀寫(xiě)鎖,也沒(méi)有考慮重入鎖的計(jì)數(shù)問(wèn)題。功能上還是比Redission差不少,在一些業(yè)務(wù)相對(duì)比較簡(jiǎn)單的場(chǎng)景可以嘗試使用SpringBoot自帶的分布式鎖。如果需要面對(duì)更細(xì)粒度的控制,提高性能,更復(fù)雜的鎖控制,就需要使用到Redission來(lái)進(jìn)行分布式鎖的編寫(xiě)了。
到此這篇關(guān)于SpringBoot integration實(shí)現(xiàn)分布式鎖的示例詳解的文章就介紹到這了,更多相關(guān)SpringBoot integration分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java的JDBC中Statement與PreparedStatement對(duì)象
這篇文章主要介紹了詳解Java的JDBC中Statement與PreparedStatement對(duì)象,PreparedStatement一般來(lái)說(shuō)比使用Statement效率更高,需要的朋友可以參考下2015-12-12SpringBoot接口或方法進(jìn)行失敗重試的實(shí)現(xiàn)方式
為了防止網(wǎng)絡(luò)抖動(dòng),影響我們核心接口或方法的成功率,通常我們會(huì)對(duì)核心方法進(jìn)行失敗重試,如果我們自己通過(guò)for循環(huán)實(shí)現(xiàn),會(huì)使代碼顯得比較臃腫,所以本文給大家介紹了SpringBoot接口或方法進(jìn)行失敗重試的實(shí)現(xiàn)方式,需要的朋友可以參考下2024-07-07MyBatis攔截器動(dòng)態(tài)替換表名的方法詳解
因?yàn)槲覀兂志脤涌蚣芨嗟厥褂肕yBatis,那我們就借助于MyBatis的攔截器來(lái)完成我們的功能,這篇文章主要給大家介紹了關(guān)于MyBatis攔截器動(dòng)態(tài)替換表名的相關(guān)資料,需要的朋友可以參考下2022-04-04Java語(yǔ)言基于無(wú)向有權(quán)圖實(shí)現(xiàn)克魯斯卡爾算法代碼示例
這篇文章主要介紹了Java語(yǔ)言基于無(wú)向有權(quán)圖實(shí)現(xiàn)克魯斯卡爾算法代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11java開(kāi)發(fā)中嵌套類(lèi)的詳解及實(shí)例
這篇文章主要介紹了 java開(kāi)發(fā)中嵌套類(lèi)的詳解及實(shí)例的相關(guān)資料,一般把定義內(nèi)部類(lèi)的外圍類(lèi)成為包裝類(lèi)(enclosing class)或者外部類(lèi),需要的朋友可以參考下2017-07-07使用JAVA+Maven+TestNG框架實(shí)現(xiàn)超詳細(xì)Appium測(cè)試安卓真機(jī)教程
這篇文章主要介紹了使用JAVA+Maven+TestNG框架實(shí)現(xiàn)超詳細(xì)Appium測(cè)試安卓真機(jī)教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Java中字符數(shù)組、String類(lèi)、StringBuffer三者之間相互轉(zhuǎn)換
這篇文章主要介紹了Java中字符數(shù)組、String類(lèi)、StringBuffer三者之間相互轉(zhuǎn)換,需要的朋友可以參考下2018-05-05SpringBoot整合多個(gè)Mq服務(wù)做法詳解
SpringBoot整合rabbitmq很容易,但是整合的目的是為了使用,那要使用rabbitmq就要對(duì)其有一定的了解,不然容易整成一團(tuán)漿糊。因?yàn)檎f(shuō)到底,SpringBoot只是在封裝rabbitmq的API,讓其更容易使用而已,廢話(huà)不多說(shuō),讓我們一起整它2023-02-02