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,代碼如有不對請多指教。