記事ディレクトリ
1.Redisトランザクション
1.1 Redis トランザクションとは
Redis では、トランザクションは単一プロセスで実行できるコマンドの集合であり、これらのコマンドの原子性、一貫性、分離性、耐久性を確保します。
1.1.1 トランザクションの概要
Redis トランザクションは、次の 4 つの主要なコマンドによって管理されます。
注文 | 説明する |
---|---|
マルチ | トランザクションを開き、トランザクション ブロックの開始をマークします。 |
執行部 | トランザクション内のすべてのコマンドを実行します。 |
破棄 | トランザクションをキャンセルし、キューに入れられたすべてのコマンドを破棄します。 |
時計 | 楽観的ロックのために 1 つ以上のキーを監視します。 |
1.1.2 Redisのトランザクション特性
Redis トランザクションには次の主要な機能があります。
取引の特徴 | 説明する |
---|---|
原子性 | トランザクション内のすべてのコマンドは、まったく実行されるか、まったく実行されないかのどちらかです。これにより、トランザクションの実行中に、一部のコマンドが成功し、一部のコマンドが失敗することがなくなります。 |
一貫性 | トランザクション内のコマンドは追加された順序で実行され、他のクライアントからのコマンドによって中断されることはありません。これにより、トランザクション内の操作が希望の順序で実行され、同時操作の影響を受けなくなります。 |
分離 | トランザクションの実行中、トランザクションは分離されており、他のトランザクションの影響を受けません。他の同時トランザクションが実行されている場合でも、トランザクションが実行されてコミットされるまで、トランザクション内の操作は他のトランザクションから認識されません。 |
持続性 | トランザクションの実行後、データベースへの変更はディスクに保存されます。これにより、システム障害によってトランザクション内の操作が失われることがなくなり、データの耐久性が保証されます。 |
上記は、Redis トランザクションの基本的な概念と特性であり、Redis で実行されるトランザクションが信頼性が高く一貫した一連の操作であることを保証します。
上のグラフは、Redis トランザクションの主要な機能間の相互関係を表しています。これらの機能は相互にサポートし、連携して Redis トランザクションの信頼性と一貫性を保証します。アトミック性により、トランザクション内のすべての操作が成功または失敗することが保証されます。一貫性により、トランザクション内の操作が他の操作に干渉されることなく特定の順序で実行されることが保証されます。分離により、トランザクションは実行中に他のトランザクションから分離され、相互に干渉しなくなります。最後に、永続性により、トランザクション実行後に行われた変更が永続化され、システム障害によって失われることがなくなります。これらのプロパティが合わさって、Redis トランザクションの信頼性と安定性の基礎を形成します。
1.2 Redis トランザクションの使用
1.2.1 トランザクションの開始とコミット
Redis でトランザクションを使用するには、次の手順が必要です。
- コマンドを使用して
MULTI
トランザクションを開始します。 - トランザクション内で実行する必要があるコマンドを実行します。
- コマンドを使用してトランザクションをコミットし
EXEC
、トランザクション内のすべてのコマンドを実行します。
Java コードを使用した詳細なステップバイステップの例を次に示します。
// 创建与Redis服务器的连接
Jedis jedis = new Jedis("localhost", 6379);
// 开启事务
Transaction transaction = jedis.multi();
// 执行事务中的命令
transaction.set("key1", "value1");
transaction.set("key2", "value2");
// 提交事务并获取执行结果
List<Object> results = transaction.exec();
上記の例では、 2 つのコマンドtransaction.set("key1", "value1")
と がtransaction.set("key2", "value2")
トランザクション キューに追加され、transaction.exec()
呼び出されると、トランザクション内のすべてのコマンドが一緒に実行されます。と の間でエラーが発生した場合、トランザクションはキャンセルされ、コマンドは実行されませんMULTI
。EXEC
1.2.2 トランザクションコマンド
トランザクション内ではSET
、、、、などの通常の Redis コマンドを使用できますGET
。これらのコマンドは、コマンドが実行されるまでトランザクション キューに追加されます。HSET
ZADD
EXEC
1.2.3 トランザクション例
以下は、トランザクション内で実行される一般的な Redis コマンドを示す Java コードの例です。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class RedisTransactionCommandsExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 开启事务
Transaction transaction = jedis.multi();
// 执行事务中的命令
transaction.set("name", "Alice");
transaction.hset("user:1", "name", "Bob");
transaction.zadd("scores", 100, "Alice");
transaction.zadd("scores", 200, "Bob");
// 提交事务并获取执行结果
List<Object> results = transaction.exec();
// 打印执行结果
for (Object result : results) {
System.out.println("Result: " + result);
}
// 关闭连接
jedis.close();
}
}
上記の例では、SET
、 、HSET
およびZADD
コマンドが使用され、トランザクション キューに追加されます。実行するとtransaction.exec()
、トランザクション内のすべてのコマンドが一緒に実行されます。ここでの例は単純なデモンストレーションであり、必要に応じてコマンドを追加して、より複雑なトランザクションを構築できます。
2. Redis パイプライン
2.1 Redis パイプラインとは
Redis Pipeline (パイプライン) は、Redis の操作を最適化するテクノロジーであり、1 回の通信で複数のコマンドを Redis サーバーに送信できるようにすることで、通信のオーバーヘッドを大幅に削減し、パフォーマンスを向上させます。パイプラインは、各コマンドの応答を待たずに複数のコマンドを一度にサーバーに送信できるため、Redis はバッチ操作や大規模なデータの読み取りと書き込みをより効率的に処理できます。
次の図は、Redis パイプラインがどのように動作するかを示しています。
上図ではクライアント(Client)がRedisサーバー(Server)に複数のコマンドを送信しており、各コマンドはなどで表されていますCommand 1
。Command 2
これらのコマンドは、各コマンドの応答を待たずに、一度にサーバーに送信されます。サーバーはすべてのコマンドを実行した後、その結果をクライアントに一度に応答します。同時に、複数のコマンドを 1 つの通信にパッケージ化することで、各コマンドの通信オーバーヘッドが削減され、システムのパフォーマンスが向上する、Redis パイプラインの動作方法について説明します。
Redis パイプラインを使用する場合、クライアントはパイプライン オブジェクトを作成し、複数のコマンドをパイプラインに追加して、パイプライン内のコマンドを一度に実行します。最後に、クライアントはすべてのコマンドの実行結果を収集できます。
2.1.1 パイプラインの概要
Redis では、パイプラインは次のコマンドで管理されます。
注文 | 説明する |
---|---|
PIPELINE |
複数のコマンドを一度に送信するために使用されるパイプライン モードをオンにします。 |
MULTI |
パイプラインで一連のコマンドを実行するために使用されるトランザクション モードをオンにします。 |
EXEC |
パイプライン内のトランザクションをコミットし、実行して結果を返します。 |
パイプラインを使用すると、複数のコマンドを一度にサーバーに送信し、1回の通信ですべてのコマンドの実行結果を取得できるため、各コマンドの通信オーバーヘッドが削減され、システムのパフォーマンスが向上します。
2.1.2 Redis のパイプライン機能
Redis パイプラインを使用すると、次の利点があります。
- 通信オーバーヘッドの削減:通常のコマンド送信では、各コマンドごとに往復のネットワーク通信が必要ですが、パイプラインで複数のコマンドをパッケージ化して一度にサーバーに送信できるため、通信オーバーヘッドが大幅に削減されます。これは、ネットワーク遅延が長いシナリオでは特に重要であり、パフォーマンスが効果的に向上します。
- スループットの向上:パイプラインを使用すると、1 つの通信で複数のコマンドを実行できるため、単位時間あたりにより多くのコマンドを処理できます。これにより、バッチ データ処理や同時リクエスト処理など、多数のコマンドを処理する必要があるシナリオで Redis のスループットと応答性を効果的に向上させることができます。
2.2 Redis パイプラインの使用
2.2.1 パイプラインコマンド
以下は、Redis パイプラインを使用して複数のコマンドを実行し、パフォーマンスを向上させる方法を示す実践的な例です。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.util.List;
public class RedisPipelineExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 创建管道
Pipeline pipeline = jedis.pipelined();
// 向管道中添加命令
for (int i = 0; i < 10000; i++) {
pipeline.set("key" + i, "value" + i);
}
// 执行管道中的命令
List<Object> results = pipeline.syncAndReturnAll();
// 关闭连接
jedis.close();
}
}
上記の場合、ループを使用して 10,000 個のコマンドがパイプラインに追加されましたSET
。パイプを使用すると、すべてのコマンドを 1 つずつではなく 1 回の通信でサーバーに送信できるため、通信のオーバーヘッドが軽減され、パフォーマンスが向上します。
2.2.2 パイプライン最適化パフォーマンス
Redis パイプラインを使用すると、特に複数のコマンドをバッチ処理する必要がある場合にパフォーマンスが向上します。パイプラインの原理は、複数のコマンドを一度にサーバーに送信し、結果を一度に取得することです。これにより、通信の往復回数が削減され、スループットが大幅に向上します。
ただし、留意すべき点がいくつかあります。
- パイプラインはトランザクションをサポートしておらず、複数のコマンドのアトミックな実行を保証できません。
- パイプラインを使用する場合、コマンドが実行される順序とコマンドが追加される順序が一致しない可能性があるため、ビジネス要件に基づいて考慮する必要があります。
- パイプラインはすべてのシナリオでパフォーマンスの向上をもたらすわけではないため、実際の条件に基づいて評価する必要があります。
パイプラインを合理的に使用することで、高パフォーマンスのデータ処理における Redis の利点を最大化できます。
3. トランザクションとパイプライン: いつどちらを使用するか
3.1 適用可能な取引シナリオ
トランザクションは、一部のシナリオではアトミックで一貫した操作を保証でき、支払操作など、強い一貫性要件が必要なビジネス操作に特に適しています。
3.1.1 強力な一貫性の操作
トランザクションは、強い一貫性が必要な操作に適したメカニズムです。操作シーケンスで複数のコマンドをアトミックに実行する必要がある場合、トランザクションは、データの一貫性を維持するために、これらのコマンドがすべて実行されるか、まったく実行されないかを保証できます。
次の例では、ある口座の残高を引き落とし、同時に別の口座の残高を増やす必要がある銀行振込操作をシミュレートします。
Jedis jedis = new Jedis("localhost", 6379);
// 开启事务
Transaction transaction = jedis.multi();
// 扣减账户1余额
transaction.decrBy("account1", 100);
// 增加账户2余额
transaction.incrBy("account2", 100);
// 提交事务并获取执行结果
List<Object> results = transaction.exec();
// 关闭连接
jedis.close();
3.1.2 高い原子性要件
ビジネスで複数の操作がすべて成功するかすべて失敗する必要がある場合は、トランザクションを選択することをお勧めします。トランザクションは、トランザクション内の一連のコマンドがアトミック操作モードで実行されることを保証し、それによってデータの一貫性を維持します。
3.2 パイプラインの適用可能なシナリオ
パイプラインは、バッチ操作と高スループット要件を必要とするシナリオに適しています。複数のコマンドを一度にサーバーに送信することで、通信のオーバーヘッドが削減され、パフォーマンスが向上します。
3.2.1 バッチ操作
パイプラインを使用すると、一括操作を効率的に実行できます。たとえば、データベースに大量のデータを追加する必要がある場合、パイプを使用すると各コマンドの通信コストが削減され、作業の効率が大幅に向上します。
次の例は、一括セット操作にパイプラインを使用する方法を示しています。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.util.List;
public class RedisPipelineBatchExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
Pipeline pipeline = jedis.pipelined();
// 向管道中添加一批设置操作
for (int i = 0; i < 1000; i++) {
pipeline.set("key" + i, "value" + i);
}
// 执行管道中的命令
List<Object> results = pipeline.syncAndReturnAll();
// 关闭连接
jedis.close();
}
}
3.2.2 高スループット要件
高スループットが必要なシナリオでは、パイプラインによってパフォーマンスが大幅に向上します。複数のコマンドを短期間に実行する必要がある場合、これらのコマンドをパッケージ化してパイプラインを使用して送信できるため、通信の往復回数が削減されます。
大規模なデータ処理にパイプラインを使用する場合、特に高負荷条件下でのシステムの処理能力を向上させることができます。
4. ケーススタディ: データの一貫性の確保と注文支払いのパフォーマンスの最適化
4.1 シナリオの説明
オンライン ショッピング モールのシステムでは、注文の支払いプロセス中にデータの一貫性をどのように確保するか、支払いオペレーションのパフォーマンスをどのように最適化するかという重要な問題に直面しています。このブログでは、このシナリオについて詳しく説明し、解決策を提供します。
4.1.1 注文の支払い要件
ユーザーが注文した後、支払いと注文ステータスの一貫性を確保するために注文の支払い操作を実行する必要があります。
4.1.2 データの整合性要件
支払いが成功した後、データの一貫性を維持するために、注文ステータスを支払い済みに更新する必要があります。
4.1.3 高額な同時支払い
同時実行性が高い場合は、注文支払いのパフォーマンスとデータの一貫性を確保する必要があります。
4.2 Redis トランザクションを使用してデータ整合性の問題を解決する
4.2.1 トランザクションは注文の支払いを実現します
Jedis jedis = new Jedis("localhost", 6379);
Transaction transaction = jedis.multi();
// 扣除用户余额
transaction.decrBy("user:balance:1", orderAmount);
// 更新订单状态为已支付
transaction.hset("order:1", "status", "paid");
List<Object> results = transaction.exec();
上記の例では、Redis トランザクションを使用して、一連の操作でユーザー残高の減額と注文ステータスの更新が同時に行われるようにしています。トランザクション内のいずれかのステップが失敗した場合、トランザクション全体がロールバックされ、データの一貫性が確保されます。
4.2.2 トランザクションの一貫性の保証
トランザクションを使用すると、ユーザー残高と注文ステータスの一貫性 (成功または失敗が同時に保証される) を保証できます。このようにして、支払いと注文ステータスの正確性が保証され、潜在的なデータ不整合の問題を回避できます。
4.3 Redis パイプラインを使用して支払いパフォーマンスを最適化する
4.3.1 パイプライン一括支払い
Jedis jedis = new Jedis("localhost", 6379);
Pipeline pipeline = jedis.pipelined();
for (Order order : orders) {
pipeline.decrBy("user:balance:" + order.getUserId(), order.getAmount());
pipeline.hset("order:" + order.getId(), "status", "paid");
}
List<Object> results = pipeline.syncAndReturnAll();
この例では、Redis パイプラインを使用して、複数の注文の支払いをバッチで処理します。複数のコマンドを一度にサーバーに送信することで通信オーバーヘッドが削減され、支払い操作のパフォーマンスが大幅に向上します。
4.3.2 パイプラインのパフォーマンスの向上
パイプラインを使用すると、複数の決済操作を 1 つの通信にパッケージ化できるため、通信の往復回数が減り、決済パフォーマンスが向上します。特に同時支払いが多いシナリオでは、パイプラインによってサーバーの負荷が大幅に軽減され、システムの応答性が向上します。
5. トランザクションおよびパイプラインの制限事項と注意事項
5.1 取引の制限
WATCH コマンドや楽観的ロックの使用など、トランザクションを使用する場合は次の制限事項に注意する必要があります。
5.1.1 WATCHコマンド
トランザクションで WATCH コマンドを使用すると、1 つ以上のキーを監視できます。トランザクションの実行中に監視対象のキーが他のクライアントによって変更された場合、トランザクションは中断されます。これは、トランザクションの一貫性を確保し、競合状態を回避するためです。
良い例:
Jedis jedis = new Jedis("localhost", 6379);
Transaction transaction = jedis.multi();
// 监视键"balance"
transaction.watch("balance");
// ... 在此期间可能有其他客户端修改了"balance"键的值 ...
// 执行事务
List<Object> results = transaction.exec();
反論:
Jedis jedis = new Jedis("localhost", 6379);
Transaction transaction = jedis.multi();
// 监视键"balance"
transaction.watch("balance");
// ... 在此期间其他客户端修改了"balance"键的值 ...
// 尝试执行事务,但由于"balance"键被修改,事务会被中断
List<Object> results = transaction.exec();
5.1.2 楽観的ロック
同時更新を処理する場合は、楽観的ロックを使用できます。バージョン番号やタイムスタンプなどのメカニズムを使用して、コマンドを実行する前にデータが他のクライアントによって変更されているかどうかを確認し、同時実行性の競合を回避します。
良い例:
Jedis jedis = new Jedis("localhost", 6379);
// 获取当前版本号
long currentVersion = Long.parseLong(jedis.get("version"));
// 更新数据前检查版本号
if (currentVersion == Long.parseLong(jedis.get("version"))) {
Transaction transaction = jedis.multi();
transaction.set("data", "new value");
transaction.incr("version");
List<Object> results = transaction.exec();
} else {
// 数据已被其他客户端修改,需要处理冲突
}
5.2 配管上の注意事項
パイプラインを使用する場合は、シリアル化やパイプラインの慎重な使用など、次の点に注意する必要があります。
5.2.1 トランザクションをサポートしていません
パイプラインはトランザクションをサポートしていないため、パイプラインを通じてトランザクションの原子性と一貫性を実現することはできません。トランザクションのサポートが必要な場合は、Redis トランザクション メカニズムを使用する必要があります。
5.2.2 パイプの使用には注意してください
パイプラインによりパフォーマンスが向上しますが、すべてのシナリオでパフォーマンスが向上するわけではありません。場合によっては、パイプラインのシリアル特性により、特定のコマンドが他のコマンドの実行をブロックし、代わりにパフォーマンスを低下させる可能性があります。
良い例:
Jedis jedis = new Jedis("localhost", 6379);
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
pipeline.set("key" + i, "value" + i);
}
// 执行管道中的命令并获取结果
List<Object> results = pipeline.syncAndReturnAll();
反論:
Jedis jedis = new Jedis("localhost", 6379);
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 1000; i++) {
// 注意:此处执行了耗时的命令,可能阻塞其他命令的执行
pipeline.get("key" + i);
}
// 执行管道中的命令并获取结果
List<Object> results = pipeline.syncAndReturnAll();
6. まとめ
このブログでは、Redis のトランザクションとパイプラインのメカニズム、およびデータの一貫性の確保とパフォーマンスの最適化におけるそれらのアプリケーションについて詳しく説明します。詳細な説明とコード例を通じて、トランザクションとパイプラインの基本的な概念、特徴、使用方法、適用可能なシナリオを理解します。このブログの主な内容をまとめると以下のようになります。
最初の部分であるRedis トランザクション では、トランザクションの概念と特性を理解します。トランザクションにより、一連のコマンドの原子性、一貫性、分離性、耐久性が保証されます。MULTI、EXEC、DISCARD、および WATCH コマンドを使用して、トランザクションの開始、コミット、ロールバックを管理し、キーの変更を監視できます。トランザクションは、原子性と一貫性を必要とする操作、特に強い一貫性要件があるシナリオに適しています。
2 番目のパート「Redis パイプライン」では、パイプラインの概念と利点を深く理解しました。パイプラインを使用すると、複数のコマンドをサーバーに一度に送信できるため、通信オーバーヘッドが削減され、パフォーマンスが向上します。PIPELINE、MULTI、および EXEC コマンドを使用して、パイプラインの作成、コマンドの追加、パイプライン内のコマンドの実行を行うことができます。パイプラインは、高バッチ操作と高スループット要件を伴うシナリオに適しており、Redis のパフォーマンスを大幅に向上させることができます。
3 つ目、トランザクションとパイプライン: いつどの部分を使用するか、トランザクションとパイプラインの該当するシナリオを比較しました。トランザクションは、強力な一貫性操作と高い原子性要件を保証するシナリオに適しており、パイプラインはバッチ操作と高スループットのシナリオに適しています。例を通じて、一貫性とパフォーマンスのニーズを満たすために、ビジネス ニーズに基づいて適切なメカニズムを選択する方法を説明します。
パート4. ケーススタディ: データの一貫性の保証と注文支払いのパフォーマンスの最適化 では、これまでの知識を適用して実際的な問題を解決しました。トランザクションを使用して注文支払いのデータの一貫性を確保する方法と、パイプラインを使用して支払い操作のパフォーマンスを最適化する方法を示します。この事例は、実際のビジネスにおけるトランザクションとパイプラインの適用を完全に反映しています。
「5. トランザクションとパイプラインの制限と注意事項」では、トランザクションとパイプラインの制限と注意事項をいくつか指摘しました。トランザクションは WATCH コマンドとオプティミスティック ロックによって制限されますが、パイプラインはトランザクションをサポートしていないため、パイプラインを使用する場合はパフォーマンスへの影響を慎重に考慮する必要があります。
このブログを通じて、私たちは Redis のトランザクションとパイプラインのメカニズムについて詳しく説明し、それらが実際のアプリケーションでデータの一貫性を確保し、パフォーマンスを最適化する方法を学びました。一貫性を重視するか、パフォーマンスを追求するかにかかわらず、ビジネス ニーズに応じて適切なメカニズムを選択し、最良の結果を達成できます。