最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

redis 解決并發(fā) java

2022-12-04 20:43 作者:限量版范兒  | 我要投稿

1、為什么要使用分布式鎖

如果在一個(gè)分布式系統(tǒng)中,我們從數(shù)據(jù)庫(kù)中讀取一個(gè)數(shù)據(jù),然后修改保存,這種情況很容易遇到并發(fā)問(wèn)題。因?yàn)樽x取和更新保存不是一個(gè)原子操作,在并發(fā)時(shí)就會(huì)導(dǎo)致數(shù)據(jù)的不正確。這種場(chǎng)景其實(shí)并不少見(jiàn),比如電商秒殺活動(dòng),庫(kù)存數(shù)量的更新就會(huì)遇到。如果是單機(jī)應(yīng)用,直接使用本地鎖就可以避免。如果是分布式應(yīng)用,本地鎖派不上用場(chǎng),這時(shí)就需要引入分布式鎖來(lái)解決。

由此可見(jiàn)分布式鎖的目的其實(shí)很簡(jiǎn)單,就是為了保證多臺(tái)服務(wù)器在執(zhí)行某一段代碼時(shí)保證只有一臺(tái)服務(wù)器執(zhí)行。

2、為了保證分布式鎖的可用性,至少要確保鎖的實(shí)現(xiàn)要同時(shí)滿足以下幾點(diǎn):

  • 互斥性。在任何時(shí)刻,保證只有一個(gè)客戶端持有鎖。

  • 不能出現(xiàn)死鎖。如果在一個(gè)客戶端持有鎖的期間,這個(gè)客戶端崩潰了,也要保證后續(xù)的其他客戶端可以上鎖。

  • 保證上鎖和解鎖都是同一個(gè)客戶端。

3、一般來(lái)說(shuō),實(shí)現(xiàn)分布式鎖的方式有以下幾種:

  • 使用MySQL,基于唯一索引。

  • 使用ZooKeeper,基于臨時(shí)有序節(jié)點(diǎn)。

  • 使用Redis,基于set命令(2.6.12 版本開始)。

本篇文章主要講解Redis的實(shí)現(xiàn)方式。

4、用到的redis命令

鎖的實(shí)現(xiàn)主要基于redis的SET命令(SET詳細(xì)解釋參考這里),我們來(lái)看SET的解釋:

SET key value [EX seconds] [PX milliseconds] [NX|XX]

  • 將字符串值 value 關(guān)聯(lián)到 key 。

  • 如果 key 已經(jīng)持有其他值, SET 就覆寫舊值,無(wú)視類型。

  • 對(duì)于某個(gè)原本帶有生存時(shí)間(TTL)的鍵來(lái)說(shuō), 當(dāng) SET 命令成功在這個(gè)鍵上執(zhí)行時(shí), 這個(gè)鍵原有的 TTL 將被清除。
    可選參數(shù)

從 Redis 2.6.12 版本開始, SET 命令的行為可以通過(guò)一系列參數(shù)來(lái)修改:

EX second :設(shè)置鍵的過(guò)期時(shí)間為 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :設(shè)置鍵的過(guò)期時(shí)間為 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在鍵不存在時(shí),才對(duì)鍵進(jìn)行設(shè)置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在鍵已經(jīng)存在時(shí),才對(duì)鍵進(jìn)行設(shè)置操作。

加鎖:使用SET key value [PX milliseconds] [NX]命令,如果key不存在,設(shè)置value,并設(shè)置過(guò)期時(shí)間(加鎖成功)。如果已經(jīng)存在lock(也就是有客戶端持有鎖了),則設(shè)置失敗(加鎖失敗)。

解鎖:使用del命令,通過(guò)刪除鍵值釋放鎖。釋放鎖之后,其他客戶端可以通過(guò)set命令進(jìn)行加鎖。

5、上面第二項(xiàng),說(shuō)了分布式鎖,要考慮的問(wèn)題,下面講解一下

5.1、互斥性。在任何時(shí)刻,保證只有一個(gè)客戶端持有鎖

redis命令是原子性的,只要客戶端調(diào)用redis的命令SET key value [PX milliseconds] [NX] 執(zhí)行成功,就算加鎖成功了

5.2、不能出現(xiàn)死鎖。如果在一個(gè)客戶端持有鎖的期間,這個(gè)客戶端崩潰了,也要保證后續(xù)的其他客戶端可以上鎖。

set命令px設(shè)置了過(guò)期時(shí)間,key過(guò)期失效了,就能避免死鎖了

5.3保證上鎖和解鎖都是同一個(gè)客戶端。

釋放鎖(刪除key)的時(shí)候,只要確保是當(dāng)前客戶端設(shè)置的value才去刪除key即可,采用lua腳本來(lái)實(shí)現(xiàn)

