ホームページ >データベース >Redis >Redis のトランザクションについて話しましょう: トランザクション モード、Lua スクリプト

Redis のトランザクションについて話しましょう: トランザクション モード、Lua スクリプト

青灯夜游
青灯夜游転載
2023-04-10 19:29:361599ブラウズ

この記事では、Redis トランザクションを徹底的に理解し、Redis トランザクションの 2 つのモード (トランザクション モードと Lua スクリプト) を比較します。

正確には、Redis トランザクションには トランザクション モード Lua スクリプト の 2 つのモードが含まれています。

最初に結論について話しましょう:

Redis のトランザクション モードには次の特徴があります:

  • 分離性は保証されますが、
  • 耐久性は保証されません。保証されています;
  • ある程度の原子性はありますが、ロールバックはサポートされていません;
  • 一貫性の概念は異なります。一貫性の中核は制約のセマンティクスであると想定されており、 Redis トランザクションは一貫性を保証できます。

しかし、Lua スクリプトには、より実用的なシナリオがあります。これはトランザクションの別の形式であり、ある程度の原子性がありますが、スクリプトがエラーを報告すると、トランザクションはロールバックされません。 Lua スクリプトは分離を確保し、前のステップの結果に応じて後続のステップを完全にサポートできます。 [関連する推奨事項: Redis ビデオ チュートリアル ]

Lua スクリプト モードは、分散ロック、遅延キュー、赤い封筒の取得、その他のシナリオなど、ほぼどこでも使用できます。

1 トランザクションの原則

Redis トランザクションには次のコマンドが含まれます:

シリアル番号 コマンドと説明
1 MULTI トランザクション ブロックの開始をマークします。
2 EXEC は、トランザクション ブロック内のすべてのコマンドを実行します。
3 DISCARD はトランザクションをキャンセルし、トランザクション ブロック内のすべてのコマンドの実行を放棄します。
4 WATCH key [key ...] トランザクションが実行される前に、この (または複数の) キーが他のコマンドによって使用されているかどうかを監視します。変更された場合、トランザクションは中断されます。
5 UNWATCH WATCH コマンドによるすべてのキーの監視を解除します。

トランザクションは 3 つの段階で構成されます:

  1. MULTI を使用してトランザクションが開かれ、このコマンドは、コマンドを実行しているクライアントを非トランザクション状態からトランザクション状態に切り替えることをマークします。
  2. コマンド キューに入った後、MULTI はトランザクションを開始します。クライアントのコマンドはすぐには実行されませんが、トランザクション キューに入れられます。
  3. トランザクションを実行するか、破棄します。 EXECコマンドを受信した場合はトランザクションキュー内のコマンドが実行され、DISCARDの場合はトランザクションが破棄されます。

次に、トランザクションの例を示します。

1
 redis> MULTI 
2
 OK
3
 redis> SET msg "hello world"
4
 QUEUED
5
 redis> GET msg
6
 QUEUED
7
 redis> EXEC
8
 1) OK
9
 1) hello world

ご質問がありますか?トランザクションの開始時に Redis キーを変更できますか?

トランザクションが EXEC コマンドを実行する前でも、Redis キーを変更できます

トランザクションを開始する前に、watch コマンドを使用して Redis キーを監視できます。トランザクションを実行する前にキーの値を変更すると、トランザクションの実行は失敗し、nil が返されます。

上記の例を通じて、watch コマンドは オプティミスティック ロックと同様の効果を達成できます。

2 トランザクションの ACID

2.1 アトミック性

アトミック性とは、トランザクションすべての操作を指します。は完了または未完了のいずれかであり、中間段階で終了することはありません。トランザクションの実行中にエラーが発生した場合は、トランザクションがまったく実行されなかったかのように、トランザクションが開始される前の状態にロールバックされます。

最初の例:

EXEC コマンドを実行する前に、クライアントから送信された運用コマンドが構文エラーまたは存在しないコマンドなど、間違っています。

1
 redis> MULTI
2
 OK
3
 redis> SET msg "other msg"
4
 QUEUED
5
 redis> wrongcommand  ### 故意写错误的命令
6
 (error) ERR unknown command 'wrongcommand' 
7
 redis> EXEC
8
 (error) EXECABORT Transaction discarded because of previous errors.
9
 redis> GET msg
10
 "hello world"

この例では、存在しないコマンドを使用したため、キューが失敗し、トランザクション全体が実行されません。

2 番目の例:

トランザクション操作がキューに登録された場合、コマンドと操作のデータ型が一致しません。キューへの登録は正常ですが、EXEC コマンドの実行は異常です。

1
 redis> MULTI  
2
 OK
3
 redis> SET msg "other msg"
4
 QUEUED
5
 redis> SET mystring "I am a string"
6
 QUEUED
