Redis事務處理的實現(xiàn)示例
歡迎繼續(xù)跟隨《Redis新手指南:從入門到精通》專欄的步伐!在本文中,我們將探討Redis的事務處理機制。了解如何使用事務來保證一系列操作的原子性和一致性,這對于構建可靠的應用程序至關重要
1 什么是Redis事務??
? Redis 事務的本質(zhì)是一組命令的集合。事務支持一次執(zhí)行多個命令,一個事務中所有命令都會被序列化。在事務執(zhí)行過程,會按照順序列化執(zhí)行隊列中的命令,其他客戶端提交的命令請求不會插入到事務執(zhí)行命令序列中
總結說:redis 事務就是一次性、順序性、排他性的執(zhí)行一個隊列中的一系列命令
2 Redis事務相關命令和使用??
MULTI、EXEC、DISCARD 和 WATCH 是 Redis 事務相關的命令
- MULTI :開啟事務,redis會將后續(xù)的命令逐個放入隊列中,然后使用EXEC命令來原子化執(zhí)行這個命令系列。原子化執(zhí)行,它們要么全部執(zhí)行成功,要么全部回滾。
- EXEC:執(zhí)行事務中的所有操作命令
- DISCARD:取消事務,放棄執(zhí)行事務塊中的所有命令
- WATCH:監(jiān)視一個或多個key,如果事務在執(zhí)行前,這個key(或多個key)被其他命令修改,則事務被中斷,不會執(zhí)行事務中的任何命令
- UNWATCH:取消WATCH對所有key的監(jiān)視
1.標準的事務執(zhí)行
給k1、k2分別賦值,在事務中修改k1、k2,執(zhí)行事務后,查看k1、k2值都被修改
127.0.0.1:6379[1]> set key1 value1 OK 127.0.0.1:6379[1]> set key2 value2 OK 127.0.0.1:6379[1]> multi # 開啟事務 OK 127.0.0.1:6379[1](TX)> set key1 11 QUEUED 127.0.0.1:6379[1](TX)> set key2 22 QUEUED 127.0.0.1:6379[1](TX)> exec # 執(zhí)行事務中所有的操作命令 1) OK 2) OK 127.0.0.1:6379[1]> get key1 "11" 127.0.0.1:6379[1]> get key2 "22"
2.事務取消
127.0.0.1:6379[1]> multi OK 127.0.0.1:6379[1](TX)> set key1 111 QUEUED 127.0.0.1:6379[1](TX)> set key2 222 QUEUED 127.0.0.1:6379[1](TX)> DISCARD # 取消事務 OK 127.0.0.1:6379[1]> exec (error) ERR EXEC without MULTI
3.事務出現(xiàn)錯誤的處理
- 語法錯誤(編譯器錯誤)
在開啟事務后,修改k1值為11,k2值為22,但k2語法錯誤,最終導致事務提交失敗,k1、k2保留原值
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> set k2 v2 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set k1 11 QUEUED 127.0.0.1:6379> sets k2 22 (error) ERR unknown command `sets`, with args beginning with: `k2`, `22`, 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k1 "v1" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379>
- Redis類型錯誤(運行時錯誤)
? 在開啟事務后,修改k1值為11,k2值為22,但將k2的類型作為List,在運行時檢測類型錯誤,最終導致事務提交失敗,此時事務并沒有回滾,而是跳過錯誤命令繼續(xù)執(zhí)行, 結果k1值改變、k2保留原值
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> set k1 v2 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set k1 11 QUEUED 127.0.0.1:6379> lpush k2 22 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> get k1 "11" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379>
3 CAS操作實現(xiàn)樂觀鎖??
WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行為
- CAS? 樂觀鎖?Redis官方的例子幫你理解
被 WATCH 的鍵會被監(jiān)視,并會發(fā)覺這些鍵是否被改動過了。如果有至少一個被監(jiān)視的鍵在 EXEC 執(zhí)行之前被修改了, 那么整個事務都會被取消, EXEC 返回nil-reply來表示事務已經(jīng)失敗。
舉個例子, 假設我們需要原子性地為某個值進行增 1 操作(假設 INCR 不存在)。
首先我們可能會這樣做:
val = GET mykey val = val + 1 SET mykey $val
上面的這個實現(xiàn)在只有一個客戶端的時候可以執(zhí)行得很好。但是, 當多個客戶端同時對同一個鍵進行這樣的操作時, 就會產(chǎn)生競爭條件。舉個例子, 如果客戶端 A 和 B 都讀取了鍵原來的值, 比如 10 , 那么兩個客戶端都會將鍵的值設為 11 , 但正確的結果應該是 12 才對。
有了 WATCH ,我們就可以輕松地解決這類問題了:
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
使用上面的代碼, 如果在 WATCH 執(zhí)行之后, EXEC 執(zhí)行之前, 有其他客戶端修改了 mykey 的值, 那么當前客戶端的事務就會失敗。程序需要做的, 就是不斷重試這個操作, 直到?jīng)]有發(fā)生碰撞為止。
這種形式的鎖被稱作樂觀鎖, 它是一種非常強大的鎖機制。并且因為大多數(shù)情況下, 不同的客戶端會訪問不同的鍵, 碰撞的情況一般都很少, 所以通常并不需要進行重試。
- watch是如何監(jiān)視實現(xiàn)的呢?
Redis使用WATCH命令來決定事務是繼續(xù)執(zhí)行還是回滾,那就需要在MULTI之前使用WATCH來監(jiān)控某些鍵值對,然后使用MULTI命令來開啟事務,執(zhí)行對數(shù)據(jù)結構操作的各種命令,此時這些命令入隊列。
當使用EXEC執(zhí)行事務時,首先會比對WATCH所監(jiān)控的鍵值對,如果沒發(fā)生改變,它會執(zhí)行事務隊列中的命令,提交事務;如果發(fā)生變化,將不會執(zhí)行事務中的任何命令,同時事務回滾。當然無論是否回滾,Redis都會取消執(zhí)行事務前的WATCH命令

- watch 命令實現(xiàn)監(jiān)視
在事務開始前用WATCH監(jiān)控k1,之后修改k1為11,說明事務開始前k1值被改變,MULTI開始事務,修改k1值為12,k2為22,執(zhí)行EXEC,發(fā)回nil,說明事務回滾;查看下k1、k2的值都沒有被事務中的命令所改變。
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> set k2 v2 OK 127.0.0.1:6379> WATCH k1 OK 127.0.0.1:6379> set k1 11 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set k1 12 QUEUED 127.0.0.1:6379> set k2 22 QUEUED 127.0.0.1:6379> EXEC (nil) 127.0.0.1:6379> get k1 "11" 127.0.0.1:6379> get k2 "v2"
- UNWATCH取消監(jiān)視
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> set k2 v2 OK 127.0.0.1:6379> WATCH k1 OK 127.0.0.1:6379> set k1 11 OK 127.0.0.1:6379> UNWATCH OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set k1 12 QUEUED 127.0.0.1:6379> set k2 22 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 127.0.0.1:6379> get k1 "12" 127.0.0.1:6379> get k2 "22" 127.0.0.1:6379>
4 Redis事務執(zhí)行步驟??
通過上文命令執(zhí)行,很顯然Redis事務執(zhí)行是三個階段:
- 開啟:以MULTI開始一個事務
- 入隊:將多個命令入隊到事務中,接到這些命令并不會立即執(zhí)行,而是放到等待執(zhí)行的事務隊列里面
- 執(zhí)行:由EXEC命令觸發(fā)事務
當一個客戶端切換到事務狀態(tài)之后, 服務器會根據(jù)這個客戶端發(fā)來的不同命令執(zhí)行不同的操作:
- 如果客戶端發(fā)送的命令為 EXEC 、 DISCARD 、 WATCH 、 MULTI 四個命令的其中一個, 那么服務器立即執(zhí)行這個命令。
- 與此相反, 如果客戶端發(fā)送的命令是 EXEC 、 DISCARD 、 WATCH 、 MULTI 四個命令以外的其他命令, 那么服務器并不立即執(zhí)行這個命令, 而是將這個命令放入一個事務隊列里面, 然后向客戶端返回 QUEUED 回復。

