千帆竞发-Redis分布式锁
千帆競(jìng)發(fā)-Redis分布式鎖
好久沒(méi)有寫(xiě)博客了,這是在公司進(jìn)行技術(shù)分享時(shí)寫(xiě)的一篇科普性質(zhì)的文章,在公司CRUD久了人容易失去對(duì)技術(shù)的向往,從而失去在如今大環(huán)境惡劣中的競(jìng)爭(zhēng)力,大家一定要保持對(duì)技術(shù)的熱愛(ài),對(duì)生活和工作亦是如此!
1.前言
小立,剛?cè)肼毮畴娚躺坛?#xff0c;第一周分到的需求就是公司商品秒殺,在了解完需求后就開(kāi)始著手設(shè)計(jì)方案,整體的方案是Redis 緩存+異步同步數(shù)據(jù)到數(shù)據(jù)庫(kù)。
實(shí)現(xiàn)思路
1秒殺前 將商品庫(kù)存信息從數(shù)據(jù)庫(kù)同步到redis
2.依靠redis來(lái)保證原子性
3.根據(jù)對(duì)應(yīng)的返回結(jié)果,將訂單數(shù)據(jù)放入redis訂閱中 或者M(jìn)Q中進(jìn)行投遞 ,防止服務(wù)器等問(wèn)題還可以 開(kāi)一個(gè)定時(shí)器去掃描這個(gè)庫(kù)存信息和訂單信息 進(jìn)行一個(gè)補(bǔ)償
4.消費(fèi)者收到數(shù)據(jù)后,持久到數(shù)據(jù)庫(kù)中
2.開(kāi)始編碼
2.1寫(xiě)第一版代碼:
/***weng@*/ @RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods(){String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;寫(xiě)完發(fā)現(xiàn)沒(méi)有加鎖,在生產(chǎn)環(huán)境下發(fā)生了超賣(mài)問(wèn)題,a b用戶(hù)同時(shí)搶到了同一個(gè)商品,直接挨屌。
2.2寫(xiě)第二版代碼:
想到學(xué)過(guò)JUC 加上鎖就能防止2個(gè)用戶(hù)同時(shí)獲取同一個(gè)商品問(wèn)題,這個(gè)時(shí)候就考慮是使用 synchronized 還是 ReetranLock了 2者都能實(shí)現(xiàn)鎖功能但要選擇哪種這個(gè)就犯難了 ,精細(xì)控制下還是 選擇r更好 synchronized 必須要釋放鎖 或者出現(xiàn)異常被動(dòng)釋放鎖 在高并發(fā)情況下應(yīng)該等得到就等 等不到就不等(1不見(jiàn)不散 , 2過(guò)時(shí)不候 )
synchronized執(zhí)行完同步方法或者代碼塊,才會(huì)釋放鎖 并發(fā)性下降。
reetranLock if (lock.tryLock()) 或者 if (lock.tryLock(2L,TimeUnit.SECONDS)) 拿得到就執(zhí)行 ,拿不到就走 提高并發(fā)。
對(duì)比之下使用ReetranLock會(huì)更好。
/***weng@*/ @RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private final Lock lock = new ReentrantLock();@GetMapping("/buy_goods")public String buyGoods() throws InterruptedException{/*synchronized (this){String number = stringRedisTemplate.opsForValue().get("goods:001");int realNumber = number == null ? 0 : Integer.parseInt(number);if(realNumber > 0){realNumber = realNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(realNumber));return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件";}}*///if (lock.tryLock(2L,TimeUnit.SECONDS))if (lock.tryLock()){try{String number = stringRedisTemplate.opsForValue().get("goods:001");int realNumber = number == null ? 0 : Integer.parseInt(number);if(realNumber > 0){realNumber = realNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(realNumber));return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件";}}finally {lock.unlock();}}return "商品售罄/活動(dòng)結(jié)束,歡迎下次光臨";} }這樣編寫(xiě)完成后,上線(xiàn)之后沒(méi)有發(fā)生類(lèi)似 a b 同時(shí)搶同一件商品的情況了,不久后公司技術(shù)架構(gòu)從傳統(tǒng)單體項(xiàng)目升級(jí)到微服務(wù)架構(gòu),從單個(gè)JVM虛擬機(jī)變成了分布式 不同虛擬機(jī)內(nèi),這也導(dǎo)致單機(jī)的線(xiàn)程鎖機(jī)制不在起作用 ,資源在不同的服務(wù)器之間共享,那么這個(gè)時(shí)候就要引出“分布式鎖”。
3.分布式鎖
分布式鎖本質(zhì)上要實(shí)現(xiàn)的目標(biāo)就是在占一個(gè)“茅坑”,當(dāng)別的進(jìn)程也要來(lái)占 時(shí),發(fā)現(xiàn)已經(jīng)有人蹲在那里了,就只好放棄或者稍后再試。 占坑一般是只允許被一個(gè)客戶(hù)端占坑。先來(lái)先占, 用完了,再調(diào)用 del 指令釋放茅坑。
常見(jiàn)實(shí)現(xiàn)分布式鎖的方式有1通過(guò)redis , 2.通過(guò)MySQL(基本沒(méi)有使用) 3.通過(guò)zk , 3.mysql 通過(guò) 悲觀鎖和樂(lè)觀鎖,悲觀鎖 select where for update 鎖表 樂(lè)觀鎖 cas 思想 update version zk 通過(guò)不斷創(chuàng)建臨時(shí)節(jié)點(diǎn)實(shí)現(xiàn) 性能 沒(méi)有redis 性能強(qiáng) 目前最主流的redis 來(lái)實(shí)現(xiàn) 分布式鎖
分布式鎖需要具備的條件和剛需:
OnlyOne,任何時(shí)刻只能有且僅有一個(gè)線(xiàn)程持有
2.高可用
若redis集群環(huán)境下,不能因?yàn)槟骋粋€(gè)節(jié)點(diǎn)掛了而出現(xiàn)獲取鎖和釋放鎖失敗的情況
3.防死鎖
杜絕死鎖,必須有超時(shí)控制機(jī)制或者撤銷(xiāo)操作,有個(gè)兜底終止跳出方案
4.不亂搶
防止張冠李戴,不能私下unlock別人的鎖,只能自己加鎖自己釋放
5.重入性
同一個(gè)節(jié)點(diǎn)的同一個(gè)線(xiàn)程如果獲得鎖之后,它也可以再次獲取這個(gè)鎖
分布式鎖:
set key value [ex seconds] [px milliseconds] [nx|xx]
ex key在多少秒之后過(guò)期
px key在多少毫秒之后過(guò)期
nx 當(dāng)key 不存在時(shí)才創(chuàng)建key 效果等同于setnx
xx 當(dāng)key 存在時(shí)覆蓋 key
setnx key value + expire 存在不安全 2條命令非原子性操作
小立了解后開(kāi)始對(duì)原來(lái)的代碼進(jìn)行升級(jí)支持分布式環(huán)境下使用
3.1 第一版代碼
@RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods(){String key = "wengRedisLock";String value = UUID.randomUUID().toString()+Thread.currentThread().getName();Boolean flagLock = stringRedisTemplate.opsForValue().setIfAbsent(key, value);if(!flagLock){return "搶奪鎖失敗";}String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");stringRedisTemplate.delete(key);System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" +realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;} }突然有一天咋線(xiàn)上加鎖之后的業(yè)務(wù)代碼發(fā)生異常,但分布式鎖沒(méi)有解放導(dǎo)致遲遲不能執(zhí)行其他的業(yè)務(wù)方法
3.2 第二版代碼
@RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods(){String key = "wengRedisLock";String value = UUID.randomUUID().toString()+Thread.currentThread().getName();try {Boolean flagLock = stringRedisTemplate.opsForValue().setIfAbsent(key, value);if(!flagLock){return "搶鎖失敗";}String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;} finally {stringRedisTemplate.delete(key);}} }我靠突然有一天公司的服務(wù)器上的微服務(wù)jar 包 突然掛了代碼層面根本沒(méi)有走到finally這塊,沒(méi)辦法保證解鎖,這個(gè)key沒(méi)有被刪除
3.3第三版代碼
要解決這個(gè)問(wèn)題必須要在redis分布式鎖加入對(duì)應(yīng)的過(guò)期時(shí)間,放在出現(xiàn)類(lèi)似這種情況 沒(méi)辦法保證解鎖,這個(gè)key沒(méi)有被刪除,需要加入一個(gè)過(guò)期時(shí)間限定key
@RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods(){String key = "wengRedisLock";String value = UUID.randomUUID().toString()+Thread.currentThread().getName();try {Boolean flagLock = stringRedisTemplate.opsForValue().setIfAbsent(key, value);stringRedisTemplate.expire(key,10L,TimeUnit.SECONDS);if(!flagLock){return "搶鎖失敗";}String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;} finally {stringRedisTemplate.delete(key);}} }突然在公司周年慶時(shí)流量QPS 提高很多,在購(gòu)買(mǎi)商品時(shí)發(fā)生在一個(gè)時(shí)刻商品明明有貨但用戶(hù)不能購(gòu)買(mǎi)被別人占有有,結(jié)果發(fā)現(xiàn)是設(shè)置key +過(guò)期不是在同一個(gè)原子操作中
3.4 第四版代碼
解決上面的這個(gè)問(wèn)題,將設(shè)置key和過(guò)期時(shí)間放到一個(gè)命令,保證原子性
原子操作就是: 不可中斷的一個(gè)或者一系列操作, 也就是不會(huì)被線(xiàn)程調(diào)度機(jī)制打斷的操作, 運(yùn)行期間不會(huì)有任何的上下文切換(context switch)
@RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods(){String key = "wymRedisLock";String value = UUID.randomUUID().toString()+Thread.currentThread().getName();try {Boolean flagLock = stringRedisTemplate.opsForValue().setIfAbsent(key,value,10L,TimeUnit.SECONDS);if(!flagLock){return "搶鎖失敗,o(╥﹏╥)o";}String result =stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;} finally {stringRedisTemplate.delete(key);}} }上線(xiàn)一段時(shí)間后發(fā)現(xiàn) 可能在設(shè)置的過(guò)期時(shí)間業(yè)務(wù)還未完成,鎖已經(jīng)被刪了,然后finally塊中就可能會(huì)刪除別的服務(wù)創(chuàng)建的鎖,張冠李戴
3.5 第五版代碼
解決上面的問(wèn)題 只能刪除自己創(chuàng)建的鎖,不能動(dòng)別人的
@RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods(){String key = "wengRedisLock";String value = UUID.randomUUID().toString()+Thread.currentThread().getName();try {Boolean flagLock = stringRedisTemplate.opsForValue().setIfAbsent(key,value,10L,TimeUnit.SECONDS);if(!flagLock){return "搶鎖失敗,o(╥﹏╥)o";}String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;} finally {if (stringRedisTemplate.opsForValue().get(key).equals(value)) {stringRedisTemplate.delete(key);}}} }在高qps 情況下 finally塊的判斷+del刪除操作不是原子性的,會(huì)發(fā)現(xiàn)商品明明在卻不能購(gòu)買(mǎi)的情況
3.6第六版代碼
使用Lua腳本Redis調(diào)用Lua腳本通過(guò)eval命令保證代碼執(zhí)行的原子性
使用jdeis@RestController public class GoodController {public static final String REDIS_LOCK_KEY = "redisLockPay";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods(){String value = UUID.randomUUID().toString()+Thread.currentThread().getName();try {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value,30L,TimeUnit.SECONDS);if(!flag){return "搶奪鎖失敗,請(qǐng)下次嘗試";}String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;} finally {Jedis jedis = RedisUtils.getJedis();String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +"then " +"return redis.call('del', KEYS[1]) " +== ARGV[1] " +"then " +"return redis.call('del', KEYS[1]) " +"else " +" return 0 " +"end";try {Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(value));if ("1".equals(result.toString())) {System.out.println("------del REDIS_LOCK_KEY success");}else{System.out.println("------del REDIS_LOCK_KEY error");}} finally {if(null != jedis) {jedis.close();}}}} }終于一系列的迭代之后基于Redis實(shí)現(xiàn)分布式鎖達(dá)到了分布式要具備的獨(dú)占性,防死鎖,不亂搶。
4.集群環(huán)境
? 隨著公司業(yè)務(wù)的發(fā)展,技術(shù)是服務(wù)業(yè)務(wù)的,redis 架構(gòu)也演進(jìn)到了集群環(huán)境 ,隨著而來(lái)在之前的單節(jié)點(diǎn)實(shí)現(xiàn)的redis 分布式鎖也暴露出了redisLock過(guò)期時(shí)間小于業(yè)務(wù)執(zhí)行時(shí)間的問(wèn)題 ,redis分布式鎖續(xù)費(fèi)的問(wèn)題,在集群環(huán)境下redis 異步復(fù)制造成鎖丟失問(wèn)題, 比如:主節(jié)點(diǎn)沒(méi)來(lái)的及把剛剛set進(jìn)來(lái)這條數(shù)據(jù)給從節(jié)點(diǎn),master就掛了,從機(jī)上位但從機(jī)上無(wú)該數(shù)據(jù) … redis 環(huán)境下 自己寫(xiě)的也不ok 要考慮的東西太多了,直接上 RedLock 的Redisson落地 。
4.1第一版代碼
@RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@Autowiredprivate Redisson redisson;@GetMapping("/buy_goods")public String buy_Goods(){String key = "wengRedisLock";RLock redissonLock = redisson.getLock(key);redissonLock.lock();try{String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;}finally {redissonLock.unlock();}} }按照之前單機(jī)版的 可能解鎖了別服務(wù)創(chuàng)建的鎖,所以解鎖時(shí)需要判斷當(dāng)前鎖是否是自己創(chuàng)建的那個(gè),避免張冠李戴
4.2 第二版代碼
@RestController public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@Autowiredprivate Redisson redisson;@GetMapping("/buy_goods")public String buy_Goods(){String key = "wengRedisLock";RLock redissonLock = redisson.getLock(key);redissonLock.lock();try{String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort);return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口:"+serverPort;}else{System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort);}return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口:"+serverPort;}finally {if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){redissonLock.unlock();}}} }1setnx分布式鎖缺點(diǎn)
嚴(yán)禁出現(xiàn)2個(gè)以上的請(qǐng)求線(xiàn)程拿到鎖。危險(xiǎn)的
? 使用到的 就是Redlock (紅鎖)算法 大概就是客戶(hù)端發(fā)送setnx指令,同時(shí)向多個(gè)redis節(jié)點(diǎn)發(fā)信息,超過(guò)半數(shù)redis節(jié)點(diǎn)加鎖成功,才會(huì)返回成功 具體的落地實(shí)現(xiàn)是redisson客戶(hù)端工具。鎖變量由多個(gè)實(shí)例維護(hù),即使有實(shí)例發(fā)生了故障,鎖變量仍然是存在的,客戶(hù)端還是可以完成鎖操作。Redlock算法是實(shí)現(xiàn)高可靠分布式鎖的一種有效解決方案,可以在實(shí)際開(kāi)發(fā)中使用。
在使用 這套理論時(shí) 該方案為了解決數(shù)據(jù)不一致的問(wèn)題,直接舍棄了異步復(fù)制只使用 master 節(jié)點(diǎn),同時(shí)由于舍棄了 slave,為了保證可用性,引入了 N 個(gè)節(jié)點(diǎn)
N = 2X + 1 (N是最終部署機(jī)器數(shù),X是容錯(cuò)機(jī)器數(shù))
失敗了多少個(gè)機(jī)器實(shí)例后我還是可以容忍的,所謂的容忍就是數(shù)據(jù)一致性還是可以O(shè)k的,CP數(shù)據(jù)一致性還是可以滿(mǎn)足
加入在集群環(huán)境中,redis失敗1臺(tái),可接受。2X+1 = 2 * 1+1 =3,部署3臺(tái),死了1個(gè)剩下2個(gè)可以正常工作,那就部署3臺(tái)。
加入在集群環(huán)境中,redis失敗2臺(tái),可接受。2X+1 = 2 * 2+1 =5,部署5臺(tái),死了2個(gè)剩下3個(gè)可以正常工作,那就部署5臺(tái)。
4.3 第三版代碼
@RestController @Slf4j public class RedLockController {public static final String CACHE_KEY_REDLOCK = "ZZYY_REDLOCK";@AutowiredRedissonClient redissonClient1;@AutowiredRedissonClient redissonClient2;@AutowiredRedissonClient redissonClient3;@GetMapping(value = "/redlock")public void getlock() {//CACHE_KEY_REDLOCK為redis 分布式鎖的keyRLock lock1 = redissonClient1.getLock(CACHE_KEY_REDLOCK);RLock lock2 = redissonClient2.getLock(CACHE_KEY_REDLOCK);RLock lock3 = redissonClient3.getLock(CACHE_KEY_REDLOCK);RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);boolean isLock;try {//waitTime 鎖的等待時(shí)間處理,正常情況下 等5s//leaseTime就是redis key的過(guò)期時(shí)間,正常情況下等5分鐘。isLock = redLock.tryLock(5, 300, TimeUnit.SECONDS);log.info("線(xiàn)程{},是否拿到鎖:{} ",Thread.currentThread().getName(),isLock);if (isLock) {//TODO if get lock success, do something;//暫停20秒鐘線(xiàn)程try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}} catch (Exception e) {log.error("redlock exception ",e);} finally {// 無(wú)論如何, 最后都要解鎖redLock.unlock();System.out.println(Thread.currentThread().getName()+"\t"+"redLock.unlock()");}}}這樣就通過(guò)調(diào)用redisson-api實(shí)現(xiàn)高可用 集群分布式鎖
5.Redisson小結(jié)
看門(mén)狗 守護(hù)線(xiàn)程“緩存續(xù)命” 額外起一個(gè)線(xiàn)程,定期檢查線(xiàn)程是否還持有鎖,如果有則延長(zhǎng)過(guò)期時(shí)間,Redisson 里面就實(shí)現(xiàn)了這個(gè)方案使用“看門(mén)狗”定期檢查(每1/3的鎖時(shí)間檢查1次),如果線(xiàn)程還持有鎖,則刷新過(guò)期時(shí)間;在獲取鎖成功后,給鎖加一個(gè) watchdog,watchdog 會(huì)起一個(gè)定時(shí)任務(wù),在鎖沒(méi)有被釋放且快要過(guò)期的時(shí)候會(huì)續(xù)期,具體的源碼可以 百度下周陽(yáng)老師的redis課程。
總結(jié)
以上是生活随笔為你收集整理的千帆竞发-Redis分布式锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux+qt+分屏显示界面,Qt5支
- 下一篇: Redis 深度历险: 核心原理和应用