1. 程式人生 > >Redis:多線程修改同一個Key使用watch+事務(mutil)實現樂觀鎖

Redis:多線程修改同一個Key使用watch+事務(mutil)實現樂觀鎖

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); } }

此時,會出現以下問題:

  1. A線程獲取key的值為0,而B線程也獲取jkey的值0,則A把key值遞增為1,B線程也實現把key值遞增為1。兩個線程都執行了key值修改:0到1。
  2. 在1)中最終key修改為了1,但是c線程獲取key的值為0(因為c線程讀取key值時,a、b線程還未觸發修改,因此c線程讀取到的值為0),此時d線程讀取到的值為1(因為d線程讀取key值時,a、b線程已觸發修改,一次d線程取到的值為1)。
  3. 此時假設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)實現樂觀鎖