7
 redis> HMSET mystring name  "test"
8
 QUEUED
9
 redis> SET msg "after"
10
 QUEUED
11
 redis> EXEC
12
 1) OK
13
 2) OK
14
 3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
15
 4) OK
16
 redis> GET msg
17
 "after"

この例では、Redis が EXEC コマンドを実行するときにエラーが発生しても、Redis は他のコマンドの実行を終了せず、コマンドの実行が失敗したためにトランザクションはロールバックされません。

要約すると、Redis トランザクションのアトミック性についての私の理解は次のとおりです。

    コマンドがキューに追加されると、エラーが報告され、トランザクションの実行は行われません。アトミック性を確保するために放棄される;
  1. コマンド キューに参加するときは正常ですが、EXEC コマンドの実行後にエラーが報告され、アトミック性が保証されません;
つまり:

Redis トランザクションは、特定の条件下でのみ特定のアトミック性を持ちます

2.2 分離

データベースの分離とは、データベースが複数の同時トランザクションで同時にデータの読み取り、書き込み、変更を行えることを意味します。機能と分離により、複数のトランザクションが同時に実行される場合のクロス実行によるデータの不整合を防ぐことができます。

トランザクション分離は、次のようなさまざまなレベルに分割されます。

    コミットされていない読み取り (コミットされていない読み取り)
  • コミットされた読み取り (コミットされた読み取り)
  • 反復可能read
  • Serializable
まず第一に、Redis にはトランザクション分離レベルの概念がないことを明確にする必要があります。ここでは、Redis の分離について説明します。

同時シナリオで、トランザクションが相互の干渉を回避できるかどうか

トランザクションの実行は、

EXEC コマンドが実行される前 EXEC コマンドが実行された後 の 2 つの段階に分けて、別々に説明できます。

    EXEC コマンドを実行する前に
トランザクションの原則に関するセクションで、トランザクションが実行される前でも Redis キーを変更できることがわかりました。現時点では、

WATCH メカニズムを使用して、オプティミスティック ロックの効果を実現できます。

    EXEC コマンドの実行後
Redis はシングルスレッド実行コマンドであるため、EXEC コマンドの実行後、Redis はコマンド内のすべてのコマンドがキューが実行されます。これにより、トランザクションの分離が保証されます。

2.3 永続性

データベースの永続性とは、トランザクションの完了後、データの変更は永続的であることを意味します。システムが失敗しても失われることはありません。

Redis データが永続化されるかどうかは、Redis の永続化構成モードによって異なります。

    RDB または AOF が構成されていない場合、トランザクションの耐久性は保証できません。
  1. RDB モードが使用されている場合は、トランザクションの実行後、次の RDB スナップショットが実行されるまでの間, if インスタンスがクラッシュした場合、トランザクションの耐久性は保証できません;
  2. AOF モードが使用されます; AOF モードの 3 つの構成オプション (no および Everysec) では、データ損失が発生します。 always はトランザクションの耐久性を保証できますが、パフォーマンスが低すぎるため、実稼働環境での使用は通常推奨されません。
要約すると、

redis トランザクションの耐久性は保証できません

2.4 一貫性

一貫性の概念は常に混乱を招きます。私が検索した情報では、2 つの異なる定義がありました。

  1. Wikipedia

まず Wikipedia で一貫性の定義を見てみましょう:

一貫性とは、トランザクションがデータベースを 1 つの有効な状態からのみ取得できることを保証します。データベースに書き込まれるすべてのデータは、制約、カスケード、トリガー、およびそれらの組み合わせを含むすべての定義されたルールに従って有効である必要があります。これにより、不正なトランザクションによるデータベースの破損が防止されますが、トランザクションが正常に実行されることは保証されません。参照整合性により、主キーと外部キーの関係が保証されます。

このテキストでは、一貫性の核心は「constraint」、「 に書き込まれるすべてのデータ」です。データベースは、定義されたすべてのルールに従って有効である必要があります」。

制約を理解するにはどうすればよいですか?以下は、Zhihu の質問 「データベースの内部一貫性と外部一貫性を理解する方法 」からの引用です。Ant Financial の OceanBase R&D 専門家である Han Fusheng が回答しました。

「制約」ユーザーは、データがこの制約またはその制約に従う必要があることをデータベースに伝えます。データが変更されると、データベースはデータが制約を満たしているかどうかを確認します。制約が満たされなくなった場合、変更操作は実行されません。

リレーショナル データベースで最も一般的な 2 つのタイプの制約は、「一意制約」と「整合性制約」です。テーブルに定義された主キーと一意キーにより、指定されたデータ項目が決して繰り返されないことが保証されます。定義された参照整合性により、異なるテーブル内の同じ属性の一貫性も保証されます。

