SpringCloud在Redis中執行Lua腳本,避免併發所致id重複問題

問題背景

應公司產品要求,爲了方便觀察,編號需要由統一前綴+日期+連續增長序列構成。

方案

採用redis作爲中間件從而產生唯一增長序列後綴。

僞代碼大概如下:(線程不安全)

if(exists(key)) {incr(key);
} else {
	// 設置初始值爲1000,並設置過期時間爲凌晨0點加隨機整數
	// 增加隨機數是爲了避免大量key同時過期,導致redis短暫不可用問題
③	SETEX key 過期時間 1000
}

發現問題

  1. 項目爲內部系統,平時使用並不集中,因此一直沒有出現過問題
  2. 由於每天凌晨需要定時獲取昨日的司機運費數據,統一彙總並存儲,項目在測試環境觀察數據時發現產生了多個一模一樣的1000這個id號

分析問題

  1. 線程到達①處代碼時,向redis詢問有無key,得到的結果爲false,同時將此結果存儲在本地內存
  2. 當執行③處代碼時,線程被切換了,此線程休眠
  3. 第二個線程執行了③處代碼,向redis設置了初始值,並獲取到了編號1000
  4. 第一個線程被調度執行的時候又重複執行了③處代碼,再次向redis設置了初始值,並獲取到了編號1000,這時就出現了id重複問題了

解決方案

  1. 鎖—簡單粗暴,由於是SpringCloud項目,所以只能使用分佈式鎖。然而代價就是原本此處只有在每天初次使用時才需要加鎖,其它時間使用redis的incr命令已經可以保證原子性了,如果採用分佈式鎖的方案就會做n-1次無用功,代價太大了
  2. 使用Lua腳本,在redis中執行具有天然的原子性特性,推薦

在SpringBoot中實現

  1. Lua腳本
 if redis.call('EXISTS', KEYS[1]) == 1   
 then   
     return redis.call('INCR', KEYS[1])   
 else   
     redis.call('SETEX', KEYS[1], ARGV[1], ARGV[2])   
     return tonumber(ARGV[2])   
 end 
  1. 代碼調用
stringRedisTemplate.execute(new DefaultRedisScript<Long>(SCRIPT, Long.class), keys, args);

注意事項

returnType 參數不能使用Integer.class ,需要使用Long.class。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章