Redis分佈式鎖理論知識

redis作爲目前最流行的k-v存儲系統之一,在實際的應用場景中如何使用Redis就變的非常重要了。
由於大家都熟知Redis可以用於緩存/隊列的使用,並且網上有很多講解內容,故在此不介紹Redis的緩存/隊列使用場景,本文更偏重於Redis的其他使用場景。

分佈式鎖

應用場景

在電商系統中,爲了推薦自己的品牌和吸引用戶量,那麼會推出一個產品,這個產品只能被一個用戶購買,如果一個用戶正在購買時,其他用戶點擊購買的時候,則告知該用戶,商品已經售出。

場景分析

假設購買流程爲:1.用戶看到商品,2.用戶點擊購買,3.用戶使用支付寶或微信支付,4.用戶購買成功

目前有2個用戶:A和B

場景並發現象:

用戶A:1.用戶看到商品,2.用戶點擊購買
用戶B:1.用戶看到商品,2.用戶點擊購買
此時用戶A和用戶B都進入到了第三步:3.用戶使用支付寶或微信支付
那可能就會出現併發問題,兩個用戶都同時購買了1個商品。

解決方案

根據以往經驗,可知,我們如上流程可以通過鎖來進行控制,傳統的數據庫鎖的方式在此不進行介紹了,來看一下redis是如何實現的。

使用redis,我們可以解決兩個用戶同時操作一個數據流程的時候:

當用戶要購買此商品的時候,我們可以創建一個key,流程完成後,並改變此商品的出售狀態(此商品已賣完),刪除key。
其他用戶在此想購買此商品的時候,去redis中讀取這個key,如果key存在,那麼就證明此商品正在被一個用戶購買(這個用戶正在購買的流程中),告知該用戶,此商品已賣完

這是我提出來的一個大概流程,但仔細思考起來,裏邊還有很多小細節,那我們來慢慢分析。

版本1

用戶A:1.用戶看到商品 -> 2.用戶點擊購買 -> set buying A (設置一個key的名稱是buying,value是A) -> 3.用戶使用支付寶或微信支付 -> 4.用戶購買成功 -> 更改商品出售狀態 -> delete buying (刪除redis的key)
用戶B:1.用戶看到商品 -> 2.用戶點擊購買 -> get buying -> echo “此商品已賣完”

上邊流程如果看懂了後,我們思考一個問題,當用戶A操作到3.用戶使用支付寶或微信支付時,突然用戶由於一些突發情況,然後頁面退出了,那麼由於流程沒有完成,所以這個redis中的key(buying)將一直存在,也就是說商品沒有賣出,但是其他人再也不能買了。

版本2

由於要解決以上問題,我們需要把這樣的一個購買時間設置一個有效期,比如說如果用戶在30分鐘內都未購買成功,那麼不管是什麼原因,我都應該讓這個redis中的key失效,讓用戶此次購買的行爲失效。

也就是說,我們應該把buying設置一個有效期

用戶A:1.用戶看到商品 -> 2.用戶點擊購買 -> set buying A (設置一個key的名稱是buying,value是A) -> set expire 30-> 3.用戶使用支付寶或微信支付 -> 4.用戶購買成功 -> 更改商品出售狀態 -> delete buying (刪除redis的key)
用戶B:1.用戶看到商品 -> 2.用戶點擊購買 -> get buying -> echo “此商品已賣完”

這個版本2中的用戶A設置了一個key後,即使中途真的發生什麼意外導致退出後,依然在指定時間後,其他用戶可以繼續購買。

那是否已經覺得這個方案很完美了呢?其實不然,我們把焦點放到set buying A(在redis設置key)的這點。

用戶是這樣操作的:

用戶A:1.用戶看到商品 -> 2.用戶點擊購買 -> get buying
用戶B:1.用戶看到商品 -> 2.用戶點擊購買 -> get buying
此時由於用戶A和用戶B同時點擊購買(2.用戶點擊購買) 然後去讀取buying,可能因爲網絡延遲問題,導致了同時都讀取了buying,但是兩個人讀取buying這個key是不存在的,會產生後邊的流程:
用戶A:1.用戶看到商品 -> 2.用戶點擊購買 -> get buying -> set buying A
用戶B:1.用戶看到商品 -> 2.用戶點擊購買 -> get buying -> set buying B

