天天看點

redisLock redis分布式鎖redis-lock

redis setnx cmmand

java object condition queue 條件隊列

retrycount 帶有重試次數限制

object wait time 帶有逾時時間的wait

delete lock 删除遠端鎖

acquire lock 申請lock

release lock 釋放lock

demo 示範

鎖的粒度問題,鎖分解、鎖分段

redis setnx 指令特性

當指定key不存在時才設定。也就是說,如果傳回1說明你的指令被執行成功了,redis伺服器中的key是你之前設定的值。如果傳回0,說明你設定的key在redis伺服器裡已經存在。

如果設定成功了,才進行過期時間設定,防止你的retry lock重複設定這個過期時間,導緻永遠不過期。

這裡有一個小竅門,可以盡可能的最大化cpu使用率又可以解決公平性問題。

當你頻繁retry的時候,要麼while(true)死循環,然後加個Thread.sleep,或者CAS。前者存在一定線程上下文切換開銷(Thread.sleep是不會釋放出目前内置鎖),而CAS在不清楚遠端鎖被占用多久的情況會浪費很多CPU計算周期,有可能一個任務計算個十幾分鐘,CPU不可能空轉這麼久。

這裡我嘗試使用condition queue條件隊列特性來實作(當然肯定還有其他更優的方法)。

使用條件隊列的好處就是,它雖然釋放出了CPU但是也不會持有目前synchronized,這樣就可以讓其他并發進來的線程也可以擷取到目前内置鎖,然後形成隊列。當wait時間到了被排程喚醒之後才會重新來申請synchronized鎖。

簡單講就是不會再鎖上等待而是在隊列裡等待。java object每一個對象都持有一個條件隊列,與目前内置鎖配合使用。

等待遠端redis lock肯定是需要一定重試機制,但是這種重試是需要一定的限制。

這種等待是需要使用者指定的, if (isWait && retryCounts < RetryCount) ,當isWait為true才會進行重試。

object.wait(timeout),條件隊列中的方法wait是需要一個waittime。

預設2000毫秒。

注意:this.wait雖然會blocking住,但是這裡的内置鎖是會立即釋放出來的。是以,有時候我們可以借助這種特性來優化特殊場景。

釋放redis lock比較簡單,直接del key就好了

一旦delete 之後,首先wait喚醒的線程将會獲得鎖。

2017-06-18 13:57:43.867 INFO 1444 --- [nio-8080-exec-1] c.plen.opensource.implement.RedisLocker : t:23,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,擷取到鎖:product:10100101:shopping 2017-06-18 13:57:47.062 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:57:49.063 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:57:51.064 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:57:53.066 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:57:55.068 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:57:57.069 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:57:59.070 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:01.071 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:03.072 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:05.073 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:07.074 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,指定時間内擷取鎖失敗:product:10100101:shopping 2017-06-18 13:58:23.768 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:25.769 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:27.770 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:29.772 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:31.773 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:33.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:58:35.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,目前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,擷取到鎖:product:10100101:shopping

thread 23 優先擷取到對商品ID 10100101 進行修改,是以先鎖住目前商品。

t:23,目前節點:843d3ec0-9c22-4d8a-bcaa-745dba35b8a4,擷取到鎖:product:10100101:shopping

緊接着,thread 25也來對目前商品 10100101進行修改,是以在嘗試擷取鎖。

2017-06-18 13:50:11.021 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:13.023 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:15.026 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:17.028 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:19.030 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:21.031 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:23.035 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:25.037 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:27.041 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:29.042 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待擷取鎖:product:10100101:shopping 2017-06-18 13:50:35.289 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,目前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,指定時間内擷取鎖失敗:product:10100101:shopping

在進行了retry10次(2000毫秒,2秒)之後,擷取失敗,直接傳回,等待下次任務排程開始。

thread 28 發起對商品 10100101 進行修改,retry6次之後擷取到lock。

這裡的例子比較簡單。如果在并發比較大的情況下是需要結合鎖分解、鎖分段來進行優化的。

修改商品,沒有必要鎖住整個商品庫,隻需要鎖住你需要修改的指定ID的商品。也可以借鑒鎖分段思路,将資料按照一定次元進行劃分,然後加上不同次元的鎖,可以提升CPU性能。可以根據商品catagory來設計段鎖或者batch來設計段鎖。

源碼已送出gihub,代碼如有不對請多指教。