淺析redis setIfAbsent的用法及在分布式鎖上的應用及同步鎖的缺陷


一、業務場景:同步鎖的問題與分布式鎖的應用

1、redis的基本命令

(1)SETNX命令(SET if Not eXists)

  語法:SETNX key value

  功能:當且僅當 key 不存在,將 key 的值設為 value ,並返回1;若給定的 key 已經存在,則 SETNX 不做任何動作,並返回0。

(2)expire命令

  語法:expire KEY seconds

  功能:設置key的過期時間。如果key已過期,將會被自動刪除。

(3)DEL命令

  語法:DEL key [KEY …]

  功能:刪除給定的一個或多個 key,不存在的 key 會被忽略。

2、實現同步鎖原理

(1)加鎖:“鎖”就是一個存儲在redis里的key-value對,key是把一組操作用字符串來形成唯一標識,value其實並不重要,因為只要這個唯一的key-value存在,就表示這個操作已經上鎖。

(2)解鎖:既然key-value對存在就表示上鎖,那么釋放鎖就自然是在redis里刪除key-value對

(3)阻塞、非阻塞:阻塞式的實現,若線程發現已經上鎖,會在特定時間內輪詢鎖。非阻塞式的實現,若發現線程已經上鎖,則直接返回。

(4)處理異常情況:假設當投資操作調用其他平台接口出現等待時,自然沒有釋放鎖,這種情況下加入鎖超時機制,用redis的expire命令為key設置超時時長,過了超時時間redis就會將這個key自動刪除,即強制釋放鎖

3、使用redis鎖還是出現同步問題

  一種可能是,2台機器同時訪問,一台訪問,還沒有把鎖設置過去的時候,另一台也查不到就會出現這個問題。

  解決方法:這跟寫代碼的方式有關。先查,如果不存在就set,這種方式有極微小的可能存在時間差,導致鎖set了2次。

  推薦使用 setIfAbsent 這樣在 redis set 的時候是單線程的,不會存在重復的問題

二、setIfAbsent的使用

1、作用:如果為空就set值,並返回1;如果存在(不為空)不進行操作,並返回0。

  很明顯,比get和set要好。因為先判斷get,再set的用法,有可能會重復set值。

2、setIfAbsent 和 setnx

  setIfAbsent 是java中的方法

  setnx 是 redis命令中的方法

redis> SETNX mykey "Hello"  // 不存在mykey,設置值並返回1
(integer) 1 redis> SETNX mykey "World"  // 存在mykey,不處理並返回0
(integer) 0 redis> GET mykey "Hello"
BoundValueOperations boundValueOperations = this.redisTemplate.boundValueOps(redisKey); flag = boundValueOperations.setIfAbsent(value); // flag 表示是否set
boundValueOperations.expire(seconds, TimeUnit.SECONDS); if(!flag){ // 重復
 repeatSerial.add(serialNo); continue; }else{// 沒有重復
 norepeatSerial.add(serialNo); }

3、需要注意的是以前 stringRedisTemplate.setIfAbsent()  在服務器是由2個命令組成的  完成一個setnx時候在設置 expire 時候中間中斷了,無法保證原子性。

  故需要使用 4 個參數的那個重載方法,這個底層是 set key value [EX seconds] [PX milliseconds] [NX|XX] 是原子性的

public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) { byte[] rawKey = rawKey(key); byte[] rawValue = rawValue(value); Expiration expiration = Expiration.from(timeout, unit); return execute(connection -> connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent()), true); } 

三、如何使用呢?分布式鎖

1、應用場景:比如我們有2台服務器,4個鏡像節點,那么就會有4個負載均衡的后台服務,如果你需要在代碼里加一個定時任務。那么最后4個后台服務都會執行這個定時任務,就會重復4次。

2、怎么辦呢?加分布式鎖,就會用到上面的方法咯。

3、分布式鎖實現中可能遇到的問題:看這篇文章,關於stringRedisTemplate.setIfAbsent()並設置過期時間遇到的問題:https://blog.csdn.net/weixin_34419326/article/details/88677793,主要注意一下幾點:

(1)加了事務管理之后,setIfAbsent的返回值是null

(2)當出現並發時,String result = stringRedisTemplate.opsForValue().get(key); 這里就會有多個線程同時拿到為空的key,然后同時寫入臟數據。

(3)最終解決方法:將redis版本升級到2.1以上,然后使用直接在setIfAbsent中設置過期時間


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM