基于Redis的分布式鎖及原子性問題(短視頻開發(fā))
短視頻開發(fā),基于Redis的分布式鎖及原子性問題
用 Redis 實現(xiàn)分布式鎖
主要應(yīng)用到的是 SETNX key value命令(如果不存在,則設(shè)置)
主要要實現(xiàn)兩個功能:
1、獲取鎖(設(shè)置一個 key)
2、釋放鎖 (刪除 key)
基本思想是執(zhí)行了 SETNX命令的線程獲得鎖,在完成操作后,需要刪除 key,釋放鎖。
加鎖:
@Override public boolean tryLock(long timeoutSec) { // 獲取線程標(biāo)示 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 獲取鎖 Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); }
釋放鎖:
@Override public void unlock() { // 獲取線程標(biāo)示 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 獲取鎖中的標(biāo)示 String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name); // 釋放鎖 stringRedisTemplate.delete(KEY_PREFIX + name); }
可是這里會存在一個隱患——假設(shè)該線程發(fā)生阻塞(或者其他問題),一直不釋放鎖(刪除 key)這可怎么辦?
為了解決這個問題,我們需要為 key 設(shè)計一個超時時間,讓它超時失效;但是這個超時時間的長短卻不好確定:
1、設(shè)置過短,會導(dǎo)致其他線程提前獲得鎖,引發(fā)線程安全問題。
2、設(shè)置過長,線程需要額外等待。
鎖的誤刪
超時時間是一個非常不好把握的東西,因為業(yè)務(wù)線程的阻塞時間是不可預(yù)估的,在極端情況下,它總能阻塞到 lock 超時失效,正如上圖中的線程1,鎖超時釋放了,導(dǎo)致線程2也進(jìn)來了,這時候 lock 是 線程2的鎖了(key 相同,value不同,value一般是線程唯一標(biāo)識);假設(shè)這時候,線程1突然不阻塞了,它要釋放鎖,如果按照剛剛的代碼邏輯的話,它會釋放掉線程2的鎖;線程2的鎖被釋放掉之后,又會導(dǎo)致其他線程進(jìn)來(線程3),如此往復(fù)。。。
為了解決這個問題,需要在釋放鎖時多加一個判斷,每個線程只釋放自己的鎖,不能釋放別人的鎖!
釋放鎖
@Override public void unlock() { // 獲取線程標(biāo)示 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 獲取鎖中的標(biāo)示 String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name); // 判斷標(biāo)示是否一致 if(threadId.equals(id)) { // 釋放鎖 stringRedisTemplate.delete(KEY_PREFIX + name); } }
原子性問題
剛剛我們談?wù)摰尼尫沛i的邏輯:
1、判斷當(dāng)前鎖是當(dāng)前線程的鎖
2、當(dāng)前線程釋放鎖
可以看到釋放鎖是分兩步完成的,如果你是對并發(fā)比較有感覺的話,應(yīng)該一下子就知道這里會存在問題了。
分步執(zhí)行,并發(fā)問題!
假設(shè) 線程1 已經(jīng)判斷當(dāng)前鎖是它的鎖了,正準(zhǔn)備釋放鎖,可偏偏這時候它阻塞了(可能是 FULL GC 引起的),鎖超時失效,線程2來加鎖,這時候鎖是線程2的了;可是如果線程1這時候醒過來,因為它已經(jīng)執(zhí)行了步驟1了的,所以這時候它會直接直接步驟2,釋放鎖(可是此時的鎖不是線程1的了)
其實這就是一個原子性的問題,剛剛釋放鎖的兩步應(yīng)該是原子的,不可分的!
要使得其滿足原子性,則需要在 Redis 中使用 Lua 腳本了。
引入 Lua 腳本保持原子性
lua 腳本:
-- 比較線程標(biāo)示與鎖中的標(biāo)示是否一致 if(redis.call('get', KEYS[1]) == ARGV[1]) then -- 釋放鎖 del key return redis.call('del', KEYS[1]) end return 0
Java 中調(diào)用執(zhí)行:
public class SimpleRedisLock implements ILock { ? private String name; private StringRedisTemplate stringRedisTemplate; ? public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) { this.name = name; this.stringRedisTemplate = stringRedisTemplate; } ? private static final String KEY_PREFIX = "lock:"; private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-"; private static final DefaultRedisScript<Long> UNLOCK_SCRIPT; static { UNLOCK_SCRIPT = new DefaultRedisScript<>(); UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua")); UNLOCK_SCRIPT.setResultType(Long.class); } ? @Override public boolean tryLock(long timeoutSec) { // 獲取線程標(biāo)示 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 獲取鎖 Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); } ? @Override public void unlock() { // 調(diào)用lua腳本 stringRedisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIX + name), ID_PREFIX + Thread.currentThread().getId()); } }
到了目前為止,我們設(shè)計的 Redis 分布式鎖已經(jīng)是生產(chǎn)可用的,相對完善的分布式鎖了。
以上就是短視頻開發(fā),基于Redis的分布式鎖及原子性問題, 更多內(nèi)容歡迎關(guān)注之后的文章
到此這篇關(guān)于短視頻開發(fā),基于Redis的分布式鎖及原子性問題的文章就介紹到這了,更多相關(guān)Redis的分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用Redis進(jìn)行數(shù)據(jù)緩存的項目實踐
在實際的業(yè)務(wù)場景中,Redis 一般和其他數(shù)據(jù)庫搭配使用,用來減輕后端數(shù)據(jù)庫的壓力,本文就介紹了利用Redis進(jìn)行數(shù)據(jù)緩存的項目實踐,具有一定的參考價值,感興趣的可以了解一下2022-06-06Redis的五種基本類型和業(yè)務(wù)場景和使用方式
Redis是一種高性能的鍵值存儲數(shù)據(jù)庫,支持多種數(shù)據(jù)結(jié)構(gòu)如字符串、列表、集合、哈希表和有序集合等,它提供豐富的API和持久化功能,適用于緩存、消息隊列、排行榜等多種場景,Redis能夠?qū)崿F(xiàn)高速讀寫操作,尤其適合需要快速響應(yīng)的應(yīng)用2024-10-10Redis集群節(jié)點(diǎn)通信過程/原理流程分析
這篇文章主要介紹了Redis集群節(jié)點(diǎn)通信過程/原理,詳細(xì)介紹了Cluster(集群)的節(jié)點(diǎn)通信的流程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03Redis數(shù)據(jù)導(dǎo)入導(dǎo)出以及數(shù)據(jù)遷移的4種方法詳解
這篇文章主要介紹了Redis數(shù)據(jù)導(dǎo)入導(dǎo)出以及數(shù)據(jù)遷移的4種方法詳解,需要的朋友可以參考下2020-02-02