「ACID の一貫性」は非常に使いやすいため、ほとんどのユーザーの血液に溶け込んでいます。ユーザーはテーブルを設計するときに必要な制約を意識的に追加します。データベースもこの制約を厳密に適用します。

したがって、 トランザクションの一貫性は事前定義された制約に関連しており、その制約によって一貫性が確保されることが保証されます。

この文を詳しく見てみましょう:

これにより、不正なトランザクションによるデータベースの破損は防止されますが、トランザクションが正しいことは保証されません

これを書いてもまだ少し混乱している人もいるかもしれませんが、古典的な

transfer のケースを考えてみましょう。

トランザクションを開始します。Zhang San と Li Si の口座の初期残高は両方とも 1,000 元で、残高フィールドには制限はありません。張三は李斯に1200元を送金した。 Zhang San の残高は -200 に更新され、Li Si の残高は 2200 に更新されます。

実際のシナリオではユーザー残高を 0 未満にすることはできないため、アプリケーション レベルから見ると、このトランザクションは明らかに違法ですが、データベースの制約に完全に従っているため、データベース レベルから見ると、このトランザクションはそれでも一貫性は保証されています。

Redis のトランザクションの一貫性とは、Redis トランザクションが実行中にデータベースの制約に従い、違法または無効なエラー データが含まれていないことを意味します。

3 つの例外シナリオについてそれぞれ説明します。

  1. EXEC コマンドを実行する前に、クライアントから送信された操作コマンドが間違っており、トランザクションは終了し、データは残っています。一貫性;

  2. EXEC コマンドを実行した後、コマンドと操作のデータ型が一致しません。間違ったコマンドについてはエラーが報告されますが、トランザクションは終了しません。間違ったコマンドを実行しても、実行は継続されます。正しいコマンドは正常に実行され、間違ったコマンドはエラーを報告します。この観点から見ると、データの一貫性も維持できます。

  3. トランザクションの実行中に、Redis サービスがダウンします。 。ここでは、サービス構成の永続モードを考慮する必要があります。

      永続性のないメモリ モード: サービスの再起動後、データベースはデータを維持しないため、データの一貫性が維持されます;
    • RDB / AOF モード: サービスの再起動後、Redis Restoreデータは RDB/AOF ファイル経由で保存され、データベースは一貫した状態に復元されます。
要約すると、

一貫性の核心は制約であるというセマンティクスに基づいて、Redis トランザクションは一貫性を保証できます

    「データ集約型アプリケーションの設計」
この本は、分散システムを始めるための魔法の本です。 ACID についてはトランザクションの章に説明があります:

原子性、分離性、耐久性はデータベースのプロパティですが、(ACID の意味での) 一貫性はデータベースのプロパティです。アプリケーションのプロパティ。アプリケーションは一貫性を達成するためにデータベースのアトミック性と分離プロパティに依存する場合がありますが、それはデータベースだけで決まるわけではありません。したがって、文字 C は実際には ACID に属しません。

原子性、分離性、耐久性はデータベースの特性ですが、一貫性 (ACID の意味で) はアプリケーションの特性です。アプリケーションは一貫性を達成するためにデータベースの原子性と分離特性に依存する場合がありますが、これはデータベースだけに依存するわけではありません。したがって、文字 C は ACID に属しません。

多くの場合、私たちが苦労している一貫性は、実際には

現実世界との整合性を指します. 現実世界における一貫性は、物事の究極の目標です。

現実世界で一貫性を実現するには、次の点を満たす必要があります:

  1. アトミック性、耐久性、分離性を保証します。これらの特性が保証できない場合、トランザクションの一貫性は保証できません。
  2. 文字列の長さなど、データベース自体の制約を超えることはできません。列制限や一意性制限、
  3. ビジネス レベルも保証する必要があります。

2.5 トランザクションの特性

通常、Redis はインメモリ データベースと呼ばれており、従来のリレーショナル データベースとは異なります。より高いパフォーマンスとより速い書き込み速度を提供するために、設計および実装レベルでいくつかのバランスが取られており、トランザクションの ACID を完全にはサポートできません。

Redis トランザクションには次の特性があります:

  • は分離を保証します;
  • は耐久性を保証できません;
  • はある程度の原子性を持ちますが、ロールバックは可能ですはサポートされていません;
  • 一貫性の概念が異なります。一貫性の核心は制約であるというセマンティクスに基づいて、Redis トランザクションは一貫性を保証できると想定されています。

エンジニアリングの観点から、トランザクション操作の各ステップが前のステップから返された結果に依存する必要があると仮定すると、監視を通じてオプティミスティック ロックを実装する必要があります。

3 Lua スクリプト

3.1 はじめに

Lua は標準 C で書かれています。コードはシンプルで美しく、ほぼすべてのオペレーティング システムとプラットフォームでコンパイルして実行できます。 Lua スクリプトは C/C コードで簡単に呼び出すことができ、C/C 関数を順番に呼び出すこともできるため、Lua はアプリケーションで広く使用されています。

