JAVA實現(xiàn)redis分布式雙重加鎖的示例代碼
背景:在日常開發(fā)過程中,遇到了一個需求,比如有一個對象User(name,age、sex)有三個屬性,現(xiàn)在需要用戶新增接口中,防止此接口被多人同時請求訪問,產生了姓名&年齡相同的,還有年齡&性別相同的數據;
此問題的考慮思路
如果一個線程調用用戶新增接口的時候,在業(yè)務中通過查詢數據庫中是否已有相關數據,從而不拋出異常提示,不讓做保存到數據庫的操作;這種考慮是我們最常見的考慮內容。還有個問題,如果是外部系統(tǒng),涉及的操作并發(fā)量特別的大,那調用這個接口的并發(fā)量也很大的話,單純在通過校驗庫中是否有重復的數據防止重復數據插入只能阻止一部分問題數據的入庫。如果同時有兩個用戶甲乙,填寫的姓名和年齡
(年齡和性別是同樣的考慮方法);此時從庫中查了,沒有已有的數據,此時為了防止這兩個用戶甲乙操作的重復數據同時入庫的情況,我們就得加上一個分布式鎖了(如果在單體應用中可以使用synchronized),分布式架構中需要使用Redis分布式鎖或者Redission分布式鎖來實現(xiàn)相應的控制了;
在設計分布式Redis鎖以避免在新增User時出現(xiàn)同name和age組合,或者同age和sex組合的情況,你需要構建一個能夠唯一標識這些條件的key。由于Redis鎖通常用于確保操作的原子性,而你的需求是檢查并避免重復數據,這里實際上可能更偏向于使用Redis的其它數據結構(如集合、有序集合或哈希表)來輔助實現(xiàn),而不是僅僅使用單獨的key-value鎖
使用Redis的key-value鎖的基本思路
1.定義鎖的key:鎖的key應該能夠唯一標識你想要保護的資源或操作。在你的場景中,由于涉及到多個字段的組合檢查,你可以考慮將這些
字段組合成一個字符串作為key。例如:
對于name和age的組合,可以使用user??name:{name}:age:{age}。
對于age和sex的組合,可以使用user??age:{age}:sex:{sex}。
2.設置鎖:在嘗試新增User之前,先嘗試設置這個鎖。如果鎖設置成功(即沒有其他進程或線程持有這個鎖),則繼續(xù)執(zhí)行檢查邏輯。
3.檢查并插入:在鎖的保護下,檢查數據庫中是否已經存在具有相同name和age或age和sex組合的User。如果不存在,則執(zhí)行插入操作。
4.釋放鎖:無論操作成功還是失敗,最后都要釋放鎖,以便其他進程或線程可以獲取鎖并執(zhí)行操作。
結合Redis數據結構實現(xiàn)避免重復
然而,更有效的方法可能是使用Redis的集合(Set)或有序集合(Sorted Set)來存儲已經存在的組合,并檢查新組合是否已存在。
1.使用集合:
- 對于name和age的組合,可以創(chuàng)建一個集合user:name_age,其中每個元素都是{name}:{age}的字符串。
- 對于age和sex的組合,可以創(chuàng)建另一個集合user:age_sex,其中每個元素都是{age}:{sex}的字符串。
- 在新增User時,先檢查相應的集合中是否已經存在該組合。如果不存在,則添加到集合中,并執(zhí)行數據庫插入操作。
2.使用有序集合(如果需要按某種順序排序): - 類似于集合,但你可以為元素指定一個分數(score),以便按特定順序存儲和檢索元素。
注意事項
性能考慮:隨著集合中元素的增加,檢查操作可能會變慢。因此,你可能需要考慮使用哈希表或其他數據結構來優(yōu)化查找性能。
事務性:確保檢查集合和插入數據庫的操作是原子性的,以防止在檢查之后但在插入之前發(fā)生數據變化。
鎖的超時:設置鎖的超時時間以防止死鎖。
鎖的粒度:根據你的應用場景,你可能需要調整鎖的粒度。例如,如果操作非常頻繁,并且可以接受一定程度的重復檢查,則可以考慮放寬鎖的粒度或使用更輕量級的同步機制。
實現(xiàn)代碼
只避免 name和age的重復
下面的實現(xiàn)的一些代碼:希望能幫到大家理解思路。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 假設的鎖過期時間
private static final long LOCK_EXPIRATION_TIME = 10L; // 10秒
// 嘗試獲取鎖
private boolean tryLock(String key) {
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
// 嘗試設置鎖,如果鍵不存在則設置成功,并設置過期時間
return opsForValue.setIfAbsent(key, "locked", LOCK_EXPIRATION_TIME, TimeUnit.SECONDS);
}
// 釋放鎖
private void releaseLock(String key) {
redisTemplate.delete(key);
}
// 新增User的邏輯
public void addUserIfNotExists(User user) {
String nameAgeLockKey = "user:lock:name:" + user.getName() + ":age:" + user.getAge();
// 嘗試獲取鎖 ,獲取鎖成功才會繼續(xù)執(zhí)行下面業(yè)務上的校驗
if (tryLock(nameAgeLockKey)) {
try {
// 在這里執(zhí)行數據庫檢查(是否已存在同name和age)
// 如果不存在,則執(zhí)行插入操作
// 假設檢查通過,執(zhí)行插入操作(這里省略了具體的數據庫操作)
System.out.println("User added successfully");
} finally {
// 釋放鎖
releaseLock(nameAgeLockKey);
}
} else {
// 未能獲取鎖,可能是其他進程正在處理相同的組合
System.out.println("Failed to acquire lock(s), user addition may be in progress");
}
}
// ... 其他代碼 ...
}
避免 name和age的和age和sex重復:使用雙重的分布式鎖實現(xiàn):
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 假設的鎖過期時間
private static final long LOCK_EXPIRATION_TIME = 10L; // 10秒
// 嘗試獲取鎖
private boolean tryLock(String key) {
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
// 嘗試設置鎖,如果鍵不存在則設置成功,并設置過期時間
return opsForValue.setIfAbsent(key, "locked", LOCK_EXPIRATION_TIME, TimeUnit.SECONDS);
}
// 釋放鎖
private void releaseLock(String key) {
redisTemplate.delete(key);
}
// 新增User的邏輯
public void addUserIfNotExists(User user) {
//加兩次鎖
String nameAgeLockKey = "user:lock:name:" + user.getName() + ":age:" + user.getAge();
String ageSexLockKey = "user:lock:age:" + user.getAge() + ":sex:" + user.getSex();
// 嘗試獲取兩個鎖
if (tryLock(nameAgeLockKey) && tryLock(ageSexLockKey)) {
try {
// 在這里執(zhí)行數據庫檢查(是否已存在同name和age或同age和sex的User)
// 如果不存在,則執(zhí)行插入操作
// 假設檢查通過,執(zhí)行插入操作(這里省略了具體的數據庫操作)
System.out.println("User added successfully");
} finally {
// 釋放鎖 釋放兩次
releaseLock(nameAgeLockKey);
releaseLock(ageSexLockKey);
}
} else {
// 未能獲取鎖,可能是其他進程正在處理相同的組合
System.out.println("Failed to acquire lock(s), user addition may be in progress");
}
}
// ... 其他代碼 ...
}
雙重加鎖的 注意點上面的代碼示例簡化了錯誤處理和重試邏輯。在實際應用中,你可能需要處理各種異常情況,例如Redis服務器不可用、鎖被意外刪除或過期等。此外,如果業(yè)務邏輯復雜或執(zhí)行時間較長,你可能需要考慮使用更高級的鎖機制,如Redis的發(fā)布/訂閱模式、Lua腳本或Redis的RedLock算法來確保鎖的安全性和可靠性。
另外,請注意,tryLock 方法中的 setIfAbsent 操作是原子的,這意味著它會在單個Redis命令中完成檢查和設置操作,從而避免了競態(tài)條件。但是,由于網絡延遲、Redis服務器性能等因素,多個客戶端仍可能幾乎同時嘗試獲取相同的鎖。因此,即使使用了鎖,也需要謹慎地設計你的業(yè)務邏輯和錯誤處理策略。
到此這篇關于JAVA實現(xiàn)redis分布式雙重加鎖的示例代碼的文章就介紹到這了,更多相關JAVA redis分布式雙重加鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Boot Actuator監(jiān)控器配置及使用解析
這篇文章主要介紹了Spring Boot Actuator監(jiān)控器配置及使用解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-07-07
SpringBoot集成RocketMQ發(fā)送事務消息的原理解析
RocketMQ 的事務消息提供類似 X/Open XA 的分布事務功能,通過事務消息能達到分布式事務的最終一致,這篇文章主要介紹了SpringBoot集成RocketMQ發(fā)送事務消息,需要的朋友可以參考下2022-06-06
springboot+EHcache 實現(xiàn)文章瀏覽量的緩存和超時更新
這篇文章主要介紹了springboot+EHcache 實現(xiàn)文章瀏覽量的緩存和超時更新,問題描述和解決思路給大家介紹的非常詳細,需要的朋友可以參考下2017-04-04
深入學習JavaWeb中監(jiān)聽器(Listener)的使用方法
這篇文章主要為大家詳細介紹了深入學習JavaWeb中監(jiān)聽器(Listener)的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09
Springboot中@RequestParam和@PathVariable的用法與區(qū)別詳解
這篇文章主要介紹了Springboot中@RequestParam和@PathVariable的用法與區(qū)別詳解,RESTful API設計的最佳實踐是使用路徑參數來標識一個或多個特定資源,而使用查詢參數來對這些資源進行排序/過濾,需要的朋友可以參考下2024-01-01
spring boot整合scurity做簡單的登錄校驗的實現(xiàn)
這篇文章主要介紹了spring boot整合scurity做簡單的登錄校驗的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04

