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

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

生活随笔

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

数据库

分布式锁之Redis6+Lua脚本实现原生分布式锁

發(fā)布時(shí)間:2023/12/3 数据库 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 分布式锁之Redis6+Lua脚本实现原生分布式锁 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

        • 簡(jiǎn)介
        • 設(shè)計(jì)分布式鎖應(yīng)該考慮的東?
        • 基于Redis實(shí)現(xiàn)分布式鎖
        • 總結(jié)
        • 解決解鎖的原子性
        • 代碼實(shí)現(xiàn)
        • 遺留?個(gè)問(wèn)題

簡(jiǎn)介

分布式鎖核?知識(shí)介紹和注意事項(xiàng)

背景

就是保證同?時(shí)間只有?個(gè)客戶端可以對(duì)共享資源進(jìn)?操作

案例

優(yōu)惠券領(lǐng)劵限制張數(shù)、商品庫(kù)存超賣

核?

為了防?分布式系統(tǒng)中的多個(gè)進(jìn)程之間相互?擾,我們需要?種分布式協(xié)調(diào)技術(shù)來(lái)對(duì)這些進(jìn)程進(jìn)?調(diào)度利?互斥機(jī)制來(lái)控制共享資源的訪問(wèn),這就是分布式鎖要解決的問(wèn)題避免共享資源并發(fā)操作導(dǎo)致數(shù)據(jù)問(wèn)題

加鎖

本地鎖:synchronize、lock等,鎖在當(dāng)前進(jìn)程內(nèi),集群部署下依舊存在問(wèn)題
分布式鎖:redis、zookeeper等實(shí)現(xiàn),雖然還是鎖,但是多個(gè)進(jìn)程共?的鎖標(biāo)記,可以?Redis、Zookeeper、Mysql等都可以

設(shè)計(jì)分布式鎖應(yīng)該考慮的東?

1.排他性 --在分布式應(yīng)?集群中,同?個(gè)?法在同?時(shí)間只能被?臺(tái)機(jī)器上的?個(gè)線程執(zhí)?
2.容錯(cuò)性 --分布式鎖?定能得到釋放,?如客戶端奔潰或者?絡(luò)中斷
3.滿?可重?、?性能、?可?
4.注意分布式鎖的開(kāi)銷、鎖粒度

基于Redis實(shí)現(xiàn)分布式鎖

實(shí)現(xiàn)分布式鎖 可以? Redis、Zookeeper、Mysql數(shù)據(jù)庫(kù)這?種 , 性能最好的是Redis

分布式鎖離不開(kāi) key - value 設(shè)置
key 是鎖的唯?標(biāo)識(shí),?般按業(yè)務(wù)來(lái)決定命名,?如想要給?種優(yōu)惠券活動(dòng)加鎖,key 命名為 “coupon:id” 。value就可以使?固定值,?如設(shè)置成1

加鎖 SETNX key value
解鎖 del (key)
配置鎖超時(shí) expire (key,30s)

綜合偽代碼

1.setnx 的含義就是 SET if Not Exists,有兩個(gè)參數(shù)setnx(key, value),該?法是原?性操作
2.如果 key 不存在,則設(shè)置當(dāng)前 key 成功,返回 1
3.如果當(dāng)前 key 已經(jīng)存在,則設(shè)置當(dāng)前 key 失敗,返回 0
4.得到鎖的線程執(zhí)?完任務(wù),需要釋放鎖,以便其他線程可以進(jìn)?,調(diào)? del(key)
5.客戶端奔潰或者?絡(luò)中斷,資源將會(huì)永遠(yuǎn)被鎖住,即死鎖,因此需要給key配置過(guò)期時(shí)間,以保證即使沒(méi)有被顯式釋放,這把鎖也要在?定時(shí)間后?動(dòng)釋放

