1. キャッシュ侵入の概念
キャッシュ侵入: キャッシュ侵入とは、クライアントから要求されたデータがキャッシュまたはデータベースに存在しないことを意味するため、キャッシュは有効にならず、これらの要求はデータベースにヒットします。
2. ソリューション
2 つの一般的な解決策があります。
- 空のオブジェクトのキャッシュ
利点: シンプルな実装と簡単なメンテナンス
欠点:
追加のメモリ消費により、
短期的な不整合が発生する可能性があります - ブルームフィルター
メリット:メモリ使用量が少ない、冗長キーがない
デメリット:
実装が複雑、
誤判定の可能性あり
**空のオブジェクトをキャッシュするアイデアの分析: **クライアントが存在しないデータにアクセスする場合、最初に redis を要求しますが、この時点で redis にはデータがなく、この時点でデータベースにアクセスします。しかし、データベースにはデータがありません. キャッシュを介してデータベースに直接アクセスすると、データベースが実行できる同時実行性が redis ほど高くないことがわかります. 多数のリクエストがそのような非アクセスにアクセスするようになった場合同時に存在するデータ, これらのリクエストはすべてデータベースにアクセスします. 簡単な解決策は、このデータでもデータベースには存在しません, また、このデータをredisに保存するので、次にユーザーが非アクセスするようになります. -既存のデータの場合、データは redis で見つけることができ、データベースには入力されません
**Bloom filter: **Bloom filter は実際にハッシュのアイデアを使ってこの問題を解決します. 巨大なバイナリ配列を介して, ハッシュのアイデアを使って, クエリ対象の現在のデータが存在するかどうかを判断します. ブルーム フィルターが存在すると判断した場合,このリクエストはredisに行きます.この時点でredisのデータが期限切れになっていても,データはデータベースに存在している必要があります.データベースのデータをクエリした後,それをredisに入れます.
ブルームフィルターがデータが存在しないと判断したとしてそのまま返す
この方法の利点は, メモリ空間を節約することと, 誤判定があることです. 誤判定の理由はブルーム フィルタがハッシュのアイデアを使用しているためです. ハッシュのアイデアを使用している限り, ハッシュの競合が発生する可能性があります.
3. コーディングの実装
核となる考え方は次のとおりです。
元のロジックでは、このデータがmysqlに存在しないことが判明した場合、直接404を返すため、キャッシュ貫通の問題が発生します
In the current logic: if the data does not exist, we will not return 404, but will still write the data to Redis, and set the value to empty. クエリが再度開始されたときに、ヒットが見つかった場合、判断します値 null かどうか、null の場合は以前に書き込まれたデータであり、キャッシュ侵入データであることが証明されます。そうでない場合は、データが直接返されます。
/**
* 防止缓存穿透的查询方法
*
* @param keyPrefix
* @param id
* @param type json字符串反序列化的对象类型
* @param dbFallback 回调函数,用于查询数据库
* @param time
* @param unit
* @param <R>
* @param <ID>
* @return
*/
public <R,ID> R queryWithPassThrough(
String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
String key = keyPrefix + id;
// 1.从redis查询商铺缓存
String json = stringRedisTemplate.opsForValue().get(key);
// 2.判断是否存在
if (StrUtil.isNotBlank(json)) {
// 3.存在,直接返回
return JSONUtil.toBean(json, type);
}
// 判断命中的是否是空值
if (json != null) {
// 返回一个错误信息
return null;
}
// 4.不存在,根据id查询数据库
R r = dbFallback.apply(id);
// 5.不存在,返回错误
if (r == null) {
// 将空值写入redis
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
// 返回错误信息
return null;
}
// 6.存在,写入redis
this.set(key, r, time, unit);
return r;
}