更深入的理解
我們再通過幾個問題來深入理解Redis事務。
為什么 Redis 不支持回滾?
如果你有使用關系式數(shù)據(jù)庫的經(jīng)驗,那么“Redis 在事務失敗時不進行回滾,而是繼續(xù)執(zhí)行余下的命令”這種做法可能會讓你覺得有點奇怪。
以下是這種做法的優(yōu)點:
- Redis 命令只會因為錯誤的語法而失?。ú⑶疫@些問題不能在入隊時發(fā)現(xiàn)),或是命令用在了錯誤類型的鍵上面:這也就是說,從實用性的角度來說,失敗的命令是由編程錯誤造成的,而這些錯誤應該在開發(fā)的過程中被發(fā)現(xiàn),而不應該出現(xiàn)在生產(chǎn)環(huán)境中。
- 因為不需要對回滾進行支持,所以 Redis 的內(nèi)部可以保持簡單且快速。
有種觀點認為 Redis 處理事務的做法會產(chǎn)生 bug , 然而需要注意的是, 在通常情況下, 回滾并不能解決編程錯誤帶來的問題。舉個例子, 如果你本來想通過 INCR 命令將鍵的值加上 1 , 卻不小心加上了 2 , 又或者對錯誤類型的鍵執(zhí)行了 INCR , 回滾是沒有辦法處理這些情況的。
如何理解Redis與事務的ACID?
一般來說,事務有四個性質(zhì)稱為ACID,分別是原子性,一致性,隔離性和持久性
原子性atomicity
# 首先通過上文知道 運行期的錯誤是不會回滾的,很多文章由此說Redis事務違背原子性的;而官方文檔認為是遵從原子性的。Redis官方文檔給的理解是,**Redis的事務是原子性的:所有的命令,要么全部執(zhí)行,要么全部不執(zhí)行**。而不是完全成功
一致性consistency
# redis事務可以保證命令失敗的情況下得以回滾,數(shù)據(jù)能恢復到?jīng)]有執(zhí)行之前的樣子,是保證一致性的,除非redis進程意外終結
隔離性Isolation
# redis事務是嚴格遵守隔離性的,原因是redis是單進程單線程模式(v6.0之前),可以保證命令執(zhí)行過程中不會被其他客戶端命令打斷.但是,Redis不像其它結構化數(shù)據(jù)庫有隔離級別這種設計
持久性Durability
# **redis事務是不保證持久性的**,這是因為redis持久化策略中不管是RDB還是AOF都是異步執(zhí)行的,不保證持久性是出于對性能的考慮
5 Redis事務其它實現(xiàn)??
- 基于Lua腳本,Redis可以保證腳本內(nèi)的命令一次性、按順序地執(zhí)行,其同時也不提供事務運行錯誤的回滾,執(zhí)行過程中如果部分命令運行錯誤,剩下的命令還是會繼續(xù)運行完
- 基于中間標記變量,通過另外的標記變量來標識事務是否執(zhí)行完成,讀取數(shù)據(jù)時先讀取該標記變量判斷是否事務執(zhí)行完成。但這樣會需要額外寫代碼實現(xiàn),比較繁瑣
到此這篇關于Redis事務處理的實現(xiàn)示例的文章就介紹到這了,更多相關Redis事務處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring?Boot實戰(zhàn)解決高并發(fā)數(shù)據(jù)入庫之?Redis?緩存+MySQL?批量入庫問題
這篇文章主要介紹了Spring?Boot實戰(zhàn)解決高并發(fā)數(shù)據(jù)入庫之?Redis?緩存+MySQL?批量入庫問題,本文通過圖文實例相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02

