前面講解到實戰問題】-- 設計禮品領取的架構設計以及多次領取現象解決?,如果出現網絡延遲的情況下,多個請求阻塞,那麼惡意攻擊就可以全部請求領取接口成功,而針對這種做法,我們使用<code>setnx</code>來解決,確定隻有一個請求可以進入接口請求。

下面,我們就專門講解一下<code>setnx</code>,<code>setnx</code>可以用作分布式鎖,但是這個場景并不是分布式鎖的一個較好的實踐,因為每個使用者的key都是不一樣的,我們主要是防止同一個使用者惡意領取,<code>setnx</code>本身是一個原子操作,可以保證多個線程隻有一個能拿到鎖,能傳回<code>true</code>,其他的都會傳回<code>false</code>。
但是上面的做法,沒有設定過期時間,在生産上一般是不可以這麼使用。不設定過期時間的key多了之後,redis伺服器很容易記憶體打滿,這時候不知道哪些是強制依賴的,隻能擴容,從代碼層面去清理,如果直接清理不常用的,也很難保證不出事。(基本不允許這麼幹,除非是基礎資料,跟着伺服器啟動,寫入<code>redis</code>的,不會變更的,比如城市資料,國家資料等等,當然,這些也可以考慮在本地記憶體中實作)
如果在上面的代碼中,加入逾時時間,假設是一個月或者半年,流程變成這樣:
設定key的逾時時間使用<code>expire</code>,但是這樣還有缺陷麼?
在<code>redis 2.6.12</code>之前,<code>setnx</code>和<code>expire</code>都不是原子操作,也就是很有可能在<code>setnx</code>成功之後,redis當季,expire設定失敗,也就不會有逾時時間了。雖然這個影響在目前業務不是很大,但是還是一個小缺陷。
<code>Redis2.6.12</code>以上版本,可以用<code>set</code>擷取鎖,set包含<code>setnx</code>和<code>expire</code>,實作了原子操作。也就是兩步要麼一起成功,要麼一起失敗。
除此之外,上面的流程可能還存在的一個問題,是請求<code>C</code>服務的時候出現逾時,然後删除key,恰好這個時候<code>redis</code>有問題,删除失敗了,這個<code>key</code>就永遠存在了。表現在業務上,就是<code>A</code>使用者點選了領取,領取失敗了,但是後面再怎麼點,都是已經領取的狀态了。
那這種現象怎麼優化呢?
這種情況,其實已經是很少見的情況,按照我們目前的業務場景也看,就是目前的使用者,<code>redis</code>記錄了它已經領取過了,但是由于接口的失敗,成功之後還沒将<code>mysql/其他資料庫</code>更新,兩個資料庫不一緻了。
我能想到的一個方法,就是再删除失敗的時候,告警,并且将業務相關的資料記錄下來,比如<code>key</code>,<code>uid</code>等等,針對這部分資料,做一次補發,或者手動删除key。
或者,啟動一個定時任務或者<code>lua</code>腳本,去判定<code>redis</code>和資料庫不一緻的情況,但是切記不要全部查詢,應該是隔一段時間,查詢最後增加的部分,做一個校驗以及相應的處理。枚舉<code>key</code>是十分耗時的操作!!!
<code>setnx</code> 除了解決上面的問題,還可以應用在解決緩存擊穿的問題上。
譬如現在有熱點資料,不僅在<code>mysql</code>資料庫存儲了,還在<code>redis</code>中存了一份緩存,那麼如果有一個時間點,緩存失效了,這時候,大量的請求打過來,同時到達,緩存拿不到資料,都去資料庫取資料,假設資料庫操作比較耗時,那麼壓力全都在資料庫伺服器上了。
這個時候所有的請求都去更新資料,明顯是不合适的,應該是使用分布式鎖,讓一個線程去請求<code>mysql</code>一次即可。但是為了避免死鎖的情況,如果逾時,得及時額外釋放鎖,要不可能請求<code>mysql</code>都失敗了,其他線程又拿不到鎖,那麼資料就會一直為<code>null</code>了。
可以使用以下的指令:
關于這個場景下的<code>setnx</code>先講到這裡,後面再講講分布式鎖相關的知識。
【刷題筆記】 Github倉庫位址:https://github.com/Damaer/codeSolution 筆記位址:https://damaer.github.io/codeSolution/
【作者簡介】:
秦懷,公衆号【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java源碼解析,JDBC,Mybatis,Spring,redis,分布式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡标題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正确,但是我保證所寫的均經過實踐或者查找資料。遺漏或者錯誤之處,還望指正。
2020年我寫了什麼?
開源刷題筆記
平日時間寶貴,隻能使用晚上以及周末時間學習寫作,關注我,我們一起成長吧~