需求背景
在開發的收入結轉平臺界面上有一個歸集按鈕,可以實作抓取結轉表里面的多條資料進行歸集操作,為了防止多人多電腦同時操作一條資料,我們自己開發了一個簡單的基于Redis實作的分布式鎖,
代碼實作
邏輯代碼中的使用案例
引數說明:
scIds:結轉資料的ID主鍵集合,
timeOutToDeleteRedisKey:最大鎖超時時間(用于自動解鎖)
organizationId:租戶ID(這個引數根據情況選擇是否需要)
ReturnLock returnLock = RedisLock.applyByIds(scIds, redisLockTime, organizationId, () -> {
// dothing 具體的代碼業務邏輯
return 123;
});
if (!returnLock.getFlag()) {
if(returnLock.getLock()){
throw new CommonException("歸集資料有程式正在運行,請稍候重繪頁面重試");
}else {
throw new CommonException(returnLock.getErrorMsg());
}
}
// 回傳物件
System.out.println(returnLock.getResObj()) // 123
Redis加鎖方法封裝
public static ReturnLock applyByIds(List<Long> scIds, Long timeOutToDeleteRedisKey, Long tenantId,
Supplier<Object> supplier) {
// 獲得鎖
Map<String, String> keyMap = getLockByIds(scIds, timeOutToDeleteRedisKey, tenantId);
ReturnLock returnLock = new ReturnLock();
returnLock.setFlag(true);
returnLock.setLock(false);
try {
// 判斷主鍵ID數量和加鎖的數量是否一致,不一致說明有加鎖失敗的資料,回傳失敗鎖資訊
if(scIds.size() > keyMap.size()){
returnLock.setFlag(false);
returnLock.setLock(true);
returnLock.setKeyMap(keyMap);
return returnLock;
}
// 應用代碼執行后的回傳結果 supplier:java8四大內置函式的供給型介面
// Supplier<T>(供給型介面)無引數,回傳型別為T的物件:T get()
returnLock.setResObj(supplier.get());
}catch (Exception e) {
returnLock.setFlag(false);
returnLock.setErrorMsg(e.getMessage());
e.printStackTrace();
}finally {
// 應用代碼執行報錯,解鎖
unLockByIds(keyMap);
}
return returnLock;
}
getLockByIds
批量獲取每一條資料的Redis鎖
private static Map<String, String> getLockByIds(List<Long> scIds, Long timeOutToDeleteRedisKey, Long tenantId) {
Map<String, String> keyMap = new HashMap<>();
// 從spring背景關系中獲取Redis的操作物件,因為這個代碼是寫在util中,所以通過背景關系方式獲取bean物件
// RedisHelper:Redis的操作物件,我們自己公司基于redisTemplate封裝的
RedisHelper redisHelper = SpringUtil.getBean(RedisHelper.class);
try {
if (CollectionUtil.isNotEmpty(scIds)) {
for (int i = 0; i < scIds.size(); i++) {
String item = "L_" + scIds.get(i);
String UUID = UUIDUtils.generateTenantUUID(tenantId);
// 判斷key值是否被鎖,如果沒有鎖則加鎖并設定過期時間, 如果有鎖, 則報錯并立即釋放已加的鎖
// strSetIfAbsent會回傳true/false,底層是封裝了java的setIfAbsent方法和Redis的setnx方法
// 如果設定成功回傳true否則false
if (redisHelper.strSetIfAbsent(item, UUID)) {
// 設定過期時間
redisHelper.setExpire(item, timeOutToDeleteRedisKey, TimeUnit.SECONDS);
// 保存設定的鎖資訊
keyMap.put(item, UUID);
} else {
// 鎖設定例外,表示有資料被鎖住了,解鎖之間加鎖的資料
if (MapUtil.isNotEmpty(keyMap)) {
if (unLockByIds(keyMap)) {
// 解鎖失敗再次解鎖
unLockByIds(keyMap);
}
}
// 回傳鎖資訊
return keyMap;
}
}
}
return keyMap;
}catch (Exception e) {
e.printStackTrace();
return keyMap;
}
}
解鎖
private static Boolean unLockByIds(Map<String, String> keyMap) {
RedisHelper redisHelper = SpringUtil.getBean(RedisHelper.class);
try {
if (MapUtil.isEmpty(keyMap)) {
return false;
}
// 判斷是否是自己的鎖
for (String key : keyMap.keySet()) {
String uuid = keyMap.get(key);
if (StringUtils.equals(uuid, redisHelper.strGet(key))) {
// 封裝了redisTemplate.delete(key);
redisHelper.delKey(key);
}
}
return true;
}catch (Exception e) {
e.printStackTrace();
return false;
}
}
實作效果
當對資料進行批量加鎖的時候,若在加鎖的程序中出現加鎖失敗,則回滾直接加的鎖,并提示"歸集資料有程式正在運行,請稍候重繪頁面重試",
若業務代碼邏輯執行成功或者執行報錯都會自動的解鎖當前加鎖的資料,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/449054.html
標籤:其他
