跳到主要内容

引言

你是否听说过 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)
}

上面的示例代码中,通过调用顺序来决定缓存查找顺序。

  1. 我们先是调用了 Local() 方法,这代表首先在本地缓存中查找,如果找到了则使用本地缓存。
  2. 如果本地缓存中没找到则紧接着去 Redis 中找,Redis(300,"slave") 方法的第一个参数是过期时间,回写缓存的时候会使用这个值,slave 代表连接名称(如果你有多个 Redis 配置) 如果在这个 Redis 中也没找到,则会继续去下一个 Redis(3600 * 24) 中找。
  3. 如果 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 丰富的数据类型。