文章目錄
- 概述
- Redis watch流程
- ABA問題
- 使用watch成功送出的事務的案例
- 使用watch復原的事務的案例
概述
在 Redis 中使用 watch 指令可以決定事務是執行還是復原。
一般而言,可以在 multi 指令之前使用 watch 指令監控某些鍵值對,然後使用 multi 指令開啟事務,執行各類對資料結構進行操作的指令,這個時候這些指令就會進入隊列。
當 Redis 使用 exec 指令執行事務的時候,它首先會去比對被 watch 指令所監控的鍵值對,
- 如果沒有發生變化,那麼它會執行事務隊列中的指令,送出事務;
- 如果發生變化,那麼它不會執行任何事務中的指令,而去事務復原。
無論事務是否復原 , Redis 都會去取消執行事務前的 watch 指令
Redis watch流程
流程如下:
Redis 參考了多線程中使用的 CAS (比較與交換, Compare And Swap ) 去執行的。在
資料高并發環境的操作中,我們把這樣的一個機制稱為樂觀鎖.
ABA問題
先簡要論述其操作的過程:
當一條線程去執行某些業務邏輯,但是這些業務務邏輯操作的資料可能被其他線程共享了,這樣會引發多線程中資料不一緻的情況。為了克服這個問題,首先,線上程開始時讀取這些多線程共享的資料,并将其儲存到目前程序的副本中,我們稱為舊值( old value), watch 指令就是這樣的一個功能 。
然後,開啟線程業務邏輯,由 multi 指令提供這一功能。在執行更新前,比較目前線程副本儲存的舊值和目前線程共享的值是否一緻,如果不一緻,那麼該資料己經被其他線程操作過,此次更新失敗。為了保持一緻,線程就不去更新任何值,而将事務復原:否則就認為它沒有被其他線程操作過,執行對應的業務邏輯, exec 指令就是執行“類似”這樣的一個功能 。
注意,“類似”這個字眼,因為不完全是,原因是 CAS 原理會産生 ABA 問題。所謂ABA 問題來自于 CAS 原理的一個設計缺陷,它可能引發 ABA 問題
在處理複雜運算的時候,被線程 2 修改的 X 的值有可能導緻線程1的運算出錯,而最後線程 2 将 X 的值修改為原來的舊值 A,那麼到了線程 1運算結束的時間順序 T6,它将j檢測 X 的值是否發生變化,就會拿舊值 A 和 目前的 X 的值 A 比對 , 結果是一緻的, 于是送出事務,然後在複雜計算的過程中 X 被線程 2 修改過了,這會導緻線程1的運算出錯。
在這個過程中,對于線程 2 而言 , X 的值的變化為 A->B->A,是以 CAS 原理的這個設計缺陷被形象地稱為“ABA 問題”。
僅僅記錄一個舊值去比較是不足夠的,還要通過其他方法避免 ABA 問題。常見的方法
如 Hibernate 對緩存的持久對象( PO )加入字段段 version 值,當每次操作一次該 PO,則version=version+ 1 , 這樣采用 CAS 原理探測 version 宇段 , 就能在多線程的環境中,排除ABA 問題,進而保證資料的一緻性。
Redis 在執行事務的過程中 , 并不會阻塞其他連接配接的并發,而隻是通過 比較 watch 監控的鍵值對去保證資料的一緻性 , 所 以 Redis 多個事務完全可 以在非阻塞的多線程環境中井發執行,而且 Redis 的機制是不會産生 ABA 問題的, 這樣就有利于在保證資料一緻的基礎上 , 提高高并發系統的資料讀/寫性能。
使用watch成功送出的事務的案例
時刻 | 用戶端 | 說明 |
T1 | set key1 value1 | 初始化 key1 |
T2 | watch key1 | 監控 key1 的健值對 |
T3 | multi | 開啟事務 |
T4 | set key2 value2 | 設定 key2 的值 |
T5 | exec | 送出事務, Redis會在這個時間點檢測 key1 的值在 T2 時刻後,有沒有被其他指令修改泣,如果沒有 , 則送出事務去執行 |
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> SET key1 value1
OK
127.0.0.1:6379> WATCH key1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET key2 value2
QUEUED
127.0.0.1:6379> EXEC
1) OK
127.0.0.1:6379>
這裡我們使用了 watch 指令設定了 一個 key1 的監控 , 然後開啟事務設定 key2 , 直至exec 指令去執行事務. 如果在目前會話中修改key1的值,也是可以成功的。
使用watch復原的事務的案例
時刻 | 用戶端1 | 用戶端2 | 說明 |
T1 | set key1 value1 | — | 用戶端 1 :傳回 OK |
T2 | watch key1 | — | 用戶端 1 :監控 key1 |
T3 | multi | — | 用戶端 1: 開啟事務 |
T4 | set key2 value2 | — | 用戶端1 : 事務指令入列 |
T5 | — | set key1 val1 | 用戶端 2:修改 key1的值 |
T6 | exec | — | 用戶端 1:執行事務,但是事務會先檢查在 T2 時刻被監控的 key1 是否被其他指令修改過。因為客戶揣 2 修改過,是以它會復原事務,事實上如果用戶端執行的是 set key1 value1 指令,它也會認為 key1 被修改過,然後傳回( nil) ,是以是不會産生 ABA 問題的 |
用戶端一
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379>
127.0.0.1:6379> SET key1 value1
OK
127.0.0.1:6379> WATCH key1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key2 value2
QUEUED
# 在這一步暫停下,打開第二個用戶端去修改key1的值,然後再exec
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379>
用戶端二:
然後回到用戶端1 執行exec