背景
開發活動報名業務,涉及到活動人數限制的問題,當併發量上來的時候,多人同時提交報名資訊,將會導致活動已報名人數的不準確,對業務造成影響,如下圖:
分析出現問題的原因是,設定操作發生的時候,並沒有確保當前人數的準確性,即沒有確保當前查詢出來的已報名人數與資料庫的一致性,導致客戶端併發的兩次操作有被覆蓋的情況發生
傳統資料庫 vs nosql
mysql
針對如上場景,若報名人數字段儲存在mysql資料庫中,可以使用一種常見的降低讀寫鎖衝突,保證資料一致性的樂觀鎖機制(compare and set cas),實現方案如下
將原來的操作sql** update act set num=# where actid=#
複製**
改為 update act set num=# where actid=# and num=#
複製**
即只有當查詢出來的資料與當前資料庫的資料一致時,才可以進行賦值操作,否則失敗
redis
若使用redis,則活動報名人數以鍵值對的形式存在記憶體中,業務**將會對記憶體中的人數進行操作,相比mysql,redis的效率更高,不會造成很大的延遲(若當併發量很大時,使用mysql進行報名人數記錄,cas的方案將會導致很多客戶端操作失敗,使用者體驗不好),但使用redis,其沒有很好的事務支援,以上mysql的解決方案不能很好的運用在redis上,因此如何設計redis鎖,進行共享資源(已報名活動人數)的操作,是需要解決的問題
使用到的命令說明
設計redis鎖之前,需要介紹下即將用到的幾個命令
setnx
將key設定值為value,如果key不存在,這種情況下等同set命令,返回值1。 當key存在時,什麼也不做,返回值0。
watch && multi
watch:標記所有指定的key 被監視起來,在事務中有條件的執行(樂觀鎖)
multi:標記乙個事務塊的開始。 隨後的指令將在執行exec時作為乙個原子執行
當兩者一起使用的時候,首先key被watch監視,若在呼叫 exec 命令執行事務時, 如果任意乙個被監視的鍵被其他客戶端修改了, 那麼整個事務不再執行, 直接返回失敗。如下表:
時間客戶端a
客戶端b
t1watch name
t2multi
t3set name owen
t4set name tom
t5exec
在時間 t4 ,客戶端 b 修改了 name 鍵的值, 當客戶端 a 在 t5 執行 exec 時,redis 會發現 name 這個被監視的鍵已經被修改, 因此客戶端 a 的事務不會被執行,而是直接返回失敗。
getset
getset key value 返回之前的舊值value,之後設定key的新值
redis基本解決思路以及遇到的問題
以下列舉使用redis鎖的基本思路
注:例子使用spring-data-redis庫,setnx命令變為setifabsent,並且返回true or false private stringredistemplate stringredistemplate;
public boolean setconcurrentlock(string key) throws interruptedexception else {
stringredistemplate.unwatch();
timeunit.milliseconds.sleep(3);
holder.set(ops.get(key));
return true;
public void deleteconcurrentlock(string key) {
valueoperations ops = stringredistemplate.opsforvalue();
long expire = long.valueof(ops.get(key));
if(exprie.equals(holder.get())){
stringredistemplate.delete(key);
holder.remove();
複製**
如上面的場景一,首先執行緒2,執行緒3同時判斷出lock超時後,對lock進行watch監視,然後將getset操作放到事務中執行,若執行緒2執行完事務,修改了lock的時間後,執行緒3由於執行事務命令lock被修改而失敗,不會覆蓋設定執行緒2的超時時間,解決場景一問題
對於場景二,為了防止已經超時的執行緒誤刪其他正在執行的執行緒lock,引入threadlock變數,將本執行緒設定的超時時間放入threadlock中,若刪除的時候,從redis取出的時間變化了,證明該執行緒超時,時間被其他執行緒重新設定過,就不需要刪除lock。最後需要注意的是使用threadlocal需要在判斷是夠刪除lock鎖時手動刪除,防止web伺服器中的執行緒池對執行緒復用,造成threadlocal重複使用。
總結本篇實踐是基於單點redis伺服器情況下的鎖(若工程在多機器下部署,可以裝逼的叫redis分布式鎖)。但在redis集群架構下,如果master節點down機,由於redis主從複製是非同步的,會有明顯的race-condition。redis文件中提供了一種解決方案:redlock,後續有機會再去實踐學習吧。。。
redis入門 redis常用的鍵
設定鍵值 set key value set name michael 獲取鍵值 get key get name 獲取制定健的值的序列化版本 dump key dump name 判斷該健是否存在 exists key exists name expire name 3 那麼name健在3秒後過期...
Redis 鍵的操作
coding utf 8 import redis 連線池連線 避免每次建立 釋放連線的開銷 pool redis.connectionpool host localhost port 6379 db 0 red redis.redis connection pool pool 在一次請求中指定多個...
Redis學習(六)Redis鍵 key
redis 鍵命令用於管理 redis 的鍵。redis 鍵命令的基本語法如下 redis 127.0.0.1 6379 command key name redis 127.0.0.1 6379 set runoobkey redis okredis 127.0.0.1 6379 del runo...