Redis分布式鎖與Redlock算法實(shí)現(xiàn)
一、簡(jiǎn)介
1. Redis的分布式鎖
Redis是一款基于內(nèi)存的高性能鍵值對(duì)數(shù)據(jù)庫(kù),通過(guò)提供多種數(shù)據(jù)類(lèi)型支持,滿(mǎn)足了大部分的應(yīng)用場(chǎng)景,常用的數(shù)據(jù)類(lèi)型有字符串、哈希表、列表、集合和有序集合等。在Redis中,可以使用多種方式實(shí)現(xiàn)分布式鎖,如使用SETNX命令或RedLock算法。
2. 分布式鎖的實(shí)現(xiàn)原理
分布式鎖的實(shí)現(xiàn)主要依靠分布式協(xié)調(diào)服務(wù),如Zookeeper、Etcd和Consul等,實(shí)現(xiàn)多個(gè)進(jìn)程之間通過(guò)共享資源進(jìn)行資源訪問(wèn)的協(xié)同工作。
二、Redis 分布式鎖使用場(chǎng)景
1. 分布式系統(tǒng)中數(shù)據(jù)資源的互斥訪問(wèn)
當(dāng)多個(gè)進(jìn)程需要同時(shí)訪問(wèn)共享資源時(shí),需要通過(guò)加鎖機(jī)制保證在同一時(shí)間只有一個(gè)進(jìn)程能夠訪問(wèn)資源,從而避免了競(jìng)態(tài)條件。
2. 分布式環(huán)境中多個(gè)節(jié)點(diǎn)之間的協(xié)作
在分布式環(huán)境中,不同的節(jié)點(diǎn)可能需要進(jìn)行協(xié)調(diào)工作,如分配任務(wù)、執(zhí)行任務(wù)等,通過(guò)加鎖機(jī)制保證每個(gè)節(jié)點(diǎn)領(lǐng)取任務(wù)后都能夠成功執(zhí)行任務(wù)。
3. 常見(jiàn)場(chǎng)景及應(yīng)用
訂單系統(tǒng)、秒殺系統(tǒng)、分布式任務(wù)調(diào)度等。
以下是一個(gè)使用Java語(yǔ)言實(shí)現(xiàn)的Redis分布式鎖示例:
import redis.clients.jedis.Jedis;
public class RedisDistributedLock {
// Redis客戶(hù)端
private Jedis jedis;
// 鎖的路徑
private String lockKey;
// 鎖的持有者
private String lockHolder;
// 鎖的過(guò)期時(shí)間(單位:毫秒)
private int expireTime;
// 循環(huán)獲取鎖的時(shí)間間隔(單位:毫秒)
private int acquireInterval;
// 獲取鎖的最大等待時(shí)間(單位:毫秒)
private int acquireTimeout;
/**
* 構(gòu)造函數(shù)
* @param jedis Redis客戶(hù)端
* @param lockKey 鎖的路徑
* @param expireTime 鎖的過(guò)期時(shí)間(單位:毫秒)
* @param acquireInterval 循環(huán)獲取鎖的時(shí)間間隔(單位:毫秒)
* @param acquireTimeout 獲取鎖的最大等待時(shí)間(單位:毫秒)
*/
public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime, int acquireInterval, int acquireTimeout) {
this.jedis = jedis;
this.lockKey = lockKey;
this.expireTime = expireTime;
this.acquireInterval = acquireInterval;
this.acquireTimeout = acquireTimeout;
this.lockHolder = null;
}
/**
* 獲取鎖
* @return 是否獲取成功
*/
public boolean acquire() {
// 獲取當(dāng)前時(shí)間戳
long now = System.currentTimeMillis();
// 計(jì)算獲取鎖的最后截止時(shí)間
long acquireDeadline = now + acquireTimeout;
// 循環(huán)嘗試獲取鎖
while (System.currentTimeMillis() < acquireDeadline) {
// 生成隨機(jī)的鎖持有者ID
String holder = Long.toString(now) + "|" + Thread.currentThread().getId();
// 將鎖持有者ID設(shè)置到鎖的值中,如果設(shè)置成功則表示獲取鎖成功
if (jedis.set(lockKey, holder, "NX", "PX", expireTime) != null) {
this.lockHolder = holder;
return true;
}
// 如果獲取鎖失敗,則等待一段時(shí)間后再次嘗試獲取
try {
Thread.sleep(acquireInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 釋放鎖
* @return 是否釋放成功
*/
public boolean release() {
// 判斷當(dāng)前鎖是否是該線程持有的,如果不是則不能釋放
if (this.lockHolder != null && this.lockHolder.equals(jedis.get(lockKey))) {
jedis.del(lockKey);
return true;
}
return false;
}
}
三、Redlock算法的原理與實(shí)現(xiàn)
1. Redlock算法的背景
在分布式系統(tǒng)中經(jīng)常要用到分布式鎖,以保證某些操作的原子性,同時(shí)避免多個(gè)節(jié)點(diǎn)同時(shí)操作同一個(gè)資源。然而傳統(tǒng)的分布式鎖存在多種問(wèn)題,例如死鎖、宕機(jī)等,激發(fā)了人們尋求更加安全可靠的分布式鎖算法。
2. Redlock算法的原理
Redlock是一個(gè)由Redis的創(chuàng)始人開(kāi)發(fā)的分布式鎖算法,其思想基于Paxos算法。Redlock算法的流程如下:
- 客戶(hù)端獲取當(dāng)前時(shí)間戳t1。
- 客戶(hù)端依次向N個(gè)Redis節(jié)點(diǎn)請(qǐng)求鎖,每個(gè)請(qǐng)求的鎖過(guò)期時(shí)間為t1+TTL(time to live)。
- 如果客戶(hù)端在大多數(shù)節(jié)點(diǎn)上都獲得了鎖,則客戶(hù)端獲得了鎖。
- 如果客戶(hù)端在少數(shù)節(jié)點(diǎn)上未能獲得鎖,則客戶(hù)端將在所有已獲得鎖的節(jié)點(diǎn)上釋放已經(jīng)獲得的鎖。
- 如果客戶(hù)端在所有節(jié)點(diǎn)上都未能獲得鎖,則重復(fù)步驟1。
其中N為Redis節(jié)點(diǎn)數(shù)量,TTL指過(guò)期時(shí)間。
3. Redlock算法的缺陷
Redlock算法并不完美,存在以下缺陷:
- 時(shí)間同步的問(wèn)題:如果Redis節(jié)點(diǎn)系統(tǒng)時(shí)間發(fā)生偏移,可能會(huì)導(dǎo)致鎖競(jìng)爭(zhēng)的嚴(yán)重性問(wèn)題。
- 網(wǎng)絡(luò)分區(qū)問(wèn)題:如果出現(xiàn)了網(wǎng)絡(luò)分區(qū)情況,則可能導(dǎo)致多個(gè)客戶(hù)端同時(shí)獲取了鎖,而無(wú)法做到原子性。
四、Redis Redlock算法的應(yīng)用
1. 實(shí)現(xiàn)分布式鎖
在分布式系統(tǒng)中,實(shí)現(xiàn)分布式鎖是一項(xiàng)非常關(guān)鍵的任務(wù)。基于Redlock算法可以很容易地實(shí)現(xiàn)分布式鎖。下面是java代碼實(shí)現(xiàn)過(guò)程:
public class RedisDistributedLock {
private static final long DEFAULT_EXPIRY_TIME = 30000;
private static final int DEFAULT_RETRIES = 3;
private static final long DEFAULT_RETRY_TIME = 500;
private final JedisPool jedisPool;
public RedisDistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 獲取分布式鎖
* @param lockKey 鎖key
* @param clientId 客戶(hù)端標(biāo)識(shí)
* @return 是否獲取到鎖
*/
public boolean acquire(String lockKey, String clientId) {
return acquire(lockKey, clientId, DEFAULT_EXPIRY_TIME, DEFAULT_RETRIES, DEFAULT_RETRY_TIME);
}
/**
* 獲取分布式鎖
* @param lockKey 鎖key
* @param clientId 客戶(hù)端標(biāo)識(shí)
* @param expiryTime 鎖超時(shí)時(shí)間,單位毫秒
* @param retryTimes 嘗試獲取鎖的次數(shù)
* @param retryInterval 每次嘗試獲取鎖的間隔時(shí)間,單位毫秒
* @return 是否獲取到鎖
*/
public boolean acquire(String lockKey, String clientId, long expiryTime, int retryTimes, long retryInterval) {
try (Jedis jedis = jedisPool.getResource()) {
int count = 0;
while (count++ < retryTimes) {
// 生成隨機(jī)字符串作為value,保證每個(gè)客戶(hù)端的鎖值是唯一的
String lockValue = UUID.randomUUID().toString();
// 嘗試獲取鎖,成功返回1,失敗返回0
String result = jedis.set(lockKey, lockValue, "NX", "PX", expiryTime);
if ("OK".equals(result)) {
// 將鎖標(biāo)識(shí)與客戶(hù)端匹配,便于解鎖時(shí)判斷鎖是否屬于當(dāng)前客戶(hù)端
jedis.hset("lockClientIdMap", lockKey, clientId);
return true;
}
try {
Thread.sleep(retryInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
return false;
}
/**
* 釋放分布式鎖
* @param lockKey 鎖key
* @param clientId 客戶(hù)端標(biāo)識(shí)
* @return 是否成功釋放鎖
*/
public boolean release(String lockKey, String clientId) {
try (Jedis jedis = jedisPool.getResource()) {
// 獲取鎖標(biāo)識(shí)對(duì)應(yīng)的客戶(hù)端標(biāo)識(shí),判斷鎖是否屬于當(dāng)前客戶(hù)端
String storedClientId = jedis.hget("lockClientIdMap", lockKey);
if (clientId.equals(storedClientId)) {
// 刪除鎖key
jedis.del(lockKey);
// 刪除鎖標(biāo)識(shí)對(duì)應(yīng)的客戶(hù)端標(biāo)識(shí)
jedis.hdel("lockClientIdMap", lockKey);
return true;
}
}
return false;
}
}
2. 保證鎖的可重入性
為了保證鎖的可重入性,可以在Redis中存儲(chǔ)一個(gè)計(jì)數(shù)器,用于記錄當(dāng)前客戶(hù)端已獲取鎖的次數(shù)。在釋放鎖時(shí),判斷計(jì)數(shù)器是否為0,如果不為0,則表示鎖仍是當(dāng)前客戶(hù)端持有的。
3. 避免死鎖
為了避免死鎖,需要嚴(yán)格控制鎖超時(shí)時(shí)間和嘗試獲取鎖的次數(shù)。在獲取鎖失敗后,需要等待一段時(shí)間再?lài)L試獲取,避免出現(xiàn)大量客戶(hù)端同時(shí)請(qǐng)求獲取鎖的情況。
五、Redlock算法的優(yōu)化措施
1. 客戶(hù)端標(biāo)識(shí)
在分布式鎖的實(shí)現(xiàn)中,加入客戶(hù)端標(biāo)識(shí)可以避免一個(gè)客戶(hù)端誤解鎖其他客戶(hù)端持有的鎖。
2. 指定多個(gè)Redis節(jié)點(diǎn)
為了提高系統(tǒng)的可用性,可以指定多個(gè)Redis節(jié)點(diǎn),當(dāng)一個(gè)Redis節(jié)點(diǎn)出現(xiàn)故障時(shí),系統(tǒng)可以切換到其他可用的節(jié)點(diǎn)繼續(xù)工作。
3. 加入時(shí)鐘偏移量
為了避免時(shí)鐘不同步導(dǎo)致的鎖失效問(wèn)題,可以加入時(shí)鐘偏移量,即在獲取鎖時(shí)獲取多個(gè)Redis節(jié)點(diǎn)的時(shí)間,并取其最小值作為鎖的過(guò)期時(shí)間。這樣可以保證所有節(jié)點(diǎn)使用的是同一個(gè)時(shí)間作為鎖的過(guò)期時(shí)間,從而避免時(shí)鐘不同步導(dǎo)致的問(wèn)題。
到此這篇關(guān)于Redis分布式鎖與Redlock算法實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Redis分布式鎖與Redlock 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis 緩存實(shí)現(xiàn)存儲(chǔ)和讀取歷史搜索關(guān)鍵字的操作方法
這篇文章主要介紹了Redis 緩存實(shí)現(xiàn)存儲(chǔ)和讀取歷史搜索關(guān)鍵字,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Redis的數(shù)據(jù)類(lèi)型和內(nèi)部編碼詳解
Redis是通過(guò)Key-Value的形式來(lái)組織數(shù)據(jù)的,而Key的類(lèi)型都是String,而Value的類(lèi)型可以有很多,在Redis中最通用的數(shù)據(jù)類(lèi)型大致有這幾種:String、List、Set、Hash、Sorted Set,下面通過(guò)本文介紹Redis數(shù)據(jù)類(lèi)型和內(nèi)部編碼,感興趣的朋友一起看看吧2024-04-04
Redis如何使用zset處理排行榜和計(jì)數(shù)問(wèn)題
Redis的ZSET數(shù)據(jù)結(jié)構(gòu)非常適合處理排行榜和計(jì)數(shù)問(wèn)題,它可以在高并發(fā)的點(diǎn)贊業(yè)務(wù)中高效地管理點(diǎn)贊的排名,并且由于ZSET的排序特性,可以輕松實(shí)現(xiàn)根據(jù)點(diǎn)贊數(shù)實(shí)時(shí)排序的功能2025-02-02
Python的Flask框架使用Redis做數(shù)據(jù)緩存的配置方法
Redis數(shù)據(jù)庫(kù)依賴(lài)于主存,在關(guān)系型數(shù)據(jù)庫(kù)以外再配套R(shí)edis管理緩存數(shù)據(jù)將對(duì)性能會(huì)有很大的提升,這里我們就來(lái)看一下Python的Flask框架使用Redis做數(shù)據(jù)緩存的配置方法2016-06-06
Redis?中ZSET數(shù)據(jù)類(lèi)型命令使用及對(duì)應(yīng)場(chǎng)景總結(jié)(案例詳解)
這篇文章主要介紹了Redis?中ZSET數(shù)據(jù)類(lèi)型命令使用及對(duì)應(yīng)場(chǎng)景總結(jié),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01
Redisson分布式限流器RRateLimiter的使用及原理小結(jié)
本文主要介紹了Redisson分布式限流器RRateLimiter的使用及原理小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
Redis 使用跳表實(shí)現(xiàn)有序集合的方法
Redis有序集合底層為什么使用跳表而非其他數(shù)據(jù)結(jié)構(gòu)如平衡樹(shù)、紅黑樹(shù)或B+樹(shù)的原因在于其特殊的設(shè)計(jì)和應(yīng)用場(chǎng)景,跳表提供了與平衡樹(shù)類(lèi)似的效率,同時(shí)實(shí)現(xiàn)更簡(jiǎn)單,調(diào)試和修改也更加容易,感興趣的朋友一起看看吧2024-09-09