Lua スクリプトはゲーム分野で大きな話題を呼び、有名な「Westward Journey II」​​や「World of Warcraft」ではどちらも Lua スクリプトが広く使用されています。 Lua スクリプトは、OpenrestyKong など、Java バックエンド エンジニアが接触した API ゲートウェイで見ることができます。

Redis バージョン 2.6.0 以降、Redis の組み込み Lua インタープリターは Redis で Lua スクリプトを実行できるようになりました。

Lua スクリプトを使用する利点:

  • ネットワークのオーバーヘッドを削減します。複数のリクエストをスクリプトの形式で一度に送信し、ネットワーク遅延を短縮します。
  • アトミック操作。 Redis はスクリプト全体を全体として実行し、途中に他のコマンドが挿入されることはありません。 ######再利用。クライアントから送信されたスクリプトは Redis に永続的に保存され、他のクライアントはコードを使用せずにこのスクリプトを再利用して同じロジックを完了できます。
  • Redis Lua スクリプトの一般的なコマンド:

シリアル番号##1EVAL script numkeys key [key ...] arg [arg ...] Lua スクリプトを実行します。 2EVALSHA sha1 numkeys key [key ...] arg [arg ...] Lua スクリプトを実行します。 3SCRIPT EXISTS script [script ...] 指定したスクリプトがキャッシュに保存されているかどうかを確認します。 4SCRIPT FLUSH スクリプト キャッシュからすべてのスクリプトを削除します。 5SCRIPT KILL 現在実行中の Lua スクリプトを強制終了します。 6SCRIPT LOAD スクリプト スクリプト スクリプトをスクリプト キャッシュに追加しますが、スクリプトはすぐには実行されません。

3.2 EVAL 命令

命令格式:

1
 EVAL script numkeys key [key ...] arg [arg ...]

说明:

  • script是第一个参数,为 Lua 5.1脚本;
  • 第二个参数numkeys指定后续参数有几个 key;
  • key [key ...],是要操作的键,可以指定多个,在 Lua 脚本中通过KEYS[1], KEYS[2]获取;
  • arg [arg ...],参数,在 Lua 脚本中通过ARGV[1], ARGV[2]获取。

简单实例:

1
 redis> eval "return ARGV[1]" 0 100 
2
 "100"
3
 redis> eval "return {ARGV[1],ARGV[2]}" 0 100 101
4
 1) "100"
5
 2) "101"
6
 redis> eval "return {KEYS[1],KEYS[2],ARGV[1]}" 2 key1 key2 first second
7
 1) "key1"
8
 2) "key2"
9
 3) "first"
10
 4) "second"

下面演示下 Lua 如何调用 Redis 命令 ,通过redis.call()来执行了 Redis 命令 。

1
 redis> set mystring 'hello world'
2
 OK
3
 redis> get mystring
4
 "hello world"
5
 redis> EVAL "return redis.call('GET',KEYS[1])" 1 mystring
6
 "hello world"
7
 redis> EVAL "return redis.call('GET','mystring')" 0
8
 "hello world"

3.3 EVALSHA 命令

使用 EVAL 命令每次请求都需要传输 Lua 脚本 ,若 Lua 脚本过长,不仅会消耗网络带宽,而且也会对 Redis 的性能造成一定的影响。

思路是先将 Lua 脚本先缓存起来 , 返回给客户端 Lua 脚本的 sha1 摘要。 客户端存储脚本的 sha1 摘要 ,每次请求执行 EVALSHA 命令即可。

EVALSHA 命令基本语法如下:

1
 redis> EVALSHA sha1 numkeys key [key ...] arg [arg ...]

实例如下:

1
 redis> SCRIPT LOAD "return 'hello world'"
2
 "5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
3
 redis> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0
4
 "hello world"

4 事务 VS Lua 脚本

从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快

因为脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。

不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。

-- redis.io/

Lua 脚本是另一种形式的事务,他具备一定的原子性,但脚本报错的情况下,事务并不会回滚。Lua 脚本可以保证隔离性,而且可以完美的支持后面的步骤依赖前面步骤的结果

Lua 脚本模式的身影几乎无处不在,比如分布式锁、延迟队列、抢红包等场景。

不过在编写 Lua 脚本时,要注意如下两点:

  1. 为了避免 Redis 阻塞,Lua 脚本业务逻辑不能过于复杂和耗时;
  2. 仔细检查和测试 Lua 脚本 ,因为执行 Lua 脚本具备一定的原子性,不支持回滚。

更多编程相关知识,请访问:编程视频!!

コマンドと説明

以上がRedis のトランザクションについて話しましょう: トランザクション モード、Lua スクリプトの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。