不管用戶A還是用戶B最終設置了buying這個redis的key值,依然產生了併發問題,結果肯定不是我們想要的。

版本3

我們知道redis本身是指令級別的,不管是使用JRdis還是spring提供的redisTemplate,我們都無法改變這個行爲,那如果解決版本2中出現的問題呢?
redis中有一個指令是:setnx

Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for “SET if Not eXists”.

返回值是這樣規定的:

Integer reply, specifically: 1 if the key was set 0 if the key was not set

@see:http://redis.io/commands/setnx

這個命令可以當我們設置一個key的時候,判斷這個key是否已經有值,如果沒有值,則設置這樣的key-value,並返回1,如果有值則不設置,直接返回0。

有了這個指令就很好的解決了版本2中的問題

用戶A:1.用戶看到商品 -> 2.用戶點擊購買 -> setnx buying A (設置一個key的名稱是buying,value是A) -> set expire 30-> 3.用戶使用支付寶或微信支付 -> 4.用戶購買成功 -> 更改商品出售狀態 -> delete buying (刪除redis的key)
用戶B:1.用戶看到商品 -> 2.用戶點擊購買 -> get buying -> echo “此商品已賣完”

版本4

以上的版本3已經可以幫我們完成的差不多了。、
但是還有一點就是如果當用戶A在redis中設置值的時候,突然掉線了,設置失效指定沒有發送,那麼其他用戶依然再也不能買這個商品了。

解決方案是使用pipe管道將setnx buying A和set expire 30 放到一起,一起發送給redis.

小結

以上便是redis作爲分佈式鎖的一個應用場景,筆者提到的本案例只起到拋磚引玉的作用,讀者可以根據自己的業務需求,再動態添加redis的一些事物等高級功能。

秒殺防超賣

應用場景

有一個秒殺商品,秒殺庫存爲10,在12:00的時候有1w用戶進行點擊購買,如何防止超賣

場景分析

例如有一秒殺產品A,1w用戶在同一秒鐘進行搶購,如果每一個用戶在秒殺時,都去讀取秒殺庫存數,判斷未超買後,再去增加佔用庫存數,那麼這樣的併發問題是顯而易見的。

當然數據操作還有一個就是使用表的行鎖進行控制,但是筆記認爲這個從用戶體驗來講,對於這個場景來說,是不可取的。

由於redis的讀寫高性能的特點,我們將使用redis去處理這樣的秒殺場景。

解決方案

假設產品A,秒殺庫存10。

當創建秒殺商品A的時候,創建redis對象:set pA 10 和 數據庫秒殺數據: pA 10
當1w用戶進行秒殺時,調用redis: INCRBY pA -1 (每次步長都爲-1,然後返回扣減之後的庫存量) 和 在redis中添加購買人與購買時間: hset pA 1 1478584205(hset 產品 用戶 秒殺時間)
如果get pA < 0了,那麼就不讓用戶購買了,echo “秒殺已賣完”
當用戶付款後,扣減數據庫中秒殺庫存數,刪除redis hdel pA 1
每一段時間都跑一下job去查詢一下對於秒殺商品pA下單還沒有付款的人,超過一段時間如果不付款則認爲用戶不想秒殺,則歸還redis庫存:INCRBY pA 1

以上步驟中,第5步,可以根據自己的業務而定。

小結

秒殺防超賣的方式有很多,以上我提供了一個redis的思考方向,其實實際生產環境,更應該考慮限流、緩存等操作。

對象關係存儲

應用場景

需要開發一個客服系統,此係統要求實時監控用戶的登錄和登出狀態,並且將用戶快速分配給客服。

場景分析

因爲客服系統會基於websock進行通訊,所以有可能發生斷網,掉線等情況,但是卻要求客服和用戶的狀態都是實時監控和記錄的。

故採用redis的存儲方式解決該該應用場景出現的問題。

redis 主要應該記錄用戶信息,客服信息,以及兩者之間的關係:用戶-客服 1-1 客服-用戶 n-1

所以這裏邊我們用到了redis中的hash結構,set結構。

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