boolean before(){
lock(){//併發下單加分佈式鎖
//判斷是否有下單資格 是否超過每日下單數
if(true){
return true;
}else{
return false;
}
}
};
validate(){};//參數校驗之類的
after(){
redis.inrc();//統計每日下單數
}
//業務主線
if(before()){
validate();
after();
}
/**
* 獲取分佈式鎖
* @param jedis
* @param lockKey 分佈式鎖Key
* @param expireTime 分佈式鎖過期時間
* @param timeoutTime 獲取分佈式鎖最大時間
* @return 分佈式鎖持有者Key,未獲取到鎖爲null
*/
public static String getDistributedLock(Jedis jedis, String lockKey, int expireTime,int timeoutTime) {
boolean result = false;
// 隨機生成一個value
String requestId = UUID.randomUUID().toString();
// 獲取鎖的超時時間,超過這個時間則放棄獲取鎖
long end = System.currentTimeMillis() + timeoutTime;
while (!result && System.currentTimeMillis() < end) {
result = tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return result ? requestId : null;
}
/**
* 嘗試獲取分佈式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @param expireTime 超期時間
* @return 是否獲取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 釋放分佈式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @return 是否釋放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
returnResource(jedis);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 釋放jedis資源
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
對於分佈式鎖,我們應該秉持 誰用誰釋放的原則,不可以等到鎖過期自動釋放。
以上僞代碼在before方法添加分佈式鎖後,什麼時候釋放呢?
按照正常的業務邏輯,應該是在before加鎖,等到after執行完後釋放,但是此時可能會釋放掉其他的鎖,造成業務混亂。如果等鎖自動過期,併發時必定會消耗大量的系統資源,造成不可預知的問題。
如果在before裏面在方法執行完後釋放鎖,在前一個請求執行validate() after()邏輯時,下一個訂單可能早就進before來了,就失去了限量的目的。
最後怎麼解決的呢?
- getDistributedLock(" “,” ",2*1000,60*1000)正常的業務邏輯一般2s內肯定可以執行完。
- 在before try catch finally 調用releaseDistributedLock釋放鎖時 使用線程延遲1s釋放,給業務足夠執行的時間。
String requestId = RedisUtil.getDistributedLock(jedis, lockKey, 2*1000, 60*1000);
if (null != requestId) {
try {
...
} catch (Exception e) {
log.error("系統下單限量管控error:"+e);
} finally {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
releaseDistributedLock(jedis, lockKey, requestId);
}
}
}).start();
}
}