Cache
cache 是一个用于缓存 HTTP 响应的中间件,开启后有助于提高服务器的并发访问能力。Hertz 也提供了对 cache 的适配,支持 multi-backend,参考了 gin-cache 的实现。
安装
go get github.com/hertz-contrib/cache
导入
import "github.com/hertz-contrib/cache"
示例代码
- memory
func main() {
h := server.New()
// 设置全局的缓存过期时间(会被更细粒度的设置覆盖)
memoryStore := persist.NewMemoryStore(1 * time.Minute)
// 设置针对以 URI 为 Key 的缓存过期时间
h.Use(cache.NewCacheByRequestURI(memoryStore, 2*time.Second))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
- redis
func main() {
h := server.New()
redisStore := persist.NewRedisStore(redis.NewClient(&redis.Options{
Network: "tcp",
Addr: "127.0.0.1:6379",
}))
h.Use(cache.NewCacheByRequestURI(redisStore, 2*time.Second))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
初始化
cache
中间件提供了三种初始化的方式。
NewCacheByRequestURI
用于创建以 URI 为 Key 的缓存响应结果的中间件。
函数签名:
func NewCacheByRequestURI(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) app.HandlerFunc
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCacheByRequestURI(memoryStore, 2*time.Second))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
NewCacheByRequestPath
用于创建以 URL 为 Key 的缓存响应结果的中间件,丢弃 query 参数。
函数签名:
func NewCacheByRequestPath(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) app.HandlerFunc
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCacheByRequestPath(memoryStore, 2*time.Second))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
NewCache
用于创建自定义缓存逻辑的中间件,必须手动声明缓存的 Key(需要使用 WithCacheStrategyByRequest
配置参数)。
函数签名:
func NewCache(
defaultCacheStore persist.CacheStore,
defaultExpire time.Duration,
opts ...Option,
) app.HandlerFunc
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCache(
memoryStore,
2*time.Second,
cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
return true, cache.Strategy{
CacheKey: c.Request.URI().String(),
}
}),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
配置
配置 | 默认值 | 介绍 |
---|---|---|
WithCacheStrategyByRequest | nil | 用于设置自定义的缓存策略 |
WithOnHitCache | nil | 用于设置缓存命中的回调函数 |
WithOnMissCache | nil | 用于设置缓存未命中的回调函数 |
WithBeforeReplyWithCache | nil | 用于设置返回缓存响应前的回调函数 |
WithOnShareSingleFlight | nil | 用于设置请求共享 SingleFlight 结果时的回调函数 |
WithSingleFlightForgetTimeout | 0 | 用于设置 SingleFlight 的超时时间 |
WithIgnoreQueryOrder | false | 用于设置当使用 URI 为缓存的 Key 时,忽略 query 参数的顺序 |
WithPrefixKey | "" | 用于设置缓存响应 Key 的前缀 |
WithoutHeader | false | 用于设置是否需要缓存响应头 |
WithCacheStrategyByRequest
通过使用 WithCacheStrategyByRequest
自定义缓存策略,包括缓存的 Key、存储介质,以及过期时间。
该配置生效的前提是,通过 cache.NewCache
方法初始化 cache
中间件。
函数签名:
func WithCacheStrategyByRequest(getGetCacheStrategyByRequest GetCacheStrategyByRequest) Option
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCache(
memoryStore,
2*time.Second,
cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
return true, cache.Strategy{
CacheKey: c.Request.URI().String(),
}
}),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
WithOnHitCache & WithOnMissCache
通过使用 WithOnHitCache
设置缓存命中的回调函数。
通过使用 WithOnMissCache
设置缓存未命中的回调函数。
函数签名:
func WithOnHitCache(cb OnHitCacheCallback) Option
func WithOnMissCache(cb OnMissCacheCallback) Option
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
var cacheHitCount, cacheMissCount int32
h.Use(cache.NewCacheByRequestURI(
memoryStore,
2*time.Second,
cache.WithOnHitCache(func(ctx context.Context, c *app.RequestContext) {
atomic.AddInt32(&cacheHitCount, 1)
}),
cache.WithOnMissCache(func(ctx context.Context, c *app.RequestContext) {
atomic.AddInt32(&cacheMissCount, 1)
}),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.GET("/get_hit_count", func(ctx context.Context, c *app.RequestContext) {
c.String(200, fmt.Sprintf("total hit count: %d", cacheHitCount))
})
h.GET("/get_miss_count", func(ctx context.Context, c *app.RequestContext) {
c.String(200, fmt.Sprintf("total miss count: %d", cacheMissCount))
})
h.Spin()
}
WithBeforeReplyWithCache
通过使用 WithBeforeReplyWithCache
设置返回缓存响应前的回调函数。
函数签名:
func WithBeforeReplyWithCache(cb BeforeReplyWithCacheCallback) Option
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCacheByRequestURI(
memoryStore,
2*time.Second,
cache.WithBeforeReplyWithCache(func(c *app.RequestContext, cache *cache.ResponseCache) {
cache.Data = append([]byte{'p', 'r', 'e', 'f', 'i', 'x', '-'}, cache.Data...)
}),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
WithOnShareSingleFlight & WithSingleFlightForgetTimeout
通过使用 WithOnShareSingleFlight
设置请求共享 SingleFlight 结果时的回调函数。
通过使用 WithSingleFlightForgetTimeout
设置 SingleFlight 的超时时间。
函数签名:
func WithOnShareSingleFlight(cb OnShareSingleFlightCallback) Option
func WithSingleFlightForgetTimeout(forgetTimeout time.Duration) Option
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCacheByRequestPath(
memoryStore,
10*time.Second,
cache.WithOnShareSingleFlight(func(ctx context.Context, c *app.RequestContext) {
hlog.Info("share the singleFlight result " + string(c.Response.Body()))
}),
cache.WithSingleFlightForgetTimeout(1*time.Second),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
time.Sleep(3 * time.Second)
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
WithIgnoreQueryOrder
通过使用 WithIgnoreQueryOrder
设置当使用 NewCacheByRequestURI
方法创建缓存中间件时,忽略 URI 的 query 参数顺序(为 true 触发参数排序)。
函数签名:
func WithIgnoreQueryOrder(b bool) Option
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCacheByRequestPath(
memoryStore,
60*time.Second,
cache.WithIgnoreQueryOrder(true),
cache.WithOnHitCache(func(c context.Context, ctx *app.RequestContext) {
hlog.Infof("hit cache IgnoreQueryOrder")
}),
cache.WithOnMissCache(func(c context.Context, ctx *app.RequestContext) {
hlog.Infof("miss cache IgnoreQueryOrder")
}),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
WithPrefixKey
通过使用 WithPrefixKey
设置响应 Key 的前缀。
函数签名:
func WithPrefixKey(prefix string) Option
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCache(
memoryStore,
60*time.Second,
cache.WithPrefixKey("prefix-"),
cache.WithOnHitCache(func(c context.Context, ctx *app.RequestContext) {
resp := &cache.ResponseCache{}
memoryStore.Get(c, "prefix-test", &resp)
hlog.Info("data = " + string(resp.Data))
}),
cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
return true, cache.Strategy{
CacheKey: "test",
}
}),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
WithoutHeader
通过使用 WithoutHeader
设置是否需要缓存响应头,为 false 则缓存响应头。
函数签名:
func WithoutHeader(b bool) Option
示例代码:
func main() {
h := server.New()
memoryStore := persist.NewMemoryStore(1 * time.Minute)
h.Use(cache.NewCache(
memoryStore,
60*time.Second,
cache.WithoutHeader(true),
cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
return true, cache.Strategy{
CacheKey: "test-key",
}
}),
cache.WithOnHitCache(func(c context.Context, ctx *app.RequestContext) {
resp := &cache.ResponseCache{}
memoryStore.Get(c, "test-key", &resp)
hlog.Info("header = " + string(resp.Header.Get("head")))
hlog.Info("data = " + string(resp.Data))
}),
))
h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
c.String(http.StatusOK, "hello world")
})
h.Spin()
}
完整示例
完整用法示例详见 cache/example