这是一个带版本号的开源Go缓存系统,核心目标是绝对保证你读到的不是过期数据,同时保持高性能和可扩展性。
基于 Go 的 CAS 安全缓存,具有读取验证的单体、批量集验证、可插拔的提供程序/编解码器以及可选的共享代!
把它想象成一个非常严谨的图书馆管理员。
核心功能
1. CAS-safe (防脏读)
* 目的: 保证你从缓存里读到的数据和数据库里的完全一致,绝不会是旧数据。
* 怎么做的: 给每一份数据都发一个“版本号”。写缓存时,会检查这个版本号是不是最新的,如果不是,说明数据已经被别人改过了,这次写入就失败。
2. read-validated singles (安全读单条数据)
* 目的: 保证单条缓存数据绝对可靠。
* 怎么做的: 读数据时也会验证版本号。万一出现缓存数据损坏、格式不对、或者版本号对不上,系统会自动认为这条缓存无效,然后默默地去数据库取最新数据,并修好缓存。你作为用户,永远看不到脏数据。
3. bulk set validation (批量缓存验证)
* 目的: 安全地缓存一批数据(比如一个列表查询的结果)。
* 怎么做的: 这批数据里的每一个成员都有自己的版本号。读取时,会检查每一个成员的版本号。只要有一个成员过期了,整批缓存都会被丢弃,重新从数据库获取。这叫“一颗老鼠屎坏了一锅粥”策略,虽然严格,但保证了批量数据的绝对一致性。
4. pluggable providers/codecs (可插拔的底层存储和序列化)
* 怎么做的: 这是一个高度模块化的设计。
* Provider (存储提供方): 你可以自由选择用哪种技术做缓存底层,比如高性能的 Ristretto
、BigCache
,或者分布式的 Redis
。系统本身不绑定任何一家。
* Codec (编解码器): 你可以自由选择数据序列化的格式,比如通用的 JSON
,高效的 Msgpack
、CBOR
,或者更高效的 Protobuf
。想换就换。
5. optional shared generations (可选的共享版本号存储)
* 目的: 处理多服务器(多副本)的场景。
* 默认模式 (Local): 版本号只在当前服务器内存里。这样最快,但如果重启服务器,版本号就丢了;多台服务器之间也无法同步缓存失效。
* 分布式模式 (Shared - 如 Redis): 把版本号统一存到一个地方(比如Redis)。这样所有服务器都遵循同一套版本号。一台服务器让缓存失效了,其他服务器都能知道。服务器重启后也能记住之前的版本状态。
整体工作流程比喻
假设我们要缓存“用户A的信息”:
1. 读数据 (Cache-Aside Pattern):
* 先问缓存:“有没有用户A的最新数据?”
* 缓存系统会找到数据并检查其版本号。
* 如果版本号最新且数据完好,直接返回给你。
* 如果版本号过期或数据损坏,它就自己偷偷去数据库拉取最新数据和最新版本号,更新缓存,然后把新数据返回给你。对你来说无感知。
2. 写数据/失效数据:
* 当“用户A的信息”在数据库被更新时。
* 系统会调用 Invalidate(“用户A”)
。
* 这个操作做两件事:① 让这条缓存数据立刻失效(删除它);② 把“用户A”的版本号+1。
* 这样,所有旧的缓存写入(版本号不对)都会失败,所有读请求(发现版本号变了)都会去读数据库。
总结
这个系统就像一个既高效又极其可靠的中间人:
* 对你(用户)来说: 简单,总能拿到最新数据,不用关心背后的复杂逻辑。
* 对系统来说:
* 安全第一: 通过版本号机制彻底杜绝脏读。
* 灵活可扩展: 缓存用什么存(Redis/本地),数据用什么格式编解码(JSON/Proto),都可以像乐高积木一样随便换。
* 适应各种场景: 单机应用用默认模式最快;大型分布式应用打开“共享版本号”开关,就能保证多台服务器之间的缓存一致性。