為了賬號安全,請及時綁定郵箱和手機立即綁定

Redis事務深入解析和使用

2019.10.28 21:21 701瀏覽

作為關系型數據庫中一項非常重要的基礎功能——事務,在 Redis 中是如何處理并使用的?

1.前言

事務指的是提供一種將多個命令打包,一次性按順序地執行的機制,并且保證服務器只有在執行完事務中的所有命令后,才會繼續處理此客戶端的其他命令。
事務也是其他關系型數據庫,所必備的一項非常重要的能力。以支付的場景為例,正常情況下只有正常消費完成之后,才會減去賬戶余額。但如果沒有事務的保障,可能會發生消費失敗了,但依舊會把賬戶的余額給扣減了,我想這種情況應該任何人都無法接受吧?所以事務是關系型數據庫中一項非常重要的基礎功能。

2.事務基本使用

事務在其他語言中一般執行過程分為三個階段:

  • 開啟事務——Begin Transaction
  • 執行業務代碼,提交事務——Common Transaction
  • 業務處理中出現異常,回滾事務——Rollback Transaction

以 Java 中的事務執行為例:

// 開啟事務  
begin();  
try {  
//......  
// 提交事務  
commit();  
} catch(Exception e) {  
// 回滾事務  
rollback();  
}  

Redis 中的事務從開始到結束也是要經歷三個階段:

  • 開啟事務
  • 命令入列
  • 執行事務/放棄事務

其中,開啟事務使用 multi 命令,事務執行使用 exec 命令,放棄事務使用 discard 命令。

1)開啟事務

multi 命令用于開啟事務,實現代碼如下:

> multi  
OK  

multi 命令可以讓客戶端從非事務模式狀態,變為事務模式狀態,如下圖所示:
image.png
注意:multi 命令不能嵌套使用,如果已經開啟了事務的情況下,再執行 multi 命令,會提示如下錯誤:

(error) ERR MULTI calls can not be nested

執行效果,如下代碼所示:

127.0.0.1:6379> multi  
OK  
127.0.0.1:6379> multi  
(error) ERR MULTI calls can not be nested  

當客戶端是非事務狀態時,使用 multi 命令,客戶端會返回結果 OK ,如果客戶端已經是事務狀態,再執行 multi 命令會 multi 命令不能嵌套的錯誤,但不會終止客戶端為事務的狀態,如下圖所示:
image.png

2)命令入列

客戶端進入事務狀態之后,執行的所有常規 Redis 操作命令(非觸發事務執行或放棄和導致入列異常的命令)會依次入列,命令入列成功后會返回 QUEUED ,如下代碼所示:

> multi  
OK  
> set k v  
QUEUED  
> get k  
QUEUED  

執行流程如下圖所示:
image.png
注意:命令會按照先進先出(FIFO)的順序出入列,也就是說事務會按照命令的入列順序,從前往后依次執行。

3)執行事務/放棄事務

執行事務的命令是 exec ,放棄事務的命令是 discard 。
執行事務示例代碼如下:

> multi  
OK  
> set k v2  
QUEUED  
> exec  
1) OK  
> get k  
"v2"  

放棄事務示例代碼如下:

> multi  
OK  
> set k v3  
QUEUED  
> discard  
OK  
> get k  
"v2"  

執行流程如下圖所示:
image.png

3.事務錯誤&回滾

事務執行中的錯誤分為以下三類:

  • 執行時才會出現的錯誤(簡稱:執行時錯誤);
  • 入列時錯誤,不會終止整個事務;
  • 入列時錯誤,會終止整個事務。

1)執行時錯誤

示例代碼如下:

> get k  
"v"  
> multi  
OK  
> set k v2  
QUEUED  
> expire k 10s  
QUEUED  
> exec  
1) OK  
2) (error) ERR value is not an integer or out of range  
> get k  
"v2"  

執行命令解釋如下圖所示:
image.png
從以上結果可以看出,即使事務隊列中某個命令在執行期間發生了錯誤,事務也會繼續執行,直到事務隊列中所有命令執行完成。

2)入列錯誤導致事務結束

示例代碼如下:

> get k  
"v"  
> multi  
OK  
> set k v2  
QUEUED  
> multi  
(error) ERR MULTI calls can not be nested  
> exec  
1) OK  
> get k  
"v2"  

執行命令解釋如下圖所示:
image.png
可以看出,重復執行 multi 會導致入列錯誤,但不會終止事務,最終查詢的結果是事務執行成功了。除了重復執行 multi 命令,還有在事務狀態下執行 watch 也是同樣的效果,下文會詳細講解關于 watch 的內容。

3)入列錯誤不會導致事務結束

示例代碼如下:

> get k  
"v2"  
> multi  
OK  
> set k v3  
QUEUED  
> set k  
(error) ERR wrong number of arguments for 'set' command  
> exec  
(error) EXECABORT Transaction discarded because of previous errors.  
> get k  
"v2"  

執行命令解釋如下圖所示:
image.png

4)為什么不支持事務回滾?

Redis 官方文檔的解釋如下:

If you have a relational databases background, the fact that Redis commands can fail during a transaction, but still Redis will execute the rest of the transaction instead of rolling back, may look odd to you.
However there are good opinions for this behavior:

  • Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
  • Redis is internally simplified and faster because it does not need the ability to roll back.

An argument against Redis point of view is that bugs happen, however it should be noted that in general the roll back does not save you from programming errors. For instance if a query increments a key by 2 instead of 1, or increments the wrong key, there is no way for a rollback mechanism to help. Given that no one can save the programmer from his or her errors, and that the kind of errors required for a Redis command to fail are unlikely to enter in production, we selected the simpler and faster approach of not supporting roll backs on errors.

大概的意思是,作者不支持事務回滾的原因有以下兩個:

  • 他認為 Redis 事務的執行時,錯誤通常都是編程錯誤造成的,這種錯誤通常只會出現在開發環境中,而很少會在實際的生產環境中出現,所以他認為沒有必要為 Redis 開發事務回滾功能;
  • 不支持事務回滾是因為這種復雜的功能和 Redis 追求的簡單高效的設計主旨不符合。

這里不支持事務回滾,指的是不支持運行時錯誤的事務回滾。

4.監控

watch 命令用于客戶端并發情況下,為事務提供一個樂觀鎖(CAS,Check And Set),也就是可以用 watch 命令來監控一個或多個變量,如果在事務的過程中,某個監控項被修改了,那么整個事務就會終止執行。
watch 基本語法如下:

watch key [key …]

watch 示例代碼如下:

> watch k  
OK  
> multi  
OK  
> set k v2  
QUEUED  
> exec  
(nil)  
> get k  
"v"  

從以上命令可以看出,如果 exec 返回的結果是 nil 時,表示 watch 監控的對象在事務執行的過程中被修改了。從 get k 的結果也可以看出,在事務中設置的值 set k v2 并未正常執行。
執行流程如下圖所示:
image.png
注意watch 命令只能在客戶端開啟事務之前執行,在事務中執行 watch 命令會引發錯誤,但不會造成整個事務失敗,如下代碼所示:

> multi  
OK  
> set k v3  
QUEUED  
> watch k  
(error) ERR WATCH inside MULTI is not allowed  
> exec  
1) OK  
> get k  
"v3"  

執行命令解釋如下圖所示:
image.png
unwatch 命令用于清除所有之前監控的所有對象(鍵值對)。
unwatch 示例如下所示:

> set k v  
OK  
> watch k  
OK  
> multi  
OK  
> unwatch  
QUEUED  
> set k v2  
QUEUED  
> exec  
1) OK  
2) OK  
> get k  
"v2"  

可以看出,即使在事務的執行過程中,k 值被修改了,因為調用了 unwatch 命令,整個事務依然會順利執行。

5.事務在程序中使用

以下是事務在 Java 中的使用,代碼如下:

import redis.clients.jedis.Jedis;  
import redis.clients.jedis.Transaction;  
  
public class TransactionExample {  
public static void main(String[] args) {  
// 創建 Redis 連接  
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx", 6379);  
// 設置 Redis 密碼  
jedis.auth("xxx");  
// 設置鍵值  
jedis.set("k", "v");  
// 開啟監視 watch  
jedis.watch("k");  
// 開始事務  
Transaction tx = jedis.multi();  
// 命令入列  
tx.set("k", "v2");  
// 執行事務  
tx.exec();  
System.out.println(jedis.get("k"));  
jedis.close();  
}  
}  

6.小結

事務為多個命令提供一次性按順序執行的機制,與 Redis 事務相關的命令有以下五個:

  • multi:開啟事務
  • exec:執行事務
  • discard:丟棄事務
  • watch:為事務提供樂觀鎖實現
  • unwatch:取消監控(取消事務中的樂觀鎖)

正常情況下 Redis 事務分為三個階段:開啟事務、命令入列、執行事務。Redis 事務并不支持運行時錯誤的事務回滾,但在某些入列錯誤,如 set key 或者是 watch 監控項被修改時,提供整個事務回滾的功能。

7.思考題

Redis 事務中如何解決并發修改的問題?Redis 支持事務回滾嗎?使用 Redis 事務時會出現哪三種錯誤?這三種錯誤對事務有何影響?只有高手才能答對的問題,你能答上來幾個?

8.參考&鳴謝

··················
歡迎關注專欄:
《設計模式深度解析34講》

點擊查看更多內容

本文原創發布于慕課網 ,轉載請注明出處,謝謝合作

4人點贊

若覺得本文不錯,就分享一下吧!

評論

相關文章推薦

正在加載中
意見反饋 幫助中心 APP下載
官方微信

舉報

0/150
提交
取消
开奖号码