布隆过滤器结合双重判定锁与空值缓存的处理链路是什么?
1 个回答
外界的声音都是参考,你不开心就不要参考。李青候## 作用 该链路用于防止“缓存穿透”和“缓存击穿”: - **布隆过滤器**:先拦截明确不存在的 key,避免无效请求访问缓存和数据库。 - **空值缓存**:对数据库确认不存在的数据写入短期空占位,后续请求直接命中空缓存。 - **singleflight 双重判定锁**:合并同一时刻对同一个 key 的并发请求,只让一个请求回源数据库,其他请求等待结果,避免热点 key 失效时大量并发打到 DB。 ## 原理 严格处理链路如下: 1. **先执行布隆过滤器判断** - 调用 `filter.Test(key)`。 - 如果返回 `false`,说明该 key 确定不存在,立即返回 `not found`。 - 此时不再查询 Redis,也不进入 singleflight 或数据库。 - 布隆过滤器必须放在 `cache.Get()` 之前,否则会浪费缓存查询,失去前置拦截意义。 2. **查询普通缓存** - `filter.Test(key)` 返回 `true` 后,再执行 `cache.Get(key)`。 - 如果命中真实数据,直接返回。 - 如果命中空值占位,也直接返回不存在。 3. **识别空值缓存** - 不建议直接缓存 `nil`,因为不同缓存库可能把它序列化成 `"<nil>"`、`NULL` 等形式,导致读出后无法正确判空。 - 应定义统一占位类型,例如 `type Null struct{}`。 - 数据库查不到数据时写入 `Null{}`,TTL 建议设置为 **1–5 秒**,避免合法数据稍后创建后仍被长期判定为不存在。 - 读取缓存后,如果发现值是 `Null{}`,立即返回不存在。 4. **缓存未命中后进入 singleflight** - 对同一个失效 key 的并发请求,通过 `singleflight.Do(key, loadFn)` 合并。 - 只有第一个请求执行 `loadFn` 回源数据库,其他并发请求等待同一个结果。 - singleflight 的 key 应与业务缓存 key 做隔离,例如加固定前缀;更重要的是,只允许通过布隆过滤器的 key 进入该阶段。 5. **在 `loadFn` 中回源数据库并回填缓存** - `loadFn` 内部必须完成完整副作用: - 查询数据库; - 判断数据是否存在; - 存在则写入真实缓存; - 不存在则写入短期空值缓存; - 返回查询结果。 - 数据库查询应设置超时时间,例如使用 `context.WithTimeout`,避免慢查询阻塞所有等待者。 - 写缓存失败不应导致流程 panic,应记录日志并继续返回本次数据库结果。 6. **最终刚性顺序** ```text filter.Test(key) ├─ false:直接返回不存在 └─ true ↓ cache.Get(key) ├─ 命中真实值:返回数据 ├─ 命中 Null 空占位:返回不存在 └─ 未命中 ↓ singleflight.Do(key, loadFn) ↓ 查询 DB ├─ 有数据:写真实缓存并返回 └─ 无数据:写短期 Null 空缓存并返回不存在 ``` ## 关键注意点 - 布隆过滤器的 `Add()` 与 `Test()` 使用的 key 格式必须完全一致,例如都使用 `[]byte("user:123")`。 - 布隆过滤器实例必须全局复用,并在服务接收流量前完成预热,不能每次请求重新创建。 - 布隆过滤器存在误判:返回 `true` 不代表一定存在,所以仍需依赖空值缓存和数据库确认。 - 删除数据后,布隆过滤器不方便删除对应标记,通常通过短 TTL 空值缓存和定期全量重建来收敛误判。 - 多实例部署时,如本地过滤器无法保证一致性,可使用 RedisBloom 或独立布隆服务。发布于 2026-10-27 01:00 •IP属地 河北举报评论收藏
公众号进入网站体验更佳

