Redis:多線程修改同一個Key使用watch+事務(mutil)實現樂觀鎖
阿新 • • 發佈:2018-07-29
width uno ... ack spool 場景 .html 高並發 遇到的問題
本篇文章是通過watch(監控)+mutil(事務)實現應用於在分布式高並發處理等相關場景。下邊先通過redis-cli.exe來測試多個線程修改時,遇到問題及解決問題。
高並發下修改同一個key遇到的問題:
1)定義一個hash類型的key,key為:lock_test,元素locker的值初始化為0。
2)實現高並發下對locker元素的值遞增:定義64個多線程,並發的對lock_test元素locker的值進行修改。
package com.dx.es; import java.util.concurrent.CountDownLatch; import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool; public class Test_UnLock { public static void main(String[] args) { final JedisPool pool = RedisUtil.getPool(); // 獲得jedis對象 Jedis jedis = pool.getResource(); jedis.hset("lock_test", "locker", "0"); String val= jedis.hget("lock_test", "locker"); System.out.println("lock_test.locker的初始值為:" + val); jedis.close(); int threahSize = 64; final CountDownLatch threadsCountDownLatch = new CountDownLatch(threahSize); Runnable handler = new Runnable() {public void run() { Jedis jedis = pool.getResource(); Integer integer = Integer.valueOf(jedis.hget("lock_test", "locker")); jedis.hset("lock_test", "locker", String.valueOf(integer + 1)); jedis.close(); threadsCountDownLatch.countDown(); } }; for (int i = 0; i < threahSize; i++) { new Thread(handler).start(); } // 等待所有並行子線程任務完成。 try { threadsCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("complete"); val = jedis.hget("lock_test", "locker"); System.out.println(val); } }
此時,會出現以下問題:
- A線程獲取key的值為0,而B線程也獲取jkey的值0,則A把key值遞增為1,B線程也實現把key值遞增為1。兩個線程都執行了key值修改:0到1。
- 在1)中最終key修改為了1,但是c線程獲取key的值為0(因為c線程讀取key值時,a、b線程還未觸發修改,因此c線程讀取到的值為0),此時d線程讀取到的值為1(因為d線程讀取key值時,a、b線程已觸發修改,一次d線程取到的值為1)。
- 此時假設d線程優先觸發遞增,則在c線程未觸發提交之前d線程已經把值修改了2,但是c此時並不知道在它獲取到值到修改之前這段時間發生了什麽,直接把值修改1。
此時執行打印結果為:
lock_test.locker的初始值為:0 complete 24 #備註:也可能是其他值,可能是正確值64的可能性比較小。
通過watch(監控)+mutil(事務)解決上邊的問題:
redis-cli.exe下的事務操作:
# 事務被成功執行 redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> INCR user_id QUEUED redis 127.0.0.1:6379> PING QUEUED redis 127.0.0.1:6379> EXEC 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG
並發情況下使用watch+mutil操作:
事務塊內所有命令的返回值,按命令執行的先後順序排列。 當操作被打斷時,返回空值 nil 。
A線程:
# 監視 key ,且事務成功執行 redis 127.0.0.1:6379> WATCH lock lock_times OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> SET lock "huangz" QUEUED redis 127.0.0.1:6379> INCR lock_times QUEUED redis 127.0.0.1:6379> EXEC 1) OK 2) (integer) 1
B線程:
# 監視 key ,且事務被打斷 redis 127.0.0.1:6379> WATCH lock lock_times OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> SET lock "joe" # 就在這時,另一個客戶端修改了 lock_times 的值 QUEUED redis 127.0.0.1:6379> INCR lock_times QUEUED redis 127.0.0.1:6379> EXEC # 因為 lock_times 被修改, joe 的事務執行失敗 (nil)
上邊演示了A、B線程並發下的watch+mutil操作情況。
需要掌握Redis 事務命令:
Watch 命令用於監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麽事務將被打斷。可用版本:>= 2.2.0
序號 | 命令及描述 |
---|---|
1 | DISCARD 取消事務,放棄執行事務塊內的所有命令。 |
2 | EXEC 執行所有事務塊內的命令。 |
3 | MULTI 標記一個事務塊的開始。 |
4 | UNWATCH 取消 WATCH 命令對所有 key 的監視。 |
5 | WATCH key [key ...] 監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麽事務將被打斷。 |
Redis:多線程修改同一個Key使用watch+事務(mutil)實現樂觀鎖