1. 程式人生 > 程式設計 >Redis事務深入解析和使用

Redis事務深入解析和使用

作為關係型資料庫中一項非常重要的基礎功能——事務,在 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.參考&鳴謝

redis.io/topics/tran…

redisbook.readthedocs.io/en/latest/f…

關注下方二維碼,訂閱更多精彩內容