インターネット技術の発展に伴い、データ処理の速度と効率がシステムのパフォーマンスを測定する重要な指標になりました。数あるデータ処理技術の中でも、キャッシュ技術はその優れたパフォーマンス最適化効果により欠かせないものとなっています。数あるキャッシュ テクノロジの中でも、Redis はその優れたパフォーマンスと豊富な機能により、大多数の開発者の支持を集めています。
Redis は、データベース、キャッシュ、メッセージング ミドルウェアとして使用できるオープン ソースのインメモリ データ構造ストレージ システムです。Redis は、文字列、ハッシュ、リスト、セット、順序付きセットなど、複数のタイプのデータ構造をサポートします。さらに、Redis は、データの永続化、トランザクション、パブリッシュとサブスクライブなどの一連の機能も提供します。
しかし、Redis を使用して効率的なキャッシュ メカニズムを実装するにはどうすればよいでしょうか? それが今日私たちが探求することです。この記事では、読み取りおよび書き込み戦略、有効期限戦略、削除戦略など、Redis のキャッシュ実装について詳しく紹介します。この記事を通じて、読者が Redis をよりよく理解して使用できるようになり、システムのパフォーマンスが向上することを願っています。
1. Redis キャッシュの実装とキャッシュ戦略
1.1. Redis キャッシュ アプリケーション
Redis キャッシュは、Redis の主要なアプリケーション シナリオです。ホットスポット データをメモリに保存すると、アプリケーションの読み取り速度が大幅に向上し、アプリケーションのパフォーマンスが向上します。
Redis をキャッシュとして使用する場合、通常は有効期限が設定されており、データの有効期限が切れると、Redis はメモリ領域を解放するためにデータを自動的に削除します。同時に、キャッシュ雪崩を防ぐために、有効期限は通常ランダム化されます。
さらに、Redis は、さまざまな複雑なキャッシュ要件を満たすために、文字列、リスト、セット、ハッシュ テーブルなどの豊富なデータ構造も提供します。たとえば、ハッシュ テーブルを使用してオブジェクトを保存したり、リストを使用して最も最近使用されていない (LRU) アルゴリズムなどを実装したりできます。
1.2. Redis キャッシュ戦略の分類
キャッシュ戦略とは、キャッシュを使用するときにキャッシュ内のデータを選択および管理する方法に関する一連のルールと方法を指します。キャッシュ戦略の目標は、データ アクセスの速度を可能な限り高め、元のデータ ソース (データベースなど) へのアクセスを減らし、それによってシステム パフォーマンスを向上させることです。
キャッシュ戦略には主に、読み取り戦略、書き込み戦略、読み込み戦略、有効期限戦略、および削除戦略の側面が含まれます。
1.3. Redis の一般的な読み取り戦略
一般的な Redis 読み取り戦略:
- リードスルー (読み取り戦略 - オンデマンドでロード): データを読み取るときに、キャッシュにないことが判明した場合、データベースからデータが読み取られ、読み取り後にデータがキャッシュに配置されます。この戦略では、キャッシュ内のデータがホット データであることを保証できますが、初めてデータを読み取るときに遅延が増加する可能性があります。
- 遅延ロード (読み取り戦略 - オンデマンドでロード): データは要求された場合にのみキャッシュにロードされます。データがキャッシュにない場合は、データベースからデータが読み取られてキャッシュに追加されます。
- プリロード (読み取り戦略 - プリロード): システムは、起動時または特定の時点で、必要になる可能性のあるデータをキャッシュにプリロードします。このようにして、データが要求されたときに、データベースにクエリを実行せずにキャッシュから直接データを取得できるため、データ アクセスの速度が向上します。プリロード戦略は、データ アクセス パターンが比較的固定されており、データ更新頻度が高くないシナリオに特に適しています。たとえば、一部の構成情報、静的コンテンツなどは、プリロード戦略の使用に非常に適しています。
実際の使用では、特定のアプリケーションのシナリオとニーズに基づいて、適切な読み取り戦略を選択できます。たとえば、データの更新頻度が低く、読み取り操作のパフォーマンス要件が高い場合は、Read Through 戦略の使用を選択できます。データの更新頻度が高い場合、またはキャッシュ領域を節約したい場合は、Read Through 戦略の使用を選択できます。遅延読み込み戦略を使用します。
1.4. Redis の一般的な記述戦略
Redis の一般的な記述戦略:
- ライトスルー (書き込み戦略 - 同期更新): データが更新されるたびに、データベースとキャッシュが同時に更新されます。この戦略の利点は、データの一貫性を維持できることですが、欠点は、各更新でデータベースとキャッシュの同時操作が必要になるため、パフォーマンスに影響することです。
- ライトバック (書き込み戦略 - 非同期更新): データが更新されるたびに、最初にキャッシュが更新され、次にデータベースが非同期で更新されます。この戦略の利点は、キャッシュの高いパフォーマンスに影響を与えず、クライアントに迅速に応答できることですが、欠点は、データがデータベースに非同期で書き戻される前に、キャッシュとデータベースのデータが一時的に不整合になることです。
- ライトアラウンド (書き込み戦略 - 直接更新): データを更新するときに、キャッシュを更新せずにデータベースを直接更新します。次回データが読み取られるとき、データがキャッシュにない場合はデータベースから読み取られます。この戦略は、書き込まれた後にほとんど読み取られないデータに適しています。
1.5. Redis の一般的な有効期限戦略
Redis の有効期限ポリシーは主に TTL (Time To Live) の設定によって実装されます。有効期限が設定された各キーについて、有効期限に達すると、Redis はキーを自動的に削除します。Redis は、期限切れのキーを処理するために、遅延削除と定期的な削除という 2 つの戦略を使用します。
- 遅延削除: キーにアクセスしたときのみ、Redis はキーの有効期限が切れているかどうかを確認し、有効期限が切れている場合はキーを削除します。この戦略の利点は、CPU 使用率を削減し、Redis のパフォーマンスに影響を与えるキーの有効期限が切れた瞬間の大量の削除操作を回避できることです。
-
定期的な削除: Redis は定期的にいくつかのキーをランダムにチェックし、キーの有効期限が切れていることが判明した場合、そのキーは削除されます。この戦略により、期限切れのキーを効果的にクリーンアップし、メモリ領域を解放できます。
ただし、Redis はすべてのキーをポーリングすることはできないため、すぐに削除されない期限切れのキーがいくつか存在する可能性があります。これが、Redis が遅延削除戦略を使用する必要がある理由です。つまり、キーがアクセスされた場合にのみ、Redis はキーの有効期限が切れているかどうかを確認し、有効期限が切れている場合はキーを削除します。
これら 2 つの戦略を組み合わせることで、Redis のパフォーマンスを確保しながら期限切れのキーを効果的に管理し、期限切れのキーが長期間メモリを占有するのを防ぐことができます。
1.6. Redis の排除戦略
では、期限切れのキーが定期的または遅延的に削除されない場合はどうすればよいでしょうか? ここで、Redis のメモリ削除戦略が機能します。
Redis のメモリ使用量が設定された上限に達した場合、新しいデータを保存する必要がある場合は、メモリ領域を解放するために古いデータの一部を削除する削除戦略を採用する必要があります。これはいわゆるメモリ消去メカニズムです。
maxmemory-policy
Redis は、次のような構成項目を通じて設定できるさまざまな排除戦略を提供します。
- noeviction: メモリが新しく書き込まれたデータを収容するのに十分でない場合、新しい書き込み操作はエラーを報告します。
- allkeys-lru: データセットから最も最近使用されていないデータを選択し、それを削除します。
- volatile-lru: 有効期限付きのデータセットから最も最近使用されていないデータを選択し、削除します。
- allkeys-random: キーをランダムに削除します。
- volatile-random: 有効期限が設定されたデータセットから削除するデータを任意に選択します。
- volatile-ttl: 有効期限が設定されているデータセットから、有効期限が近づいているデータを選択して削除します。
上記の戦略は、実際のアプリケーションのニーズとシナリオに基づいて選択できます。
2. Redis キャッシュに関する一般的な問題と解決策
2.1. Redis ホットキーの問題
いわゆるホット キーの問題とは、1 つまたはいくつかのキーが多数の同時リクエストによってアクセスされることで、トラフィックが集中しすぎて物理ネットワーク カードの上限に達し、Redis サーバーがクラッシュし、雪崩を引き起こします。
ホットキーの問題の解決策:
- 事前にホットキーを異なるサーバーに分散する: シャーディングとも呼ばれるこの方法では、ホットキー データを複数の Redis サーバーに分散して、単一サーバーへのアクセスの負荷を軽減できます。
- 2 次キャッシュ: アプリケーション サーバー内にローカル キャッシュを維持し、Redis がダウンしているときにローカル キャッシュからデータを取得できます。この方法によりシステムの可用性が向上しますが、ローカル キャッシュと Redis の間でデータの一貫性の問題が発生する可能性があることに注意してください。
さらに、電流制限やサーキット ブレーカーなどのトラフィック制御方法を使用して、多数のリクエストが同時にホットキーにアクセスするのを防ぎ、サーバーのダウンタイムを回避することも検討できます。
2.2. Redis キャッシュの浸透
キャッシュの侵入とは、キャッシュまたはデータベースに存在しないデータをクエリすることを指し、すべてのリクエストがデータベースにヒットし、データベースに過剰な負荷がかかります。
有効な解決策は次のとおりです。
- インターフェイスの検証: 要求されたパラメータを検証します。不正な要求は直接エラーを返し、データベースにアクセスするのを防ぎます。
- null 値をキャッシュする: データベース内でデータがクエリされなかった場合でも、null 値がキャッシュに書き込まれるため、次回同じデータがクエリされたときに、null 値は何も取得せずにキャッシュから直接取得されます。データベースに再度アクセスします。
- ブルーム フィルター: ブルーム フィルターは、要素がセット内にあるかどうかを判断するために使用できる確率的データ構造です。ブルーム フィルタにすべての可能なデータのキーを格納できます。データをクエリするときは、最初にキーがブルーム フィルタにあるかどうかを判断します。そうでない場合は、キーが存在しないことを直接返します。存在する場合は、キャッシュに移動して、データベースに問い合わせます。
ブルームフィルターの主な機能は次のとおりです。
- 要素が存在しないと判断する: ブルーム フィルターが要素が存在しないと判断した場合、この要素は存在してはなりません。
- 存在の判定: ブルームフィルターによって要素が存在すると判定された場合、その要素は存在する場合と存在しない可能性があり、一定の誤判定率が存在します。この誤検知率は、ブルーム フィルターのパラメーターを調整することで制御できます。
ブルーム フィルターはビット配列 (BitSet) と一連のハッシュ関数で構成され、スペース効率の高い確率的アルゴリズムおよびデータ構造であり、主にセット内に要素が存在するかどうかを判断するために使用されます。
HashMap と比較すると、大量のデータを処理する場合、ブルーム フィルターには明らかな利点があります。データ量が少ない場合、HashMap は問題を適切に処理でき、誤検知率はありません。ただし、データの量が増えると、特に保存するキーがより多くのスペースを占めると、ブルーム フィルターのスペースの利点が失われ始めます。
これらの方法により、キャッシュの侵入の問題を効果的に防止し、多数の無効なリクエストによってデータベースが過負荷になるのを防ぐことができます。
2.3. Redis キャッシュの内訳
キャッシュのブレークダウンとは、キャッシュ内でホット データの有効期限が切れる瞬間を指します。大量のリクエストがデータベースに直接ヒットし、データベースの負荷が急激に増加したり、データベースが崩壊したりする可能性があります。
有効な解決策は次のとおりです。
- ミューテックス ロックを追加する: 最初のリクエストがデータベースにクエリを実行してキャッシュを更新している間、他のリクエストは待機します。これにより、データベースにアクセスするリクエストは 1 つだけになり、データベースへの過度の負荷が回避されます。
- ホットスポット データは期限切れになりません: ホットスポット データが期限切れにならないように設定し、スケジュールされたタスクを通じてデータを非同期的に更新します。この方法では、ホットスポット データの突然の期限切れによって引き起こされるキャッシュの故障の問題を回避できますが、これによりデータが一定期間不整合になる可能性があることに注意する必要があり、ビジネス ニーズに基づいて許容できるかどうかを判断する必要があります。
これらの方法により、キャッシュの侵入の問題を効果的に防止し、大量のリクエストによるデータベースの過負荷から保護できます。
2.4. Redis キャッシュ雪崩
キャッシュなだれとは、大量のホット データが同時に期限切れになり、多数のリクエストがデータベースに直接アクセスすることを意味します。これにより、データベースの負荷が急激に増加したり、データベースが崩壊したりする可能性があります。
有効な解決策は次のとおりです。
- 有効期限を分散する: 各キーの有効期限にランダムな値を追加して、各キーの有効期限を分散し、多数のキーが同時に期限切れになるのを防ぎます。
- ミューテックス ロックを追加する: 同じキーに対して、データベースのクエリとキャッシュの更新を許可されるリクエストは 1 つだけであり、他のリクエストは待機します。これにより、データベースにアクセスするリクエストは 1 つだけになり、データベースへの過度の負荷が回避されます。
- ホットスポット データは期限切れになりません: ホットスポット データが期限切れにならないように設定し、スケジュールされたタスクを通じてデータを非同期的に更新します。この方法では、ホットスポット データの突然の期限切れによって引き起こされるキャッシュ雪崩の問題を回避できますが、これによりデータが一定期間不整合になる可能性があることに注意する必要があり、ビジネス ニーズに基づいて許容できるかどうかを判断する必要があります。
これらの方法により、キャッシュ雪崩の問題を効果的に防止し、大量のリクエストによる過負荷からデータベースを保護できます。
3. Java での Redis キャッシュの実装
3.1. Jedis の実装
以下は、Java を使用してリードスルー戦略とライトスルー戦略を実装する簡単な例です。
import redis.clients.jedis.Jedis;
public class Cache {
private Jedis jedis;
private Database db;
public Cache() {
this.jedis = new Jedis("localhost", 6379);
this.db = new Database();
}
// Read Through策略
public String readThrough(String key) {
// 先从缓存中读取数据
String value = jedis.get(key);
if (value == null) {
// 如果缓存中没有,那么从数据库中读取
value = db.getFromDatabase(key);
// 将从数据库中读取的数据放入缓存
jedis.set(key, value);
}
return value;
}
// Write Through策略
public void writeThrough(String key, String value) {
// 先将数据写入数据库
db.writeToDatabase(key, value);
// 然后将数据写入缓存
jedis.set(key, value);
}
}
class Database {
// 这里假设我们有一个数据库,具体实现省略
public String getFromDatabase(String key) {
// 从数据库中获取数据的代码
return "data";
}
public void writeToDatabase(String key, String value) {
// 将数据写入数据库的代码
}
}
この例では、まずCache
コンストラクターで Redis サーバーに接続し、データベース オブジェクトを初期化するクラスを作成します。
readThrough
次に、とという 2 つのメソッドを定義しました。writeThrough
これらはそれぞれ、リードスルー戦略とライトスルー戦略を実装します。
readThrough
このメソッドは、まずキャッシュからデータを読み取ろうとします (キャッシュにない場合)。次にデータベースから読み取り、データベースから読み取ったデータをキャッシュに置きます。writeThrough
このメソッドは、まずデータをデータベースに書き込み、次にデータをキャッシュに書き込みます。
Database
クラスは仮想のデータベース クラスであり、具体的な実装は省略されています。
3.2. SpringBoot の実装
Spring Boot では、springframework.data.redis
リードスルー戦略とライトスルー戦略を実装するために使用することもできます。簡単な例を次に示します。
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class DataService {
private final StringRedisTemplate redisTemplate;
public DataService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public String readThrough(String key) {
// 先从缓存中读取数据
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
// 如果缓存中没有,那么从数据库中读取
value = getFromDatabase(key);
// 将从数据库中读取的数据放入缓存
redisTemplate.opsForValue().set(key, value);
}
return value;
}
public void writeThrough(String key, String value) {
// 先将数据写入数据库
writeToDatabase(key, value);
// 然后将数据写入缓存
redisTemplate.opsForValue().set(key, value);
}
private String getFromDatabase(String key) {
// 从数据库中获取数据的代码
return "data";
}
private void writeToDatabase(String key, String value) {
// 将数据写入数据库的代码
}
}
この例では、まずDataService
Spring によって管理されるクラスを作成します。
readThrough
次に、とという 2 つのメソッドを定義しました。writeThrough
これらはそれぞれ、リードスルー戦略とライトスルー戦略を実装します。
readThrough
このメソッドは、まずキャッシュからデータを読み取ろうとします (キャッシュにない場合)。次にデータベースから読み取り、データベースから読み取ったデータをキャッシュに置きます。writeThrough
このメソッドは、まずデータをデータベースに書き込み、次にデータをキャッシュに書き込みます。
getFromDatabase
およびwriteToDatabase
メソッドはデータベースからデータを取得したり、データベースにデータを書き込むためのコードであり、具体的な実装は省略します。
注: 実際に使用する場合は、Spring Boot 設定ファイルに Redis 接続情報を設定する必要があります。