日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

千帆竞发-Redis分布式锁

發(fā)布時(shí)間:2023/12/16 数据库 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 千帆竞发-Redis分布式锁 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

千帆競(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) 分布式鎖

分布式鎖需要具備的條件和剛需:

  • 獨(dú)占性
  • 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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。