在springcloud項目開發中redis分佈式鎖使用主要有兩個場景
需要JAVA Spring Cloud大型企業分佈式微服務雲構建的B2B2C電子商務平臺源碼請加企鵝求求 :二一四七七七五六三三
1.訂單重複提交或支付提交等,防止刷單
2.對某個業務進行鎖定,例如:當用戶同一時間,進行對賬戶充值和提現操作,那麼這裏需要根據用戶ID對賬戶進行鎖定,只有一個完成了纔可以進行第二個。
開發實現方式
1.pom.xml中引入jar包,最好引入到基礎模塊中,其他模塊通用
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
創建redis操作類RedisGlobalLock(自定義)
redis提供RedisTemplate方法
redis提供三個方法:
(1)lock 獲取鎖並鎖定 本方法是立即獲取鎖狀態,如果獲取成功並鎖定,如果獲取失敗
(2)tryLock 嘗試獲取鎖並鎖定 本方式是在指定時間嘗試獲取鎖
(3)unlock 釋放鎖 當業務處理完畢必須釋放鎖
重點:
lock和tryLock區別:lock是實時獲取,tryLock是嘗試在一段時間內一直在獲取
@Service
public class RedisGlobalLock {
private static Log log = LogFactory.getLog(RedisGlobalLock.class);
private static final String TYPE_NAME = RedisGlobalLock.class.getTypeName();
/** 默認30ms嘗試一次 */
private final static long LOCK_TRY_INTERVAL = 30L;
/** 默認嘗試20s */
private final static long LOCK_TRY_TIMEOUT = 20 * 1000L;
/** 單個業務持有鎖的時間30s,防止死鎖 */
private final static long LOCK_EXPIRE = 30 * 1000L;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 獲取鎖
* @param key 鎖Key
* @return 是否獲取鎖
*/
public boolean lock(String key) {
return getLock(key, 0, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
}
/**
* 獲取鎖
* @param key 鎖Key
* @param expire 有效期
* @param expireUnit 有效期時間單位
* @return 是否獲取鎖
*/
public boolean lock(String key, long expire, TimeUnit expireUnit) {
return getLock(key, 0, expire, expireUnit);
}
/**
* 嘗試獲取鎖
* @param key 鎖Key
* @return 是否獲取鎖
*/
public boolean tryLock(String key) {
return tryLock(key, LOCK_TRY_TIMEOUT, TimeUnit.MILLISECONDS);
}
/**
* 嘗試獲取鎖
* @param key 鎖Key
* @param timeout 等待超時時間
* @param unit 等待超時時間單位
* @return 是否獲取鎖
*/
public boolean tryLock(String key, long timeout, TimeUnit unit) {
// 超時時間轉成毫秒
timeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
return getLock(key,timeout, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
}
/**
* 嘗試獲取鎖
* @param key 鎖Key
* @param timeout 等待超時時間
* @param timeoutUnit 等待超時時間單位
* @param expire 有效期
* @param expireUnit 有效期時間單位
* @return
*/
public boolean tryLock(String key, long timeout, TimeUnit timeoutUnit, long expire, TimeUnit expireUnit) {
// 超時時間轉成毫秒
timeout = TimeUnit.MILLISECONDS.convert(timeout, timeoutUnit);
return getLock(key,timeout, expire, expireUnit);
}
/**
* 釋放鎖
* @param key 鎖Key
*/
public void unlock(String key) {
key = getPrefix(TYPE_NAME) + key;
Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
if(null != oldExpireTime && oldExpireTime >= System.currentTimeMillis()) {
// 大於過期時間,則刪除key
redisTemplate.delete(key);
}
}
/**
* 獲取鎖
* @param key 鎖鍵值
* @param timeout 超時時間
* @param time 全局鎖生命週期
* @param unit 時間單位
* @return 是否獲取到鎖
*/
private boolean getLock(String key, long timeout, long time, TimeUnit unit) {
key = getPrefix(TYPE_NAME) + key;
try {
long startTimeMillis = System.currentTimeMillis();
do {
long newValue = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
Boolean isOk = redisTemplate.opsForValue().setIfAbsent(key, newValue);
if(isOk) {
// 獲得鎖
redisTemplate.expire(key, time, unit);
return true;
}
// 獲取過期時間
Long oldExpireTime = (Long) redisTemplate.opsForValue().get(key);
if(null == oldExpireTime) {
oldExpireTime = 0L;
}
if(oldExpireTime >= System.currentTimeMillis()) {
// 不小於系統時間並且過了超時時間,則不獲取鎖
if((System.currentTimeMillis() - startTimeMillis) > timeout) {
return false;
}
// 休眠
Thread.sleep(LOCK_TRY_INTERVAL);
}
// 新的過期時間
long newExpireTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit);
Long currentExpireTime = (Long) redisTemplate.opsForValue().getAndSet(key, newExpireTime);
if(null == currentExpireTime) {
currentExpireTime = 0L;
}
if(currentExpireTime.equals(oldExpireTime)) {
// 獲取到鎖
redisTemplate.expire(key, time, unit);
return true;
}
} while (true);
} catch (Exception e) {
return false;
}
}
/**
* 獲取緩存標識前綴
* @param typeName 類名
* @return 前綴
*/
protected final String getPrefix(String typeName) {
return typeName;
}
}
在業務邏輯層引入redis操作類
@Resource
private RedisGlobalLock redisGlobalLock;
// 1、獲取分佈式鎖防止重複調用 =====================================================
String key = PayDistributePrefix.PAY_MEMBER_ACCOUNT + memberId;
if(redisGlobalLock.lock(key)) {
try{
System.out.println("--處理業務---");
}catch (Exception e){
throw e;
}finally {
// 4、釋放分佈式鎖 ================================================================
redisGlobalLock.unlock(key);
}
}else{
// 如果沒有獲取鎖
Ensure.that(true).isTrue("17000706");
}
所有鎖業務必須釋放鎖,防止死鎖