【redis】分散式鎖
一、前言
最近專案中,基本功能實現了,準備都訪問多的介面加快取。當然快取就想到了redis。正好自己也查了查redis帶來的一些問題:快取穿透、快取併發、熱點快取等。
也想到自己負責的模組涉及到資金,同一時間只能有一個人操作,想象一下,同一時間2個使用者同時還款放款,一個人賬戶增加一個減少,為了方式同時操作資料不一致,需要鎖。如果是單體服務,可以直接利用資料庫的行鎖或者表鎖。如果是微服務叢集,多個客戶端同時修改一個共享資料就需要分散式鎖了。
二、使用redis實現分散式鎖
場景:秒殺
現在有100個商品,進行秒殺,因為商品價格便宜,所以就會有很多人來搶。首先我們要保證的就是:
1 商品最後不能成負數
2 防止超出資料庫連線池數目
3 保證在分散式部署的應用叢集中,同一個方法在同一時間只能被一臺機器上的一個執行緒執行。
業務核心:
每個使用者訪問的時候,先要獲取鎖,如果獲取到了,就可以購買商品,如果獲取不到就再次訪問。
/** * 分散式鎖嘗試 * 每個使用者訪問的時候,先要獲取鎖,如果獲取到了,就可以購買商品,如果獲取不到就再次訪問 * * * @param feeCode * @throws Exception */ public void stock(String feeCode) throws Exception { //加鎖 long time = System.currentTimeMillis()+50000; if (!redisService.lock(feeCode, String.valueOf(time))){ throw new Exception("沒有獲取到分散式鎖呀!!"); } //1.查詢商品庫存。為0則買完了 if (count<1){ throw new Exception("商品搶購結束"); }else { //下單 count--; Thread.sleep(1000); log.info(String.valueOf(count)); } //解鎖 redisService.unlock(feeCode, String.valueOf(time)); }
分散式鎖,加鎖:
這裡要注意的是,使用redis的SetNx來實現加鎖。SetNx是如果key不存在就儲存到redis中,並返回1,否則如果key存在,就什麼也不做,返回0。
這裡呢,因為很多人搶一種產品,所以key可以為productTypeId ,value為 當前時間+超時時間。
針對這個超時時間呢?大家在平常的時候很常見,就是比如在某寶、某東、12306等買東西的時候,當新增購物車後,前去結賬,結賬的時候,都會有一個請在30分鐘內付款。否則就自動放棄。我們的這個超時時間就是類似付款時候的30分鐘。這個是要根據我們具體的業務具體設定的。
如果鎖過期,獲取上一個鎖的時間,如果鎖過期,也就是儲存的鎖的值小於當前時間,那麼使用GetSet方法把值替換掉。
/**
* 分散式鎖,加鎖
* @param key
* @param value 當前時間+超時時間
* @return
*/
public boolean lock(String key, String value) {
//如果key存在,就返回,否則就儲存
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
String currentValue = (String) redisTemplate.opsForValue().get(key);
//如果鎖過期
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//獲取上一個鎖的時間
String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue)&&oldValue.equals(currentValue) ){
return true;
}
}
return false;
}
分散式鎖,解鎖:
我們先來根據
/**
* 分散式鎖,解鎖
* @param key
* @param value
*/
public void unlock(String key,String value){
try{
String currentValue = (String) redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)&¤tValue.equals(value)){
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e){
log.error("[redis分散式鎖]解鎖異常,{}",e);
}
}
三、小結
保證在分散式部署的應用叢集中,同一個方法在同一時間只能被一臺機器上的一個執行緒執行。分散式鎖就可以實現這個功能。用到了redis的兩個命令,SetNx和GetSet。瞭解一下。