methodA(){String key = "coupon_66";if(setnx(key,1== 1{expire(key,30,TimeUnit.MILLISECONDS)try {//做對(duì)應(yīng)的業(yè)務(wù)邏輯//查詢?戶是否已經(jīng)領(lǐng)券//如果沒(méi)有則扣減庫(kù)存//新增領(lǐng)劵記錄} finally {del(key)}}else{//睡眠100毫秒,然后?旋調(diào)?本?法methodA()} }

存在什么問(wèn)題?

多個(gè)命令之間不是原?性操作,如setnx和expire之間,如果setnx成功,但是expire失敗,且宕機(jī)了,則這個(gè)資源就是死鎖業(yè)務(wù)超時(shí),存在其他線程勿刪,key 30秒過(guò)期,假如線程A執(zhí)?很慢超過(guò)30秒,則key就被釋放了,其他線程B就得到了鎖,這個(gè)時(shí)候線程A執(zhí)?完成,?B還沒(méi)執(zhí)?完成,結(jié)果就是線程A刪除了線程B加的鎖

問(wèn)題解決

使?原?命令:設(shè)置和配置過(guò)期時(shí)間 setnx / setex
如: set key 1 ex 30 nx
java??redisTemplate.opsForValue().setIfAbsent(“seckill_1”,“success”,30,TimeUnit.MILLISECONDS)
可以在 del 釋放鎖之前做?個(gè)判斷,驗(yàn)證當(dāng)前的鎖是不是??加的鎖, 那 value 應(yīng)該是存當(dāng)前線程的標(biāo)識(shí)或者uuid
String key = "coupon_66"String value = Thread.currentThread().getId()
進(jìn)?步細(xì)化誤刪當(dāng)線程A獲取到正常值時(shí),返回帶代碼中判斷期間鎖過(guò)期了,線程B剛好重新設(shè)置了新值,線程A那邊有判斷value是??的標(biāo)識(shí),然后調(diào)?del?法,結(jié)果就是刪除了新設(shè)置的線程B的值核?還是判斷和刪除命令不是原?性操作導(dǎo)致

總結(jié)

加鎖+配置過(guò)期時(shí)間:保證原?性操作
解鎖: 防?誤刪除、也要保證原?性操作

解決解鎖的原子性

前?說(shuō)了redis做分布式鎖存在的問(wèn)題核?是保證多個(gè)指令原?性,加鎖使?setnx setex 可以保證原?性,那解鎖使? 判斷和刪除怎么保證原?性

多個(gè)命令的原?性:采? lua腳本+redis, 由于【判斷和刪除】是lua腳本執(zhí)?,所以要么全成功,要么全失敗

//獲取lock的值和傳遞的值?樣,調(diào)?刪除操作返回1,否則返回0 String script = "if redis.call('get',KEYS[1])== ARGV[1] then returnredis.call('del',KEYS[1]) else return 0 end"; //Arrays.asList(lockKey)是key列表,uuid是參數(shù) Integer result = redisTemplate.execute(newDefaultRedisScript<>(script, Integer.class),Arrays.asList(lockKey), uuid);

代碼實(shí)現(xiàn)

import net.xdclass.xdclassredis.util.JsonData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;import java.time.Duration; import java.util.Arrays; import java.util.Random; import java.util.UUID; import java.util.concurrent.TimeUnit;@RestController @RequestMapping("/api/v1/coupon") public class CouponController {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@GetMapping("add")public JsonData saveCoupon(@RequestParam(value = "coupon_id",required = true) int couponId){//防止其他線程誤刪String uuid = UUID.randomUUID().toString();String lockKey = "lock:coupon:"+couponId;lock(couponId,uuid,lockKey);return JsonData.buildSuccess();}private void lock(int couponId,String uuid,String lockKey){//lua腳本String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30));System.out.println(uuid+"加鎖狀態(tài):"+nativeLock);if(nativeLock){//加鎖成功try{//TODO 做相關(guān)業(yè)務(wù)邏輯TimeUnit.SECONDS.sleep(10L);} catch (InterruptedException e) {} finally {//解鎖Long result = redisTemplate.execute( new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockKey),uuid);System.out.println("解鎖狀態(tài):"+result);}}else {//自旋操作try {System.out.println("加鎖失敗,睡眠5秒 進(jìn)行自旋");TimeUnit.MILLISECONDS.sleep(5000);} catch (InterruptedException e) { }//睡眠一會(huì)再嘗試獲取鎖lock(couponId,uuid,lockKey);}}}

遺留?個(gè)問(wèn)題

鎖的過(guò)期時(shí)間,如何實(shí)現(xiàn)鎖的?動(dòng)續(xù)期或者避免業(yè)務(wù)執(zhí)?時(shí)間過(guò)?,鎖過(guò)期了?

1.原??式的話,?般把鎖的過(guò)期時(shí)間設(shè)置久?點(diǎn),?如10分鐘時(shí)間
原?代碼+redis實(shí)現(xiàn)分布式鎖使??較復(fù)雜,且有些鎖續(xù)期問(wèn)題更難處理

2.框架 官?推薦?式:https://redis.io/topics/distlock 使?特別簡(jiǎn)單

總結(jié)

以上是生活随笔為你收集整理的分布式锁之Redis6+Lua脚本实现原生分布式锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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