標籤:cli 任務 命令 完全 etl iss 修改 部署 允許
分布式鎖顧名思義就是在分布式系統下的鎖,而使用鎖的唯一目的就是為了防止多個請求同時對某一個資源進行競爭性讀寫
在使用多線程時,為了讓某一資源某一時刻只能有一個操作者,經常使用synchronized,這點大家都很熟悉
那什麼時候使用分布式鎖?
當一套項目只部署一套的時候,使用synchronized就可以了,但是當同一套項目部署了多套,即進行分布式部署時,
假設部署了同樣的A,B,C三套系統,系統裡面有一個操作同一時刻只允許一個使用者進行操作,如上所說,只部署一套時,用synchronized限定可以達到要求
現在部署三套之後,如果 a1,b1,c1三個甚至更多使用者來同時訪問ABC三套系統中只能有一個人操作的方法時,則都可以進行操作。synchronized是不是沒達到設計效果
所以:
只有當項目進行分布式部署且有限定不能同時操作的資源時,才會使用分布式鎖。
明確了啥時候用,那麼該如何用,怎麼設計?
按照在學java之初的思路,應該設定一個全域的標識,假設為 flag = true
如果某一時刻某個線程來擷取的時候,發現是true,就表示該線程擷取到鎖了,並改為false,其它線程來發現是false就等一段時間再試,獲得鎖的線程執行完了,修改為false以便其它線程使用
那麼問題來了:
1.怎麼儲存這個全域的flag,因為要頻繁的讀取修改
2.怎麼保證同一時刻只有一個線程取得鎖, 如果兩個線程同時來判斷flag,都發現是true,那麼兩個線程都擷取到鎖了,達不到目的
帶著這兩個問題,正式步入正題:
由於需要頻繁的讀取,而儲存的值很簡單,則考慮使用緩衝,而redis就相當符合需要,redis可以達到每秒100,000次的讀寫,而且可以供多重專案同時操作
儲存問題解決了,那麼怎麼保證同一時刻只有一個線程擷取到鎖,這就需要用到redis相關的命令了
redis中有一個setnx(set if not exist)命令,表示如果沒有這個key就設值並返回1,如果key已經存在則返回0
由於redis是單線程單進程的基於記憶體操作的工具,所以同一時刻只會有一個命令執行成功。
所以,可以簡單的使用setnx命令來進行鎖的擷取,如果返回的是1,表示擷取到了鎖,就開始執行商務邏輯,完成之後刪除key,其它線程才可以擷取到鎖
但是問題又來了,如果已經擷取到鎖的線程由於執行出錯等原因,一直不釋放鎖(delete key),那麼其它線程則永遠也無法擷取到鎖,這就和死結一樣吧
所以,釋放鎖的策略很重要
redis 有一個expire命令,可以讓key在一定時間後失效(自動刪除),但如果成功設定了key但expire來沒設定成功時服務就掛了,並且程式又執行出錯死結了一直不釋放鎖怎麼辦?
這時就需要其它線程來進行解鎖,其它線程解鎖的判斷條件就至關重要,必須明確啥時候可以解鎖
參考了很多相關文章發現其中一種比較好的策略:
redis中的value設定為 當前目前時間+失效時間,使用setnx命令成功擷取鎖後,執行任務,如果執行成功,則刪除key,如果執行失敗,導致鎖不釋放,則由其它線程來釋放鎖
當其它線程通過get key 擷取到時間發現已經逾時了,則可以進行鎖的擷取,
其它線程通過使用getset命令來對key進行設定,如果返回的值(舊值) 等於 自己發送過去設定的值(新值),則表示當前線程擷取到了鎖,如果不一致,則表示其它線程擷取到了鎖,
疑問來了,如果getset執行成功了,但是返回的值和該線程設定的值不一致,會不會影響其它線程? 不會哈,因為這個時間改動範圍是很小很小的,可以忽略了
各個伺服器時間一定要同步哦
以上就是基本的實現思路了
在自己實現過程中,發現了一個較好的開源項目,也是基於redis, 地址:https://github.com/redisson/redisson/wiki
並且可以快速的和springmvc等框架組成
下面紅色標註的就是我在整合過程中遇見的問題,一定要小心
1.pom中對jar包進行引入(jedis相關jar包也要引入哦)
1 <!--Redisson --> 2 <dependency> 3 <groupId>org.redisson</groupId> 4 <artifactId>redisson</artifactId> 5 <version>3.4.3</version> 6 <exclusions> 7 <exclusion> 8 <groupId>org.slf4j</groupId> 9 <artifactId>slf4j-api</artifactId>10 </exclusion>11 <exclusion>12 <groupId>com.fasterxml.jackson.core</groupId>13 <artifactId>jackson-databind</artifactId>14 </exclusion>15 <exclusion>16 <groupId>com.fasterxml.jackson.core</groupId>17 <artifactId>jackson-core</artifactId>18 </exclusion>19 </exclusions>20 </dependency>
2.增加spring整合設定檔 applicationContext-redission.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:redisson="http://redisson.org/schema/redisson" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://redisson.org/schema/redisson classpath:org/redisson/spring/support/redisson-1.1.xsd"> <redisson:client id="redissonClient"> <redisson:single-server address="${redis.address}"/> </redisson:client> <!-- <redisson:client> --> <!-- <redisson:cluster-servers> --> <!-- <redisson:node-address value="redis://192.168.0.32:7000" /> --> <!-- <redisson:node-address value="redis://192.168.0.32:7001" /> --> <!-- <redisson:node-address value="redis://192.168.0.32:7002" /> --> <!-- <redisson:node-address value="redis://192.168.0.32:7003" /> --> <!-- <redisson:node-address value="redis://192.168.0.32:7004" /> --> <!-- <redisson:node-address value="redis://192.168.0.32:7005" /> --> <!-- </redisson:cluster-servers> --> <!-- </redisson:client> --></beans>
注意address值格式為: redis://ip:port 如: redis://192.168.0.32:6379
classpath:org/redisson/spring/support/redisson-1.1.xsd 注意定義檔案是這麼引入的,開源wiki裡面可不是這麼寫的,小坑了我一下
3.使用就簡單了,我簡單寫了一個demo進行了測試,同時也可以部署兩套,同時發起請求進行測試
1 @Autowired 2 private RedissonClient redissonClient; 3 4 private static AtomicInteger st = new AtomicInteger(0); 5 6 @RequestMapping("/test/redission") 7 public void test() { 8 st.getAndSet(0); 9 for(int i=0;i<=9999;i++){10 new Thread(new Runnable() {11 @Override12 public void run() {13 test(String.valueOf(st.getAndIncrement()));14 }15 }).start();16 }17 18 }19 20 public void test(String value) {21 RLock rLock = redissonClient.getLock("anyLock");22 boolean res = false;23 try {24 res = rLock.tryLock(200, 10, TimeUnit.SECONDS);25 if (res) {26 System.out.println(String.format("%04d",Integer.valueOf(value)));27 //System.out.println("開始執行業務:" + value + ", " + Thread.currentThread().getName() + ", " + format.format(new Date()) + ", 取得的值為:" + String.valueOf(value));28 if(Integer.valueOf(value) % 1000 == 0){29 Thread.sleep(1000);30 } else {31 //Thread.sleep(1000);32 }33 //System.out.println("業務執行結束:" + value + ", " + Thread.currentThread().getName() + ", " + format.format(new Date()));34 } else {35 System.out.println("not lock:"+String.format("%04d",Integer.valueOf(value)));36 }37 } catch (InterruptedException e) {38 e.printStackTrace();39 } finally {40 if(rLock.isHeldByCurrentThread()){41 rLock.unlock();42 }43 }44 }
啟動多個線程類比並發訪問,根據值來進行區分,同時可以調整逾時時間來進行測試鎖逾時時的情況,具體使用參照:https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
在我進行壓力測試過程中發現,使用公平鎖效率要低很多,其它的鎖暫時還沒進行過壓力測試,不知道具體情況、
但具體業務中,肯定是不同業務使用不同的鎖,千萬不要整個系統中不同的業務都使用一個鎖哈,只要不相互關聯就要完全分開
以上純屬個人思路,有錯誤的地方敬請指正
分布式鎖實現思路及開源項目整合到springmvc並使用