在Redis中,執(zhí)行Lua語(yǔ)言是原子性,也就是說(shuō)Redis執(zhí)行Lua的時(shí)候是不會(huì)被中斷的,具備原子性,這個(gè)特性有助于Redis對(duì)并發(fā)數(shù)據(jù)一致性的支持。

6、java代碼實(shí)現(xiàn)

先把需要的jar包引入

? ? ? ?<dependency> ? ? ? ? ? ?<groupId>redis.clients</groupId> ? ? ? ? ? ?<artifactId>jedis</artifactId> ? ? ? ? ? ?<version>2.9.3</version> ? ? ? ?</dependency>

加鎖設(shè)置參數(shù)的實(shí)體類

import lombok.Data;//加鎖設(shè)置的參數(shù)@Datapublic class LockParam { ? ?//鎖的key ? ?private String lockKey; ? ?//嘗試獲得鎖的時(shí)間(單位:毫秒),默認(rèn)值:3000毫秒 ? ?private Long tryLockTime; ? ?//嘗試獲得鎖后,持有鎖的時(shí)間(單位:毫秒),默認(rèn)值:5000毫秒 ? ?private Long holdLockTime; ? ?public LockParam(String lockKey){ ? ? ? ?this(lockKey,1000*3L,1000*5L); ? ?}; ? ?public LockParam(String lockKey,Long tryLockTime){ ? ? ? ?this(lockKey,tryLockTime,1000*5L); ? ?}; ? ?public LockParam(String lockKey,Long tryLockTime,Long holdLockTime){ ? ? ? ?this.lockKey = lockKey; ? ? ? ?this.tryLockTime = tryLockTime; ? ? ? ?this.holdLockTime = holdLockTime; ? ?}; }

redis分布式具體代碼實(shí)現(xiàn)

