Redis 通過 MULTI 屏鳍、 DISCARD 锈锤、 EXEC 和 WATCH 四個(gè)命令來實(shí)現(xiàn)事務(wù)功能闯割, 本章首先討論使用 MULTI 拉讯、 DISCARD 和 EXEC 三個(gè)命令實(shí)現(xiàn)的一般事務(wù), 然后再來討論帶有 WATCH 的事務(wù)的實(shí)現(xiàn)挑辆。
因?yàn)槭聞?wù)的安全性也非常重要例朱, 所以本章最后通過常見的 ACID 性質(zhì)對 Redis 事務(wù)的安全性進(jìn)行了說明。
事物
事務(wù)提供了一種“將多個(gè)命令打包鱼蝉, 然后一次性洒嗤、按順序地執(zhí)行”的機(jī)制, 并且事務(wù)在執(zhí)行的期間不會主動中斷 —— 服務(wù)器在執(zhí)行完事務(wù)中的所有命令之后魁亦, 才會繼續(xù)處理其他客戶端的其他命令渔隶。
以下是一個(gè)事務(wù)的例子, 它先以 MULTI 開始一個(gè)事務(wù)洁奈, 然后將多個(gè)命令入隊(duì)到事務(wù)中间唉, 最后由 EXEC 命令觸發(fā)事務(wù), 一并執(zhí)行事務(wù)中的所有命令:
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set msg 'hello world'
QUEUED
redis 127.0.0.1:6379> get msg
QUEUED
redis 127.0.0.1:6379> sadd tag 'java' 'c++' 'C#'
QUEUED
redis 127.0.0.1:6379> smembers tag
QUEUED
redis 127.0.0.1:6379> exec
1) OK
2) "hello world"
3) (integer) 3
4) 1) "c++"
2) "C#"
3) "java"
redis 127.0.0.1:6379>
一個(gè)事務(wù)從開始到執(zhí)行會經(jīng)歷以下三個(gè)階段:
1.開始事務(wù)利术。
2.命令入隊(duì)呈野。
3.執(zhí)行事務(wù)。
下文將分別介紹事務(wù)的這三個(gè)階段氯哮。
開始事物
MULTI 命令的執(zhí)行標(biāo)記著事務(wù)的開始:
redis 127.0.0.1:6379> multi
OK
這個(gè)命令唯一做的就是际跪, 將客戶端的 REDIS_MULTI 選項(xiàng)打開, 讓客戶端從非事務(wù)狀態(tài)切換到事務(wù)狀態(tài)喉钢。
命令入隊(duì)
當(dāng)客戶端處于非事務(wù)狀態(tài)下時(shí)姆打, 所有發(fā)送給服務(wù)器端的命令都會立即被服務(wù)器執(zhí)行:
redis 127.0.0.1:6379> set msg 'hello world'
OK
redis 127.0.0.1:6379> get msg
"hello world"
但是, 當(dāng)客戶端進(jìn)入事務(wù)狀態(tài)之后肠虽, 服務(wù)器在收到來自客戶端的命令時(shí)幔戏, 不會立即執(zhí)行命令, 而是將這些命令全部放進(jìn)一個(gè)事務(wù)隊(duì)列里税课, 然后返回 QUEUED 闲延, 表示命令已入隊(duì):
redis 127.0.0.1:6379> set msg 'hello world'
QUEUED
redis 127.0.0.1:6379> get msg
QUEUED
以下流程圖展示了這一行為:
事務(wù)隊(duì)列是一個(gè)數(shù)組, 每個(gè)數(shù)組項(xiàng)是都包含三個(gè)屬性:
1.要執(zhí)行的命令(cmd)韩玩。
2.命令的參數(shù)(argv)垒玲。
3.參數(shù)的個(gè)數(shù)(argc)。
舉個(gè)例子找颓, 如果客戶端執(zhí)行以下命令:
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set msg 'hello world'
QUEUED
redis 127.0.0.1:6379> get msg
QUEUED
redis 127.0.0.1:6379> sadd tag 'java' 'c++' 'C#'
QUEUED
redis 127.0.0.1:6379> smembers tag
QUEUED
那么程序?qū)榭蛻舳藙?chuàng)建以下事務(wù)隊(duì)列:
數(shù)字索引 | cmd | argv | argc |
---|---|---|---|
0 | set | ["msg","hello world"] | 2 |
1 | get | ["msg"] | 1 |
2 | sadd | ["tag","java","c++","C#"] | 4 |
3 | smembers | ["tag"] | 1 |
執(zhí)行事物
前面說到合愈, 當(dāng)客戶端進(jìn)入事務(wù)狀態(tài)之后, 客戶端發(fā)送的命令就會被放進(jìn)事務(wù)隊(duì)列里。
但其實(shí)并不是所有的命令都會被放進(jìn)事務(wù)隊(duì)列佛析, 其中的例外就是 EXEC 益老、 DISCARD 、 MULTI 和 WATCH 這四個(gè)命令 —— 當(dāng)這四個(gè)命令從客戶端發(fā)送到服務(wù)器時(shí)寸莫, 它們會像客戶端處于非事務(wù)狀態(tài)一樣捺萌, 直接被服務(wù)器執(zhí)行:
如果客戶端正處于事務(wù)狀態(tài), 那么當(dāng) EXEC 命令執(zhí)行時(shí)膘茎, 服務(wù)器根據(jù)客戶端所保存的事務(wù)隊(duì)列桃纯, 以先進(jìn)先出(FIFO)的方式執(zhí)行事務(wù)隊(duì)列中的命令: 最先入隊(duì)的命令最先執(zhí)行, 而最后入隊(duì)的命令最后執(zhí)行披坏。
比如說慈参,對于以下事務(wù)隊(duì)列:
數(shù)字索引 | cmd | argv | argc |
---|---|---|---|
0 | set | ["msg","hello world"] | 2 |
1 | get | ["msg"] | 1 |
2 | sadd | ["tag","java","c++","C#"] | 4 |
3 | smembers | ["tag"] | 1 |
程序會首先執(zhí)行 SET 命令, 然后執(zhí)行 GET 命令刮萌, 再然后執(zhí)行 SADD 命令, 最后執(zhí)行 SMEMBERS 命令娘扩。
執(zhí)行事務(wù)中的命令所得的結(jié)果會以 FIFO 的順序保存到一個(gè)回復(fù)隊(duì)列中着茸。
比如說,對于上面給出的事務(wù)隊(duì)列琐旁,程序?qū)殛?duì)列中的命令創(chuàng)建如下回復(fù)隊(duì)列:
數(shù)字索引 | 回復(fù)類型 | 回復(fù)內(nèi)容 |
---|---|---|
0 | status code reply | OK |
1 | bulk reply | "hello world" |
2 | integer reply | 3 |
3 | multi-bulk reply | ["java","c++","C#"] |
當(dāng)事務(wù)隊(duì)列里的所有命令被執(zhí)行完之后涮阔, EXEC 命令會將回復(fù)隊(duì)列作為自己的執(zhí)行結(jié)果返回給客戶端, 客戶端從事務(wù)狀態(tài)返回到非事務(wù)狀態(tài)灰殴, 至此敬特, 事務(wù)執(zhí)行完畢。
事務(wù)的整個(gè)執(zhí)行過程可以用以下偽代碼表示:
def execute_transaction():
# 創(chuàng)建空白的回復(fù)隊(duì)列
reply_queue = []
# 取出事務(wù)隊(duì)列里的所有命令牺陶、參數(shù)和參數(shù)數(shù)量
for cmd, argv, argc in client.transaction_queue:
# 執(zhí)行命令伟阔,并取得命令的返回值
reply = execute_redis_command(cmd, argv, argc)
# 將返回值追加到回復(fù)隊(duì)列末尾
reply_queue.append(reply)
# 清除客戶端的事務(wù)狀態(tài)
clear_transaction_state(client)
# 清空事務(wù)隊(duì)列
clear_transaction_queue(client)
# 將事務(wù)的執(zhí)行結(jié)果返回給客戶端
send_reply_to_client(client, reply_queue)
在事務(wù)和非事務(wù)狀態(tài)下執(zhí)行命令
無論在事務(wù)狀態(tài)下, 還是在非事務(wù)狀態(tài)下掰伸, Redis 命令都由同一個(gè)函數(shù)執(zhí)行皱炉, 所以它們共享很多服務(wù)器的一般設(shè)置, 比如 AOF 的配置狮鸭、RDB 的配置合搅,以及內(nèi)存限制,等等歧蕉。
不過事務(wù)中的命令和普通命令在執(zhí)行上還是有一點(diǎn)區(qū)別的灾部,其中最重要的兩點(diǎn)是:
1.非事務(wù)狀態(tài)下的命令以單個(gè)命令為單位執(zhí)行,前一個(gè)命令和后一個(gè)命令的客戶端不一定是同一個(gè)惯退;
而事務(wù)狀態(tài)則是以一個(gè)事務(wù)為單位赌髓,執(zhí)行事務(wù)隊(duì)列中的所有命令:除非當(dāng)前事務(wù)執(zhí)行完畢,否則服務(wù)器不會中斷事務(wù),也不會執(zhí)行其他客戶端的其他命令春弥。
2.在非事務(wù)狀態(tài)下呛哟,執(zhí)行命令所得的結(jié)果會立即被返回給客戶端;
而事務(wù)則是將所有命令的結(jié)果集合到回復(fù)隊(duì)列匿沛,再作為 EXEC 命令的結(jié)果返回給客戶端扫责。
事務(wù)狀態(tài)下的 DISCARD 、 MULTI 和 WATCH 命令
除了 EXEC 之外逃呼, 服務(wù)器在客戶端處于事務(wù)狀態(tài)時(shí)鳖孤, 不加入到事務(wù)隊(duì)列而直接執(zhí)行的另外三個(gè)命令是 DISCARD 、 MULTI 和 WATCH 抡笼。
DISCARD 命令用于取消一個(gè)事務(wù)苏揣, 它清空客戶端的整個(gè)事務(wù)隊(duì)列, 然后將客戶端從事務(wù)狀態(tài)調(diào)整回非事務(wù)狀態(tài)推姻, 最后返回字符串 OK
給客戶端平匈, 說明事務(wù)已被取消。
Redis 的事務(wù)是不可嵌套的藏古, 當(dāng)客戶端已經(jīng)處于事務(wù)狀態(tài)增炭, 而客戶端又再向服務(wù)器發(fā)送 MULTI 時(shí), 服務(wù)器只是簡單地向客戶端發(fā)送一個(gè)錯(cuò)誤拧晕, 然后繼續(xù)等待其他命令的入隊(duì)隙姿。 MULTI 命令的發(fā)送不會造成整個(gè)事務(wù)失敗, 也不會修改事務(wù)隊(duì)列中已有的數(shù)據(jù)厂捞。
WATCH 只能在客戶端進(jìn)入事務(wù)狀態(tài)之前執(zhí)行输玷, 在事務(wù)狀態(tài)下發(fā)送 WATCH 命令會引發(fā)一個(gè)錯(cuò)誤, 但它不會造成整個(gè)事務(wù)失敗靡馁, 也不會修改事務(wù)隊(duì)列中已有的數(shù)據(jù)(和前面處理 MULTI 的情況一樣)欲鹏。
帶 WATCH 的事務(wù)
WATCH 命令用于在事務(wù)開始之前監(jiān)視任意數(shù)量的鍵: 當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí), 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶端修改了奈嘿, 那么整個(gè)事務(wù)不再執(zhí)行貌虾, 直接返回失敗。
以下示例展示了一個(gè)執(zhí)行失敗的事務(wù)例子:
redis 127.0.0.1:6379> watch name
OK
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set name 'cheng'
QUEUED
redis 127.0.0.1:6379> exec
(nil)
redis 127.0.0.1:6379>
以下執(zhí)行序列展示了上面的例子是如何失敗的:
時(shí)間 | 客戶端A | 客戶端B |
---|---|---|
t1 | watch name | |
t2 | multi | |
t3 | set name 'cheng' | |
t4 | set name 'zhao' | |
t5 | exec |
在時(shí)間 T4 裙犹,客戶端 B 修改了 name 鍵的值尽狠, 當(dāng)客戶端 A 在 T5 執(zhí)行 EXEC 時(shí),Redis 會發(fā)現(xiàn) name 這個(gè)被監(jiān)視的鍵已經(jīng)被修改叶圃, 因此客戶端 A 的事務(wù)不會被執(zhí)行袄膏,而是直接返回失敗。
下文就來介紹 WATCH 的實(shí)現(xiàn)機(jī)制掺冠,并且看看事務(wù)系統(tǒng)是如何檢查某個(gè)被監(jiān)視的鍵是否被修改沉馆,從而保證事務(wù)的安全性的码党。
WATCH 命令的實(shí)現(xiàn)
在每個(gè)代表數(shù)據(jù)庫的 redis.h/redisDb 結(jié)構(gòu)類型中, 都保存了一個(gè) watched_keys 字典斥黑, 字典的鍵是這個(gè)數(shù)據(jù)庫被監(jiān)視的鍵揖盘, 而字典的值則是一個(gè)鏈表, 鏈表中保存了所有監(jiān)視這個(gè)鍵的客戶端锌奴。
比如說兽狭,以下字典就展示了一個(gè) watched_keys 字典的例子:
其中, 鍵 key1 正在被 client2 鹿蜀、 client5 和 client1 三個(gè)客戶端監(jiān)視箕慧, 其他一些鍵也分別被其他別的客戶端監(jiān)視著。
WATCH 命令的作用茴恰, 就是將當(dāng)前客戶端和要監(jiān)視的鍵在 watched_keys 中進(jìn)行關(guān)聯(lián)颠焦。
舉個(gè)例子, 如果當(dāng)前客戶端為 client10086 往枣, 那么當(dāng)客戶端執(zhí)行 WATCH key1 key2 時(shí)伐庭, 前面展示的 watched_keys 將被修改成這個(gè)樣子:
通過 watched_keys 字典, 如果程序想檢查某個(gè)鍵是否被監(jiān)視分冈, 那么它只要檢查字典中是否存在這個(gè)鍵即可似忧; 如果程序要獲取監(jiān)視某個(gè)鍵的所有客戶端, 那么只要取出鍵的值(一個(gè)鏈表)丈秩, 然后對鏈表進(jìn)行遍歷即可。
WATCH 的觸發(fā)
在任何對數(shù)據(jù)庫鍵空間(key space)進(jìn)行修改的命令成功執(zhí)行之后 (比如 FLUSHDB 淳衙、 SET 蘑秽、 DEL 、 LPUSH 箫攀、 SADD 肠牲、 ZREM ,諸如此類)靴跛, multi.c/touchWatchedKey函數(shù)都會被調(diào)用 —— 它檢查數(shù)據(jù)庫的 watched_keys 字典缀雳, 看是否有客戶端在監(jiān)視已經(jīng)被命令修改的鍵, 如果有的話梢睛, 程序?qū)⑺斜O(jiān)視這個(gè)/這些被修改鍵的客戶端的 REDIS_DIRTY_CAS
選項(xiàng)打開:
當(dāng)客戶端發(fā)送 EXEC 命令肥印、觸發(fā)事務(wù)執(zhí)行時(shí), 服務(wù)器會對客戶端的狀態(tài)進(jìn)行檢查:
- 如果客戶端的 REDIS_DIRTY_CAS 選項(xiàng)已經(jīng)被打開绝葡,那么說明被客戶端監(jiān)視的鍵至少有一個(gè)已經(jīng)被修改了深碱,事務(wù)的安全性已經(jīng)被破壞。服務(wù)器會放棄執(zhí)行這個(gè)事務(wù)藏畅,直接向客戶端返回空回復(fù)敷硅,表示事務(wù)執(zhí)行失敗。
- 如果 REDIS_DIRTY_CAS 選項(xiàng)沒有被打開,那么說明所有監(jiān)視鍵都安全绞蹦,服務(wù)器正式執(zhí)行事務(wù)力奋。
可以用一段偽代碼來表示這個(gè)檢查:
def check_safety_before_execute_trasaction():
if client.state & REDIS_DIRTY_CAS:
# 安全性已破壞,清除事務(wù)狀態(tài)
clear_transaction_state(client)
# 清空事務(wù)隊(duì)列
clear_transaction_queue(client)
# 返回空回復(fù)給客戶端
send_empty_reply(client)
else:
# 安全性完好幽七,執(zhí)行事務(wù)
execute_transaction()
舉個(gè)例子景殷,假設(shè)數(shù)據(jù)庫的 watched_keys 字典如下圖所示:
如果某個(gè)客戶端對 key1 進(jìn)行了修改(比如執(zhí)行 DEL key1 ), 那么所有監(jiān)視 key1 的客戶端锉走, 包括 client2 滨彻、 client5 和 client1 的 REDIS_DIRTY_CAS 選項(xiàng)都會被打開, 當(dāng)客戶端 client2 挪蹭、 client5 和 client1 執(zhí)行 EXEC 的時(shí)候亭饵, 它們的事務(wù)都會以失敗告終。
最后梁厉,當(dāng)一個(gè)客戶端結(jié)束它的事務(wù)時(shí)辜羊,無論事務(wù)是成功執(zhí)行,還是失敗词顾, watched_keys 字典中和這個(gè)客戶端相關(guān)的資料都會被清除八秃。
事務(wù)的 ACID 性質(zhì)
在傳統(tǒng)的關(guān)系式數(shù)據(jù)庫中,常常用 ACID 性質(zhì)來檢驗(yàn)事務(wù)功能的安全性肉盹。
Redis 事務(wù)保證了其中的一致性(C)和隔離性(I)昔驱,但并不保證原子性(A)和持久性(D)。
以下四小節(jié)是關(guān)于這四個(gè)性質(zhì)的詳細(xì)討論上忍。
原子性(Atomicity)
單個(gè) Redis 命令的執(zhí)行是原子性的骤肛,但 Redis 沒有在事務(wù)上增加任何維持原子性的機(jī)制,所以 Redis 事務(wù)的執(zhí)行并不是原子性的窍蓝。如果一個(gè)事務(wù)隊(duì)列中的所有命令都被成功地執(zhí)行腋颠,那么稱這個(gè)事務(wù)執(zhí)行成功。
另一方面吓笙,如果 Redis 服務(wù)器進(jìn)程在執(zhí)行事務(wù)的過程中被停止 —— 比如接到 KILL 信號淑玫、宿主機(jī)器停機(jī),等等面睛,那么事務(wù)執(zhí)行失敗絮蒿。當(dāng)事務(wù)失敗時(shí),Redis 也不會進(jìn)行任何的重試或者回滾動作叁鉴。
一致性(Consistency)
Redis 的一致性問題可以分為三部分來討論:入隊(duì)錯(cuò)誤歌径、執(zhí)行錯(cuò)誤、Redis 進(jìn)程被終結(jié)亲茅。
入隊(duì)錯(cuò)誤
在命令入隊(duì)的過程中回铛,如果客戶端向服務(wù)器發(fā)送了錯(cuò)誤的命令狗准,比如命令的參數(shù)數(shù)量不對,等等茵肃, 那么服務(wù)器將向客戶端返回一個(gè)出錯(cuò)信息腔长, 并且將客戶端的事務(wù)狀態(tài)設(shè)為 REDIS_DIRTY_EXEC 。
當(dāng)客戶端執(zhí)行 EXEC 命令時(shí)验残, Redis 會拒絕執(zhí)行狀態(tài)為 REDIS_DIRTY_EXEC的事務(wù)捞附, 并返回失敗信息。
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command
redis 127.0.0.1:6379> EXISTS key
QUEUED
redis 127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
因此您没,帶有不正確入隊(duì)命令的事務(wù)不會被執(zhí)行鸟召,也不會影響數(shù)據(jù)庫的一致性。
執(zhí)行錯(cuò)誤
如果命令在事務(wù)執(zhí)行的過程中發(fā)生錯(cuò)誤氨鹏,比如說欧募,對一個(gè)不同類型的 key 執(zhí)行了錯(cuò)誤的操作, 那么 Redis 只會將錯(cuò)誤包含在事務(wù)的結(jié)果中仆抵, 這不會引起事務(wù)中斷或整個(gè)失敗跟继,不會影響已執(zhí)行事務(wù)命令的結(jié)果,也不會影響后面要執(zhí)行的事務(wù)命令镣丑, 所以它對事務(wù)的一致性也沒有影響舔糖。
Redis 進(jìn)程被終結(jié)
如果 Redis 服務(wù)器進(jìn)程在執(zhí)行事務(wù)的過程中被其他進(jìn)程終結(jié),或者被管理員強(qiáng)制殺死莺匠,那么根據(jù) Redis 所使用的持久化模式金吗,可能有以下情況出現(xiàn):
- 內(nèi)存模式:如果 Redis 沒有采取任何持久化機(jī)制,那么重啟之后的數(shù)據(jù)庫總是空白的趣竣,所以數(shù)據(jù)總是一致的辽聊。
- RDB 模式:在執(zhí)行事務(wù)時(shí),Redis 不會中斷事務(wù)去執(zhí)行保存 RDB 的工作期贫,只有在事務(wù)執(zhí)行之后,保存 RDB 的工作才有可能開始异袄。所以當(dāng) RDB 模式下的 Redis 服務(wù)器進(jìn)程在事務(wù)中途被殺死時(shí)通砍,事務(wù)內(nèi)執(zhí)行的命令,不管成功了多少烤蜕,都不會被保存到 RDB 文件里封孙。恢復(fù)數(shù)據(jù)庫需要使用現(xiàn)有的 RDB 文件讽营,而這個(gè) RDB 文件的數(shù)據(jù)保存的是最近一次的數(shù)據(jù)庫快照(snapshot)虎忌,所以它的數(shù)據(jù)可能不是最新的,但只要 RDB 文件本身沒有因?yàn)槠渌麊栴}而出錯(cuò)橱鹏,那么還原后的數(shù)據(jù)庫就是一致的膜蠢。
- AOF 模式:因?yàn)楸4?AOF 文件的工作在后臺線程進(jìn)行堪藐,所以即使是在事務(wù)執(zhí)行的中途,保存 AOF 文件的工作也可以繼續(xù)進(jìn)行挑围,因此礁竞,根據(jù)事務(wù)語句是否被寫入并保存到 AOF 文件,有以下兩種情況發(fā)生:
1)如果事務(wù)語句未寫入到 AOF 文件杉辙,或 AOF 未被 SYNC 調(diào)用保存到磁盤模捂,那么當(dāng)進(jìn)程被殺死之后,Redis 可以根據(jù)最近一次成功保存到磁盤的 AOF 文件來還原數(shù)據(jù)庫蜘矢,只要 AOF 文件本身沒有因?yàn)槠渌麊栴}而出錯(cuò)狂男,那么還原后的數(shù)據(jù)庫總是一致的,但其中的數(shù)據(jù)不一定是最新的品腹。
2)如果事務(wù)的部分語句被寫入到 AOF 文件岖食,并且 AOF 文件被成功保存,那么不完整的事務(wù)執(zhí)行信息就會遺留在 AOF 文件里珍昨,當(dāng)重啟 Redis 時(shí)县耽,程序會檢測到 AOF 文件并不完整,Redis 會退出镣典,并報(bào)告錯(cuò)誤兔毙。需要使用 redis-check-aof 工具將部分成功的事務(wù)命令移除之后,才能再次啟動服務(wù)器兄春。還原之后的數(shù)據(jù)總是一致的澎剥,而且數(shù)據(jù)也是最新的(直到事務(wù)執(zhí)行之前為止)。
隔離性(Isolation)
Redis 是單進(jìn)程程序赶舆,并且它保證在執(zhí)行事務(wù)時(shí)哑姚,不會對事務(wù)進(jìn)行中斷,事務(wù)可以運(yùn)行直到執(zhí)行完所有事務(wù)隊(duì)列中的命令為止芜茵。因此叙量,Redis 的事務(wù)是總是帶有隔離性的。
持久性(Durability)
因?yàn)槭聞?wù)不過是用隊(duì)列包裹起了一組 Redis 命令九串,并沒有提供任何額外的持久性功能绞佩,所以事務(wù)的持久性由 Redis 所使用的持久化模式?jīng)Q定:
- 在單純的內(nèi)存模式下,事務(wù)肯定是不持久的猪钮。
- 在 RDB 模式下品山,服務(wù)器可能在事務(wù)執(zhí)行之后、RDB 文件更新之前的這段時(shí)間失敗烤低,所以 RDB 模式下的 Redis 事務(wù)也是不持久的肘交。
- 在 AOF 的“總是 SYNC ”模式下,事務(wù)的每條命令在執(zhí)行成功之后扑馁,都會立即調(diào)用 fsync 或 fdatasync 將事務(wù)數(shù)據(jù)寫入到 AOF 文件涯呻。但是凉驻,這種保存是由后臺線程進(jìn)行的,主線程不會阻塞直到保存成功魄懂,所以從命令執(zhí)行成功到數(shù)據(jù)保存到硬盤之間沿侈,還是有一段非常小的間隔,所以這種模式下的事務(wù)也是不持久的市栗。
其他 AOF 模式也和“總是 SYNC ”模式類似缀拭,所以它們都是不持久的。
總結(jié)
- 事務(wù)提供了一種將多個(gè)命令打包填帽,然后一次性蛛淋、有序地執(zhí)行的機(jī)制。
- 事務(wù)在執(zhí)行過程中不會被中斷篡腌,所有事務(wù)命令執(zhí)行完之后褐荷,事務(wù)才能結(jié)束。
- 多個(gè)命令會被入隊(duì)到事務(wù)隊(duì)列中嘹悼,然后按先進(jìn)先出(FIFO)的順序執(zhí)行叛甫。
- 帶 WATCH 命令的事務(wù)會將客戶端和被監(jiān)視的鍵在數(shù)據(jù)庫的 watched_keys 字典中進(jìn)行關(guān)聯(lián),當(dāng)鍵被修改時(shí)杨伙,程序會將所有監(jiān)視被修改鍵的客戶端的 REDIS_DIRTY_CAS 選項(xiàng)打開其监。
- 只有在客戶端的 REDIS_DIRTY_CAS 選項(xiàng)未被打開時(shí),才能執(zhí)行事務(wù)限匣,否則事務(wù)直接返回失敗抖苦。
- Redis 的事務(wù)保證了 ACID 中的一致性(C)和隔離性(I),但并不保證原子性(A)和持久性(D)米死。
轉(zhuǎn)載自http://redisbook.readthedocs.io/en/latest/feature/transaction.html#id11