序文
私たちの日常の開発では、データベースを使用してデータを保存しています。通常、ほとんどのシステムタスクには同時実行性の高いシナリオがないため、問題はないと思われます。ショッピングモールなどの大量のデータのニーズに関しては、急いでいる商品の場合、またはホームページへの訪問数が多い場合、データベースのみを使用してデータを保存するシステムは、ディスク指向で低速のディスク読み取りおよび書き込み速度のため、パフォーマンスに重大な欠点があります。数千のリクエストが瞬時に届くため、システムは非常に短い時間で数千の読み取り操作と書き込み操作を完了する必要があり、データベースシステムの麻痺やサービスのダウンタイムなどの深刻な問題を簡単に引き起こす可能性があります。現時点では、Redisはこの問題を非常にうまく解決します。キャッシュは、データベースへの圧力を軽減するために追加された保護のレイヤーにすぎません。外部から必要なデータをキャッシュからクエリできない場合、データベースをクエリする必要がありますが、データベースはそれに従います。もう1つの問題は、キャッシュの浸透、キャッシュの内訳、キャッシュのなだれです。
1.キャッシュ侵入
特定のキーに対応するデータがデータベースに存在しません。このキーに対するリクエストをキャッシュから取得できないたびに、リクエストはデータベースに送られ、データベースを圧倒する可能性があります。たとえば、キャッシュやデータベースがない場合でも、存在しないユーザーIDを使用してユーザー情報を取得すると、ハッカーがこの脆弱性を利用して攻撃を行うと、データベースを圧倒する可能性があります。
2つの解決策があります。
解決策1:データベースに存在しないデータの場合、クエリによって返されたデータが空の場合(データが存在しないか、システムに障害があるかに関係なく)、空の結果をキャッシュし、デフォルト値Nullをキャッシュに設定します、リソースの占有を回避するために、設定の有効期限は非常に短く、5分以下です。これは比較的単純ですが、簡単に解読できます。たとえば、攻撃者がデータ形式を分析してデータベースに存在しないデータを繰り返し要求することはありません。このように、ソリューション1は無効と同等です。
public object getProductListNew(){
//设置失效时间
int cacheTime = 30;
String cacheKey = "product_list";
String cacheValue = CacheHelper.Get(cacheKey);
if (cacheValue != null) {
return cacheValue;
}
cacheValue = CacheHelper.Get(cacheKey);
if (cacheValue != null) {
return cacheValue;
} else {
//数据库查询不到,为空
cacheValue = GetProductListFromDB();
if (cacheValue == null) {
//如果发现为空,设置个默认值,也缓存起来
cacheValue = string.Empty;
}
CacheHelper.Add(cacheKey, cacheValue, cacheTime);
return cacheValue;
}
}
オプション2:いくつかのフィルタリングルールを設定できます。最も一般的なのは、ブルームフィルターを使用することです。その設計思想は、データベースクエリの前にデータをフィルタリングすることです。データが存在しないことが判明した場合、データベースのアクセス圧力を軽減するためにデータベースクエリは実行されません。
ブルームフィルターは、効率的な挿入とクエリを特徴とする一種の確率的データ構造であり、従来のリスト、セット、マップ、およびその他のデータ構造と比較して、「存在してはならない、または存在する可能性がある」と通知します。 Longフィルターはビット配列であり、より効率的で場所を取りませんが、返される結果は正確ではなく確率的であるという欠点があります。可能なすべてのデータを十分に大きなビットマップにハッシュします。存在してはならないデータはビットマップによってインターセプトされるため、基になるストレージシステムへのクエリのプレッシャーを回避できます。
値をブルームフィルターにマップする場合、複数の異なるハッシュ関数を使用して複数のハッシュ値を生成する必要があり、生成された各ハッシュ値はビット位置1を指します。たとえば、値 "zhangsan 」と、ハッシュ値を生成する3つの異なるハッシュ関数1、4、7
ここで、別の値「lisi」を保存します。ハッシュ関数が4、5、8を返す場合、グラフは次のようになります。
ブルームフィルターが特定のデータを記録するかどうかを判断する場合、ブルームフィルターは最初に同じハッシュ処理をデータに対して実行します。たとえば、「wangwu」のハッシュ関数は2、5、8を返します。その結果、ビット2の値が0であり、このビットに値がマッピングされていないことから、「wangwu」というデータは存在しないことが確実にわかります。
しかし同時に、「zhangsan」と「lisi」の両方のハッシュ関数がこのビットを返すため、4ビットが上書きされていることがわかります。次に、ブルームフィルターによって保存されたデータが増加し続けると、繰り返しの確率が増加し続けるため、特定のデータをフィルター処理するときに、3つのハッシュ値がすべてフィルターに記録されていることがわかった場合、他のデータのハッシュ値が結果に影響を与える可能性があるため、データがフィルターに含まれている可能性があることだけを示すことができますが、これは絶対に確実ではありません。これは、上記のブルームフィルターは "何かが存在してはならない、または存在する可能性があります。」3つの異なるハッシュ関数を使用して値を取得する理由については、3つのハッシュ値の1つが存在しない限り、データはフィルターに含まれていてはなりません。これを減らすことができます。ハッシュの衝突によるエラーの確率(2つのデータのハッシュ値は同じです)。
2.キャッシュの内訳
特定のキーが特定の時点で非常に同時にアクセスされる場合があります。これは非常に「ホット」なデータです。このとき、考慮する必要がある問題があります:キャッシュの「破壊」の問題です。
ミューテックスキーを使用する
業界で一般的な方法は、ミューテックスを使用することです。簡単に言うと、キャッシュが無効な場合(判定された値が空)、dbをすぐにロードするのではなく、最初に、キャッシュツール(Redis SETNXやMemcacheなど)の正常な操作の戻り値で特定の操作を使用します。 ADD)を使用してmutexキーを設定します。操作が正常に終了したら、load db操作を実行してキャッシュをリセットします。それ以外の場合は、get cacheメソッド全体を再試行します。
SETNXは "SET if Not eXists"の略であり、存在しない場合にのみ設定され、ロック効果を実現するために使用できます。
public String get(key) {
String value = redis.get(key);
if (value == null) {
//代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {
//代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else {
//这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}```
**三. 缓存雪崩**