Bangumi Server 缓存策略揭秘:Redis 缓存仓储如何扛住高并发

Bangumi Server 缓存策略揭秘:Redis 缓存仓储如何扛住高并发 Bangumi Server 缓存策略揭秘Redis 缓存仓储如何扛住高并发【免费下载链接】serverAPI server for bgm.tv项目地址: https://gitcode.com/gh_mirrors/server17/serverBangumi Server 是动画评分社区 bgm.tv 的开源 API 服务端面对海量条目请求它的Redis 缓存策略是支撑高并发访问的核心秘密。本文将从源码出发为你揭秘这套缓存仓储模式Cache Repository的完整设计缓存键怎么命名、TTL 怎么定、批量读取怎么做、命中率怎么监控以及普通项目能直接借鉴的缓存优化经验。为什么 API 服务必须引入 Redis 缓存每次用户请求条目详情、标签列表背后都是一次 MySQL 查询。在高并发场景下热点条目如热门新番可能每秒被请求上千次如果全部打到数据库很容易把连接池打爆。Bangumi Server 的做法是在数据访问层Repository和数据库之间插入一层Redis 缓存让绝大多数读请求在内存中直接返回只有缓存未命中时才查询 MySQL。项目采用经典的Cache-Aside旁路缓存策略先读缓存命中直接返回未命中则查库并回填缓存。整个逻辑被封装成缓存仓储CachedRepo对上层调用方完全透明。三层缓存架构从 Redis 到内存的降级方案Bangumi Server 的缓存并非只有 Redis 一层而是精心设计的三层结构源码位于 internal/pkg/cache/ 目录缓存层实现方式适用场景源码Redis 缓存JSON 序列化后存 Redis支持 Get/Set/Del/MGet条目、标签等大流量数据redis.go内存缓存sync.Map直接存对象不序列化用户组权限等值空间小的数据memory.goNoop 缓存空实现读写都不做本地开发、单元测试noop.go其中Redis 缓存是扛高并发的主力。值得一提的是内存缓存虽然快但注释里明确警告过期的缓存不会自动回收所以只适合缓存值空间非常小的数据——这个取舍非常务实。一次请求如何命中 Redis 缓存读懂 Cache-Aside 读流程以条目Subject缓存为例internal/subject/cache_repo.go 中的Get方法展示了完整的读流程1. 根据条目 ID 拼接缓存键 2. 尝试从 Redis 读取GET key 3. 命中 → 直接返回零数据库开销 4. 未命中 → 查询 MySQL 仓储 5. 把结果写回 Redis并设置 TTL1 分钟 6. 返回数据这个模式有几个精妙之处回填失败不阻塞主流程写缓存出错只记录日志cant set response to cache不影响响应返回脏数据自动清理读取时发现 JSON 反序列化失败会主动删除该键并重新查库避免毒缓存持续污染装饰器模式cacheRepo包裹原始Repo业务代码无感知缓存可以随时开关。缓存键设计chii:repo:subject:123背后的命名智慧缓存键看似简单实则暗藏玄机。Bangumi Server 把所有缓存键集中在 internal/cachekey/cachekey.go 统一管理格式为chii:版本号:repo:资源类型:ID例如chii:v0:repo:subject:123。这种设计有三个好处前缀隔离chii:前缀加上版本号定义在 config/key.go保证同一 Redis 实例上不同环境、不同版本的数据互不冲突也方便一键清理类型统一subject、character、person、episode 各有独立命名空间语义清晰排查问题一目了然集中管理所有键的生成逻辑收拢在一个文件里改格式只需改一处从源头避免到处手写字符串导致的键不一致。不同数据不同 TTL缓存时间的精细化管理Bangumi Server 没有一刀切的 TTL而是根据数据变更频率精细分级这是缓存策略中最值得学习的一点缓存数据TTL 设置设计原因条目详情1 分钟条目信息变化较频繁短 TTL 保证新鲜度条目标签24 小时标签相对稳定长 TTL 提升命中率浏览列表第一页24 小时首页热度高、数据稳定浏览列表后续页1 小时翻页数据时效性要求更高浏览计数10 分钟计数变化快但又不想频繁查库代码位于 internal/subject/cache_repo.go 的browseCacheTTLFirst与browseCacheTTLOther两个常量以及 internal/tag/cache_repo.go 中的cacheTTL。批量读取 MGet一次网络往返缓存多个键高并发场景下Redis 的**网络往返RTT**往往是最大瓶颈。Bangumi Server 在标签缓存中实现了高效的批量读取见 internal/tag/cache_repo.go 的GetByIDs用MGet一次命令批量查询所有条目的标签缓存对比请求 ID 找出未命中的缺口missing只对缺失的 ID 查数据库避免重复查询已命中的数据把查到的结果逐条回填缓存。这种批量命中 缺口回源的策略把 N 次网络请求压缩成 1 次极大降低了缓存穿透的比例是典型的批量读取优化手法。连接池与高性能客户端底层性能保障缓存策略再好底层驱动不给力也是白搭。Bangumi Server 选用了高性能 Redis 客户端rueidisinternal/pkg/driver/redis.go并做了关键调优PipelineMultiplex: 2每个 Redis 节点建立 4 条连接用于管道复用请求自动管道化Pipelining多条命令合并在一条 TCP 连接上发送配合项目自研的泛型对象池 internal/pkg/generic/pool/pool.go复用对象减少 GC 压力。如何监控缓存命中率Prometheus 指标实战缓存不是加了就完事必须持续观测。Bangumi Server 为每个缓存仓储注册了两个 Prometheus 计数器源码见 internal/tag/cache_repo.gochii_query_count_total总查询次数chii_query_cached_count_total缓存命中的查询次数。用命中次数 ÷ 总次数就能算出缓存命中率。如果命中率突然下降往往意味着缓存键设计出了问题、TTL 设置不合理或是有大量新数据涌入导致缓存大面积失效。这套指标让运维人员能第一时间发现缓存健康度变化。缓存一致性TTL 过期与事件驱动的数据同步高并发缓存系统最头疼的问题是数据一致性。Bangumi Server 的选择是以 TTL 过期兜底 事件驱动同步大部分缓存不主动失效靠 TTL 自然过期实现简单、无并发删键的竞态问题数据变更通过canal 监听 MySQL binlog触发事件canal/ 目录驱动搜索引擎索引更新如 canal/on_subject.go而不是直接操作缓存Redis 缓存层的Del接口internal/pkg/cache/redis.go则为需要立即失效的场景留好了后门。小结普通项目能借鉴的 5 个缓存设计经验用缓存仓储模式封装把缓存逻辑收进 Repository 装饰器业务代码零侵入缓存键统一管理集中生成、带前缀和版本号避免键冲突和格式漂移TTL 按数据分级变更频繁的用短 TTL稳定数据用长 TTL兼顾新鲜度与命中率批量读取优先能用 MGet 就不要逐个 Get把 RTT 压到最低指标先行从一开始就埋点监控命中率让缓存效果看得见。Bangumi Server 的 Redis 缓存策略并不复杂但每个细节都经过高并发场景的检验。如果你想深入了解完整实现可以直接阅读 internal/subject/cache_repo.go 和 internal/pkg/cache/redis.go 这两个核心文件相信会对你的缓存设计大有启发。【免费下载链接】serverAPI server for bgm.tv项目地址: https://gitcode.com/gh_mirrors/server17/server创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考