import lombok.extern.slf4j.Slf4j;import redis.clients.jedis.Jedis;import java.util.Collections;import java.util.UUID;/** * redis分布式鎖 */@Slf4jpublic class RedisLock {//鎖key的前綴private final static String prefix_key = "redisLock:";//釋放鎖的lua腳本private final static ?String unLockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//執(zhí)行unLockScript腳本,釋放鎖成功值private final static ?Long unLockSuccess = 1L;//加鎖設(shè)置的參數(shù)(key值、超時(shí)時(shí)間、持有鎖的時(shí)間)private LockParam lockParam;//嘗試獲得鎖的截止時(shí)間【lockParam.getTryLockTime()+System.currentTimeMillis()】private Long tryLockEndTime;//redis加鎖的keyprivate String redisLockKey;//redis加鎖的vlausprivate String redisLockValue;//redis加鎖的成功標(biāo)示private Boolean holdLockSuccess= Boolean.FALSE;//jedis實(shí)例private Jedis jedis;//獲取jedis實(shí)例private Jedis getJedis(){return this.jedis; }//關(guān)閉jedisprivate void closeJedis(Jedis jedis){ jedis.close(); jedis = null; }public RedisLock(LockParam lockParam){if(lockParam==null){new RuntimeException("lockParam is null"); }if(lockParam.getLockKey()==null || lockParam.getLockKey().trim().length()==0){new RuntimeException("lockParam lockKey is error"); }this.lockParam = lockParam;this.tryLockEndTime = lockParam.getTryLockTime()+System.currentTimeMillis();this.redisLockKey = prefix_key.concat(lockParam.getLockKey());this.redisLockValue = UUID.randomUUID().toString().replaceAll("-","");//todo 到時(shí)候可以更換獲取Jedis實(shí)例的實(shí)現(xiàn) jedis = new Jedis("127.0.0.1",6379); }/** * 加鎖 * @return 成功返回true,失敗返回false */public boolean lock() {while(true){//判斷是否超過(guò)了,嘗試獲取鎖的時(shí)間if(System.currentTimeMillis()>tryLockEndTime){return false; }//嘗試獲取鎖 holdLockSuccess = tryLock();if(Boolean.TRUE.equals(holdLockSuccess)){return true;//獲取鎖成功 }try {//獲得鎖失敗,休眠50毫秒再去嘗試獲得鎖,避免一直請(qǐng)求redis,導(dǎo)致redis cpu飆升Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } }/** * 執(zhí)行一次加鎖操作:成功返回true 失敗返回false * @return 成功返回true,失敗返回false */private boolean tryLock() {try {String result = getJedis().set(redisLockKey,redisLockValue, "NX", "PX", lockParam.getHoldLockTime());if ("OK".equals(result)) {return true; } }catch (Exception e){ log.warn("tryLock failure redisLockKey:{} redisLockValue:{} lockParam:{}",redisLockKey,redisLockValue,lockParam,e); }return false; }/** * 解鎖 * @return 成功返回true,失敗返回false */public Boolean unlock() {Object result = null;try {//獲得鎖成功,才執(zhí)行l(wèi)ua腳本if(Boolean.TRUE.equals(holdLockSuccess)){//執(zhí)行Lua腳本 result = getJedis().eval(unLockScript, Collections.singletonList(redisLockKey), Collections.singletonList(redisLockValue));if (unLockSuccess.equals(result)) {//釋放成功return true; } } } catch (Exception e) { log.warn("unlock failure redisLockKey:{} redisLockValue:{} lockParam:{} result:{}",redisLockKey,redisLockValue,lockParam,result,e); } finally {this.closeJedis(jedis); }return false; } }

redis分布式鎖使用

import lombok.extern.slf4j.Slf4j;@Slf4jpublic class test {static String lockKey = "666";public static void main(String[] args) throws InterruptedException { log.info("下面測(cè)試兩個(gè)線程同時(shí),搶占鎖的結(jié)果");Thread thread1 = new Thread(()->{ testRedisLock(); }); thread1.setName("我是線程1");Thread thread2 = new Thread(()->{ testRedisLock(); }); thread2.setName("我是線程2");//同時(shí)啟動(dòng)線程 thread1.start(); thread2.start(); Thread.sleep(1000*20); log.info("-----------------我是一條分割線----------------"); log.info(""); log.info(""); log.info(""); log.info("下面是測(cè)試 ?一個(gè)線程獲取鎖成功后,由于業(yè)務(wù)執(zhí)行時(shí)間超過(guò)了設(shè)置持有鎖的時(shí)間,是否會(huì)把其他線程持有的鎖給釋放掉");Thread thread3 = new Thread(()->{ testRedisLock2(); }); thread3.setName("我是線程3"); thread3.start(); Thread.sleep(1000*1);//暫停一秒是為了讓線程3獲的到鎖Thread thread4 = new Thread(()->{ testRedisLock(); }); thread4.setName("我是線程4"); thread4.start(); }public static void testRedisLock(){LockParam lockParam = new LockParam(lockKey); lockParam.setTryLockTime(2000L);//2秒時(shí)間嘗試獲得鎖 lockParam.setHoldLockTime(1000*10L);//獲得鎖成功后持有鎖10秒時(shí)間RedisLock redisLock = new RedisLock(lockParam);try {Boolean lockFlag = redisLock.lock(); log.info("加鎖結(jié)果:{}",lockFlag);if(lockFlag){try {//20秒模擬處理業(yè)務(wù)代碼時(shí)間 Thread.sleep(1000*5L); } catch (InterruptedException e) { e.printStackTrace(); } } }catch (Exception e) { log.info("testRedisLock e---->",e); }finally {boolean unlockResp = redisLock.unlock(); log.info("釋放鎖結(jié)果:{}",unlockResp); } }public static void testRedisLock2(){LockParam lockParam = new LockParam(lockKey); lockParam.setTryLockTime(1000*2L);//2秒時(shí)間嘗試獲得鎖 lockParam.setHoldLockTime(1000*2L);//獲得鎖成功后持有鎖2秒時(shí)間RedisLock redisLock = new RedisLock(lockParam);try {Boolean lockFlag = redisLock.lock(); log.info("加鎖結(jié)果:{}",lockFlag);if(lockFlag){try {//10秒模擬處理業(yè)務(wù)代碼時(shí)間 Thread.sleep(1000*10L); } catch (InterruptedException e) { e.printStackTrace(); } } }catch (Exception e) { log.info("testRedisLock e---->",e); }finally {boolean unlockResp = redisLock.unlock(); log.info("釋放鎖結(jié)果:{}",unlockResp); } } }

這是代碼在執(zhí)行過(guò)程中,通過(guò)redis可視化工具看到的效果,可以參考一下~

控制臺(tái)日志打印結(jié)果

15:02:28.569 [main] INFO com.test.test - 下面測(cè)試兩個(gè)線程同時(shí),搶占鎖的結(jié)果15:02:28.645 [我是線程2] INFO com.test.test - 加鎖結(jié)果:true15:02:30.618 [我是線程1] INFO com.test.test - 加鎖結(jié)果:false15:02:30.620 [我是線程1] INFO com.test.test - 釋放鎖結(jié)果:false15:02:33.652 [我是線程2] INFO com.test.test - 釋放鎖結(jié)果:true15:02:48.614 [main] INFO com.test.test - -----------------我是一條分割線----------------15:02:48.614 [main] INFO com.test.test - 15:02:48.614 [main] INFO com.test.test - 15:02:48.614 [main] INFO com.test.test - 15:02:48.614 [main] INFO com.test.test - 下面是測(cè)試 ?一個(gè)線程獲取鎖成功后,由于業(yè)務(wù)執(zhí)行時(shí)間超過(guò)了設(shè)置持有鎖的時(shí)間,是否會(huì)把其他線程持有的鎖給釋放掉15:02:48.616 [我是線程3] INFO com.test.test - 加鎖結(jié)果:true15:02:50.645 [我是線程4] INFO com.test.test - 加鎖結(jié)果:true15:02:55.647 [我是線程4] INFO com.test.test - 釋放鎖結(jié)果:true15:02:58.621 [我是線程3] INFO com.test.test - 釋放鎖結(jié)果:false

  • 可以看到多個(gè)線程競(jìng)爭(zhēng)一把鎖的時(shí)候,保證了只有一個(gè)線程持有鎖

  • 分割線下面的日志也能看出,一個(gè)線程持有了鎖,由于處理業(yè)務(wù)代碼時(shí)間,超過(guò)了設(shè)置持有鎖的時(shí)間,通過(guò)lua腳本釋放鎖的時(shí)候,也不會(huì)把其他線程持有的鎖給釋放掉,保證了安全釋放了鎖

7、分布式鎖 實(shí)際使用中需要注意的一些問(wèn)題

假設(shè)有這樣一個(gè)場(chǎng)景: 有一個(gè)修改訂單狀態(tài)的接口,訂單狀態(tài)修改為失敗,就不允許在修改為其他狀態(tài)了;
在單臺(tái)機(jī)器上,在代碼方法上加了synchronized來(lái)做并發(fā)控制,由于代碼邏輯比較復(fù)雜,現(xiàn)在它的TPS是1,一秒就只能處理一個(gè)訂單。
后面對(duì)這個(gè)系統(tǒng)做集群,部署了一百臺(tái),那么這個(gè)接口性能就提升了100倍了。
但是synchronized是進(jìn)程級(jí)別的鎖,在集群環(huán)境下synchronized沒(méi)辦法控制其他服務(wù)器下線程并發(fā)訪問(wèn) 臨界代碼了,后面就采用了分布式鎖來(lái)做并發(fā)控制。

7.1、那么使用分布鎖要注意什么了?

7.1.1、鎖粒度

如果分布式鎖的key 設(shè)置的是?redisLock:updateOrderStatus?相當(dāng)于集群下對(duì)這個(gè)接口加了相同的一把大鎖,按照上面那個(gè)場(chǎng)景TPS就變成1了,集群部署就浪費(fèi)了。

7.1.2、那么如何控制鎖粒度了?

平常我們修改訂單的時(shí)候都有訂單號(hào),那么分布式的key可以設(shè)置為:redisLock:updateOrderStatus:{orderCode}?,{orderCode}執(zhí)行的時(shí)候動(dòng)態(tài)的替換成訂單編號(hào),那么鎖粒度就控制到這條訂單了,就跟數(shù)據(jù)庫(kù)從表鎖 變成了行鎖一樣,接口支持更高的并發(fā)了。

7.1.3、獲取鎖時(shí)間

如果時(shí)間設(shè)置的太長(zhǎng):用戶就會(huì)等待太久才能得到響應(yīng)結(jié)果
太短:頻繁獲取鎖失敗,用戶體驗(yàn)性也不好
只能按照不同的業(yè)務(wù)代碼,由開發(fā)人員來(lái)衡量設(shè)置多長(zhǎng)的時(shí)間

7.1.4、持有鎖時(shí)間:

如果鎖粒度比較小,時(shí)間可以設(shè)置長(zhǎng)一點(diǎn),就算執(zhí)行比較慢,影響面比較小可以接受

7.1.5、難道每次想使用分布式鎖的時(shí)候都需要下面流程一樣,在編碼一次?有什么辦法能優(yōu)化嗎?

1、先創(chuàng)建一個(gè) 分布式鎖對(duì)象;RedisLock redisLock = new RedisLock(lockParam);
2、加鎖;Boolean lockFlag = redisLock.lock();
3、finally 解鎖;redisLock.unlock();

鏈接:https://www.dianjilingqu.com/628686.html

redis 解決并發(fā) java的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
雷山县| 三江| 莱芜市| 分宜县| 东山县| 句容市| 肇源县| 四子王旗| 中超| 凌云县| 南安市| 五寨县| 芒康县| 潜江市| 调兵山市| 临洮县| 阿坝县| 宁德市| 儋州市| 舟山市| 台南市| 利川市| 旺苍县| 正镶白旗| 重庆市| 阿拉善盟| 阜阳市| 香格里拉县| 元氏县| 醴陵市| 平罗县| 中阳县| 奉新县| 巧家县| 牙克石市| 乌审旗| 福建省| 黄平县| 达孜县| 平南县| 兴仁县|