分布式锁 哨兵模式_手撕redis分布式锁,隔壁张小帅都看懂了!
前言
上一篇老貓和小伙伴們分享了為什么要使用分布式鎖以及分布式鎖的實(shí)現(xiàn)思路原理,目前我們主要采用第三方的組件作為分布式鎖的工具。上一篇運(yùn)用了Mysql中的select …for update實(shí)現(xiàn)了分布式鎖,但是我們說(shuō)這種實(shí)現(xiàn)方式并不常用,因?yàn)楫?dāng)大并發(fā)量的時(shí)候,會(huì)給數(shù)據(jù)庫(kù)帶來(lái)比較大的壓力。當(dāng)然也有小伙伴給老貓留言說(shuō)“ 在quartz的集群模式中,就是使用了基于mysql的分布式鎖,select for update ”。沒錯(cuò),其實(shí)quartz的集群模式中,任務(wù)執(zhí)行的節(jié)點(diǎn)個(gè)數(shù)是可預(yù)知的,而且沒有那么大的量級(jí),所以是沒有問題的。但是如果像千萬(wàn)級(jí)別的并發(fā)秒殺場(chǎng)景的情況下,那么這種方案其實(shí)是不可行的。因?yàn)閙ysql操作是需要IO的,IO的速度比內(nèi)存速度慢,因此mysql如果在那種場(chǎng)景下使用的話是會(huì)存在系統(tǒng)瓶頸的。所以本篇就和小伙伴們分享基于內(nèi)存操作的比較常用的分布式鎖——redis分布式鎖。
手?jǐn)]Redis分布式鎖
實(shí)現(xiàn)原理
redis分布式鎖實(shí)現(xiàn)原理其實(shí)也是比較簡(jiǎn)單的,主要是依賴于redis的 set nx命令,我們來(lái)看一下完整的設(shè)置redis的命令:“Set resource_name my_random_value NX PX 30000”。看到這串命令,了解redis的小伙伴應(yīng)該都看得懂這條命令是在redis中存入一個(gè)帶有過(guò)期時(shí)間的值。具體上述設(shè)值語(yǔ)句解釋如下:
resource_name:資源名稱,可以根據(jù)不同的業(yè)務(wù)區(qū)分不同的鎖。(其實(shí)就是對(duì)應(yīng)我們上一篇myql鎖中的business_code)。
my_random_value:隨機(jī)值,每個(gè)線程的隨機(jī)值都不相同,主要用于釋放鎖的時(shí)候用來(lái)校驗(yàn)。
NX:key不存在的時(shí)候設(shè)置成功,key存在則設(shè)置不成功。
PX:自動(dòng)失效時(shí)間,如果出現(xiàn)異常情況,鎖可以過(guò)期實(shí)現(xiàn),因此達(dá)到了自動(dòng)釋放。
那么為什么可以使用這個(gè)思路呢?其實(shí)很簡(jiǎn)單,主要就是利用了set nx的原子性,在多個(gè)線程并發(fā)執(zhí)行時(shí),只有一個(gè)線程可以設(shè)置成功,如果設(shè)置成功,那么就代表著獲得了鎖,就可以執(zhí)行后續(xù)的業(yè)務(wù)。如果出現(xiàn)了異常,過(guò)了鎖的有效期,鎖會(huì)自動(dòng)釋放,釋放鎖主要采用了redis的delete命令,釋放鎖之前會(huì)校驗(yàn)當(dāng)前redis存儲(chǔ)的隨機(jī)數(shù),只有當(dāng)前的隨機(jī)數(shù)和存儲(chǔ)的隨機(jī)數(shù)一致的時(shí)候才允許釋放。具體的redis的刪除,我們可以通過(guò)lua腳本進(jìn)行刪除,具體Lua腳本如下:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
那么我們?yōu)槭裁匆捎眠@種方式釋放鎖呢?其實(shí)使用這種方式釋放鎖可以避免刪除別的客戶端獲取成功的鎖 。
如下圖:
客戶端A取得資源鎖,但是緊接著被一個(gè)其他操作阻塞了,當(dāng)客戶端A運(yùn)行完畢其他操作后要釋放鎖時(shí),原來(lái)的鎖早已超時(shí)并且被Redis自動(dòng)釋放,并且在這期間資源鎖又被客戶端B再次獲取到。如果僅使用DEL命令將key刪除,那么這種情況就會(huì)把客戶端B的鎖給刪除掉。使用Lua腳本就不會(huì)存在這種情況,因?yàn)槟_本僅會(huì)刪除value等于客戶端A的value的key(value相當(dāng)于客戶端的一個(gè)簽名)(說(shuō)明:其實(shí)這些例子在redis的官網(wǎng)都有介紹)。
代碼實(shí)現(xiàn)方式
老貓對(duì)redis鎖機(jī)制進(jìn)行了相關(guān)的抽取,并且封裝成了工具類,核心工具類代碼如下:
/**
* @author kdaddy@163.com
* @date 2021/1/7 22:36
* 公眾號(hào)“程序員老貓”
*/
@Service
public class RedisLockUtil {
@Autowired
private RedisTemplate redisTemplate;
private String value = UUID.randomUUID().toString();
public Boolean lock(String key){
RedisCallback redisCallback = redisConnection -> {
//表示set nx 存在key的話就不設(shè)置,不存在則設(shè)置
RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
//設(shè)置過(guò)期時(shí)間
Expiration expiration = Expiration.seconds(30);
byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
byte[] redisValue = redisTemplate.getKeySerializer().serialize(value);
Boolean result = redisConnection.set(redisKey,redisValue,expiration,setOption);
return result;
};
//獲取分布式鎖
Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
return lock;
}
//釋放分布式鎖
public Boolean releaseLock(String key){
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
RedisScript redisScript = RedisScript.of(script,Boolean.class);
List keys = Arrays.asList(key);
boolean result = (Boolean) redisTemplate.execute(redisScript,keys,value);
return result;
}
}
當(dāng)然相關(guān)的業(yè)務(wù)代碼,老貓還是使用了之前并發(fā)扣減庫(kù)存的例子,在此相關(guān)的代碼以及最終運(yùn)行的結(jié)果也不一一進(jìn)行舉例。小伙伴們可以自行去老貓的github獲取相關(guān)的示例源碼信息,然后運(yùn)行一下即可。github地址:https://github.com/maoba/kd-distribute。代碼已經(jīng)完成了更新。
Redisson分布式鎖
介紹和使用
那么Redisson究竟為何物呢?Redisson 是架設(shè)在Redis基礎(chǔ)上的一個(gè)Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)。 充分的利用了Redis鍵值數(shù)據(jù)庫(kù)提供的一系列優(yōu)勢(shì),基于Java實(shí)用工具包中常用接口,為使用者提供了一系列具有分布式特性的常用工具類。使得原本作為協(xié)調(diào)單機(jī)多線程并發(fā)程序的工具包獲得了協(xié)調(diào)分布式多機(jī)多線程并發(fā)系統(tǒng)的能力,大大降低了設(shè)計(jì)和研發(fā)大規(guī)模分布式系統(tǒng)的難度。同時(shí)結(jié)合各富特色的分布式服務(wù),更進(jìn)一步簡(jiǎn)化了分布式環(huán)境中程序相互之間的協(xié)作。 (摘自redisson官網(wǎng):https://redisson.org/)
下面我們來(lái)看一下具體用redisson實(shí)現(xiàn)分布式鎖實(shí)戰(zhàn),其實(shí)是相當(dāng)簡(jiǎn)單的,redisson已經(jīng)給我們進(jìn)行了相關(guān)的封裝,我們開箱即用。
/**
* @author kdaddy@163.com
* @date 2021/1/9 14:23
* @公眾號(hào)“程序員老貓”
*/
public Integer createOrder() throws Exception{
log.info("進(jìn)入了方法");
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("ktdaddy");
RedissonClient redissonClient = Redisson.create(config);
RLock rlock = redissonClient.getLock(ORDER_KEY);
rlock.lock(30, TimeUnit.SECONDS);
try {
log.info("拿到了鎖");
//....具體可以參考老貓的github
return order.getId();
}catch (Exception e){
e.printStackTrace();
}finally {
rlock.unlock();
}
return null;
}
原理
老貓上文中自己實(shí)現(xiàn)redis鎖的時(shí)候用到了lua腳本,redisson實(shí)現(xiàn)的時(shí)候其實(shí)所有的指令都是通過(guò)lua腳本去實(shí)現(xiàn)的。上述為redisson的簡(jiǎn)單架構(gòu)圖,畫的比較粗糙。老貓稍微作一下解釋。上圖中有個(gè)看門狗(watchdog)概念。其實(shí)這就是一個(gè)定時(shí)任務(wù),在線程獲取鎖之后,它會(huì)每隔10s幫忙將key的超時(shí)時(shí)間設(shè)置為30s,這樣就不會(huì)出現(xiàn)線程一直持有鎖從而影響其他線程獲取鎖的問題。小伙伴們可以發(fā)現(xiàn)該功能其實(shí)就是set px,只是換成了定時(shí)任務(wù)去實(shí)現(xiàn)。當(dāng)然看門狗的存在保證了出現(xiàn)死鎖的情況下會(huì)自動(dòng)釋放。
以上只是針對(duì)redisson做了一個(gè)簡(jiǎn)單的應(yīng)用介紹,redisson其實(shí)是相當(dāng)強(qiáng)大的,首先說(shuō)配置,老貓上述連接redis的方式其實(shí)很簡(jiǎn)單,由于搭建的是單機(jī)redis,所以就使用了單機(jī)redis的連接方式,當(dāng)然redisson還支持主從、哨兵、集群等等連接方式;當(dāng)然鎖的種類也相當(dāng)豐富,以上老貓?zhí)峁┑氖强芍厝腈i的流程。其實(shí)還包括公平鎖、聯(lián)鎖、紅鎖、讀寫鎖等等,另外的redisson對(duì)分布式的容器、隊(duì)列等等進(jìn)行了特有的封裝,包括分布式的Blocking Queue、分布式Map、分布式Set、分布式List等等。redisson的強(qiáng)大之處老貓?jiān)诖瞬灰灰幻杜e,有興趣的小伙伴可以深入研究一下。
缺陷
redis鎖可以比較完美地解決高并發(fā)的時(shí)候分布式系統(tǒng)的線程安全性的問題,但是這種鎖機(jī)制也并不是完美的。在哨兵模式下,客戶端對(duì)master節(jié)點(diǎn)加了鎖,此時(shí)會(huì)異步復(fù)制給slave節(jié)點(diǎn),此時(shí)如果master發(fā)生宕機(jī),主備切換,slave變成了master。因?yàn)橹笆钱惒綇?fù)制,所以此時(shí)正好又有個(gè)線程來(lái)嘗試加鎖的時(shí)候,就會(huì)導(dǎo)致多個(gè)客戶端對(duì)同一個(gè)分布式鎖完成了加鎖操作,這時(shí)候業(yè)務(wù)上會(huì)出現(xiàn)臟數(shù)據(jù)了。關(guān)于redis的相關(guān)知識(shí),大家可以訪問老貓之前的一些文章,包括redis的哨兵模式、持久化等等。
寫在最后
本篇主要和小伙伴們分享了redis鎖,從老貓自己實(shí)現(xiàn)的乞丐版的redis鎖到大牛實(shí)現(xiàn)的redisson。相信大家也會(huì)有一定的收貨。其實(shí)關(guān)于分布式鎖,出了redis鎖之外還有基于zookeeper的實(shí)現(xiàn)。后續(xù)老貓會(huì)整理并且分享給大家,敬請(qǐng)期待。
當(dāng)然更多技術(shù)干貨也歡迎大家搜索關(guān)注公眾號(hào)“程序員老貓”
總結(jié)
以上是生活随笔為你收集整理的分布式锁 哨兵模式_手撕redis分布式锁,隔壁张小帅都看懂了!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 分区 性能更差_用案例分析M
- 下一篇: php utf8 html字符,PHP: