Redis事務深刻解析和使用

做爲關係型數據庫中一項很是重要的基礎功能——事務,在 Redis 中是如何處理並使用的?html

1.前言

事務指的是提供一種將多個命令打包,一次性按順序地執行的機制,而且保證服務器只有在執行完事務中的全部命令後,纔會繼續處理此客戶端的其餘命令。java

事務也是其餘關係型數據庫,所必備的一項很是重要的能力。以支付的場景爲例,正常狀況下只有正常消費完成以後,纔會減去帳戶餘額。但若是沒有事務的保障,可能會發生消費失敗了,但依舊會把帳戶的餘額給扣減了,我想這種狀況應該任何人都沒法接受吧?因此事務是數據庫中一項很是重要的基礎功能。redis

2.事務基本使用

事務在其餘語言中,通常分爲如下三個階段:shell

  • 開啓事務——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 命令,會提示以下錯誤:app

(error) ERR MULTI calls can not be nestedide

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

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.參考&鳴謝

https://redis.io/topics/transactions

https://redisbook.readthedocs.io/en/latest/feature/transaction.html#id3

相關文章
相關標籤/搜索