一、業務場景:同步鎖的問題與分布式鎖的應用
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中設置過期時間

