引言
你是否听说过 Phil Karlton 广为人知一句著名格言,被计算机界大量引用,称为 "Karlton's Law"
There are only two hard things in Computer Science: cache invalidation and naming things.
他说计算机科学中只有两个难题: 缓存失效和命名。这足以说明缓存的管理不是一件容易的事。Goodle 框架提供的缓存流水线,通过链式调用让复杂的缓存管理化繁为简。
场景
假设你在查询数据库之前需要先判断是否有本地缓存,有缓存就使用缓存,如果没有再去 Redis 看看有没有缓存,假设 Redis 又分了主从, 你要先在主库上找,主库没有再去从库找,从库可能还分了两层,两层从库也找不到最后再去数据库查询,然后再将查询到的结果回写到这一条缓存链中。
对于大流量高并发的分布式系统中,这种分层缓存链路是很常见的,对于这一长条缓存链路,我们的缓存 Pipeline 只需要一行链式调用的代码就能完成。
使用示例
查询并回写缓存
main.go
import (
"github.com/text3cn/goodle/goodle"
"github.com/text3cn/goodle/providers/cache"
"github.com/text3cn/goodle/providers/httpserver"
)
func main() {
// 存储桶容量,字节为单位,最小 32MB ,少于 32MB 会当做 32MB 处理
bucketSize := 32 * 1024 * 1024
cache.NewFreeCache("bucket1", bucketSize)
// 启动 http 服务
goodle.Init().RunHttp(func(engine *httpserver.Engine) {
engine.Get("/", Index)
}, ":3333")
}
func Index(ctx *httpserver.Context) {
bucket := ctx.Cache.FastCache("bucket1")
// 使用 Pipeline
p := bucket.Pipeline("key1").Local(60).Redis(300,"slave").Redis(3600 * 24).Setter(func() (string) {
// 查询数据库,return 字符串值会回写到缓存中
return 8899, "8899"
})
println(p.Data.(int))
println(p.Cache)
}
上面的示例代码中,通过调用顺序来决定缓存查找顺序。
- 我们先是调用了
Local()
方法,这代表首先在本地缓存中查找,如果找到了则使用本地缓存。 - 如果本地缓存中没找到则紧接着去 Redis 中找,
Redis(300,"slave")
方法的第一个参数是过期时间,回写缓存的时候会使用这个值,slave 代表连接名称(如果你有多个 Redis 配置) 如果在这个 Redis 中也没找到,则会继续去下一个Redis(3600 * 24)
中找。 - 如果 Redis 中都没找到缓存,最后会进入
Setter()
方法中,你可以在闭包函数里查询数据库,然后将结果返回, 返回的结果用于设置为缓存数据的字符串,你可以将数据用 Json 或 Protobuf 序列化后作为缓存,在 Redis 中会保存为 string 类型, 当Setter()
执行完毕后,缓存会被自动设置。如果Setter()
方法返回空字符串则不会进行缓存设置。
更新缓存
只需要将 Pipeline 的第二个参数设置为 true 即可,这时会跳过查找缓存的步骤直接去 Setter() 中重写缓存。 这可用于特定场景,比如管理后台中手动触发刷新缓存。
bucket.Pipeline("key1", true).Local(60).Redis(300,"slave").Redis(3600 * 24).Setter(func() (string) {
// 查询数据库,return 字符串值会回写到缓存中
return 8899, "8899"
})
删除缓存
bucket.Pipeline("key1").Local().Redis().Delete()
注意,没有在调用链中的缓存不会被删除。
组合拳
只查询缓存不设置缓存
result := bucket.Pipeline("key1", true).Local().Redis()
fmt.Println(result.Data)
如果只想使用本地缓存,不使用 Redis
bucket.Pipeline("key1", true).Local(60).Setter(func() (string) {
// 查询数据并设置缓存
})
只使用 Redis 不使用本地缓存
func Index(ctx *httpserver.Context) {
d := ctx.Redis.Pipeline("key1").Redis(300, "slave").Redis(3600*24, "master")
fmt.Println("p2", d.Cache)
}
局限性
使用流水线来管理缓存的局限性是只支持 string 类型,目前没有去支持 Redis 丰富的数据类型。