GoFrame 缓存组件
GoFrame 缓存组件采用了接口化设计,通过 Adapter
接口实现,支持灵活的自定义实现和扩展。
接口设计
Adapter 接口定义
任何实现了 Adapter
接口的对象均可注册到缓存管理对象中。接口定义请参考:
https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache#Adapter
接口操作方法
注册接口实现
// SetAdapter changes the adapter for this cache.
// Be very note that, this setting function is not concurrent-safe, which means you should not call
// this setting function concurrently in multiple goroutines.
func (c *Cache) SetAdapter(adapter Adapter)
具体示例请参考下方 Redis 缓存适配器章节。
获取接口实现
// GetAdapter returns the adapter that is set in current Cache.
func (c *Cache) GetAdapter() Adapter
内存缓存实现
缓存组件默认提供了一个高速的内存缓存实现,CPU 性能损耗在 ns 纳秒级别,操作效率非常高效。
基本使用
package main
import (
"fmt"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
var (
ctx = gctx.New()
cache = gcache.New()
)
// 设置缓存,不过期
err := cache.Set(ctx, "k1", "v1", 0)
if err != nil {
panic(err)
}
// 获取缓存值
value, err := cache.Get(ctx, "k1")
if err != nil {
panic(err)
}
fmt.Println(value)
// 获取缓存大小
size, err := cache.Size(ctx)
if err != nil {
panic(err)
}
fmt.Println(size)
// 缓存中是否存在指定键名
b, err := cache.Contains(ctx, "k1")
if err != nil {
panic(err)
}
fmt.Println(b)
// 删除并返回被删除的键值
removedValue, err := cache.Remove(ctx, "k1")
if err != nil {
panic(err)
}
fmt.Println(removedValue)
// 关闭缓存对象,让GC回收资源
if err = cache.Close(ctx); err != nil {
panic(err)
}
}
输出结果:
1
true
v1
过期控制
package main
import (
"fmt"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gctx"
"time"
)
func main() {
var ctx = gctx.New()
// 当键名不存在时写入,设置过期时间1秒
_, err := gcache.SetIfNotExist(ctx, "k1", "v1", time.Second)
if err != nil {
panic(err)
}
// 打印当前的键名列表
keys, err := gcache.Keys(ctx)
if err != nil {
panic(err)
}
fmt.Println(keys)
// 打印当前的键值列表
values, err := gcache.Values(ctx)
if err != nil {
panic(err)
}
fmt.Println(values)
// 获取指定键值,如果不存在时写入,并返回键值
value, err := gcache.GetOrSet(ctx, "k2", "v2", 0)
if err != nil {
panic(err)
}
fmt.Println(value)
// 打印当前的键值对
data1, err := gcache.Data(ctx)
if err != nil {
panic(err)
}
fmt.Println(data1)
// 等待1秒,以便k1:v1自动过期
time.Sleep(time.Second)
// 再次打印当前的键值对,k1:v1已经过期
data2, err := gcache.Data(ctx)
if err != nil {
panic(err)
}
fmt.Println(data2)
}
输出结果:
[k1]
[v1]
v2
map[k1:v1 k2:v2]
map[k2:v2]
GetOrSetFunc 方法说明
GetOrSetFunc
方法用于获取缓存值,当缓存不存在时执行指定的 f func(context.Context) (interface{}, error)
方法,并将结果缓存后返回。
注意事项:
GetOrSetFunc
的缓存方法参数f
在缓存的锁机制外执行f
内部可以嵌套调用GetOrSetFunc
- 如果
f
执行耗时较长,高并发时可能被多次执行GetOrSetFuncLock
在锁机制内执行,保证只执行一次,但会占用锁的时间
示例代码:
package main
import (
"fmt"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gctx"
"time"
)
func main() {
var (
ch = make(chan struct{}, 0)
ctx = gctx.New()
key = `key`
value = `value`
)
// 启动10个协程并发测试
for i := 0; i < 10; i++ {
go func(index int) {
<-ch
_, err := gcache.GetOrSetFuncLock(ctx, key, func(ctx context.Context) (interface{}, error) {
fmt.Println(index, "entered")
return value, nil
}, 0)
if err != nil {
panic(err)
}
}(i)
}
close(ch)
time.Sleep(time.Second)
}
输出结果(随机值):
9 entered
LRU 缓存淘汰
package main
import (
"fmt"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gctx"
"time"
)
func main() {
var (
ctx = gctx.New()
cache = gcache.New(2) // 设置LRU淘汰数量为2
)
// 添加10个元素,不过期
for i := 0; i < 10; i++ {
if err := cache.Set(ctx, i, i, 0); err != nil {
panic(err)
}
}
// 查看当前缓存状态
size, err := cache.Size(ctx)
if err != nil {
panic(err)
}
fmt.Println(size)
keys, err := cache.Keys(ctx)
if err != nil {
panic(err)
}
fmt.Println(keys)
// 读取键名1,确保其优先保留
value, err := cache.Get(ctx, 1)
if err != nil {
panic(err)
}
fmt.Println(value)
// 等待自动淘汰(默认1秒检查一次)
time.Sleep(3 * time.Second)
// 查看淘汰后的缓存状态
size, err = cache.Size(ctx)
if err != nil {
panic(err)
}
fmt.Println(size)
keys, err = cache.Keys(ctx)
if err != nil {
panic(err)
}
fmt.Println(keys)
}
输出结果:
[2 3 5 6 7 0 1 4 8 9]
1
2
[1 9]
Redis 缓存适配器
GoFrame 提供了 Redis 缓存适配器实现,特别适用于:
- 多节点数据一致性场景
- Session 共享
- 数据库查询缓存
使用示例
func ExampleCache_SetAdapter() {
var (
err error
ctx = gctx.New()
cache = gcache.New()
redisConfig = &gredis.Config{
Address: "127.0.0.1:6379",
Db: 9,
}
cacheKey = `key`
cacheValue = `value`
)
// 创建Redis客户端
redis, err := gredis.New(redisConfig)
if err != nil {
panic(err)
}
// 创建并设置Redis适配器
cache.SetAdapter(gcache.NewAdapterRedis(redis))
// 使用缓存对象设置和获取
err = cache.Set(ctx, cacheKey, cacheValue, time.Second)
if err != nil {
panic(err)
}
fmt.Println(cache.MustGet(ctx, cacheKey).String())
// 使用Redis客户端直接获取
fmt.Println(redis.MustDo(ctx, "GET", cacheKey).String())
}
输出结果:
value
value
注意事项
1. Clear/Size 等方法的特殊性
- 相同的
gredis.Config
会连接到相同的 Redis DB - Redis 没有数据分组功能
- 多个
gcache.Cache
对象共享同一个 Redis DB Clear
、Size
等操作会影响整个 Redis DB- 行为与内存缓存不同,请谨慎使用
2. Redis DB 使用建议
建议为不同的业务缓存类型配置不同的 Redis DB,避免与其他业务数据混用。
性能测试
测试环境
- CPU: Intel® Core™ i5-4460 CPU @ 3.20GHz
- 内存: 8GB
- 系统: Ubuntu 16.04 amd64
测试结果
john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcache$ go test *.go -bench=".*" -benchmem
goos: linux
goarch: amd64
Benchmark_CacheSet-4 2000000 897 ns/op 249 B/op 4 allocs/op
Benchmark_CacheGet-4 5000000 202 ns/op 49 B/op 1 allocs/op
Benchmark_CacheRemove-4 50000000 35.7 ns/op 0 B/op 0 allocs/op
Benchmark_CacheLruSet-4 2000000 880 ns/op 399 B/op 4 allocs/op
Benchmark_CacheLruGet-4 3000000 212 ns/op 33 B/op 1 allocs/op
Benchmark_CacheLruRemove-4 50000000 35.9 ns/op 0 B/op 0 allocs/op
Benchmark_InterfaceMapWithLockSet-4 3000000 477 ns/op 73 B/op 2 allocs/op
Benchmark_InterfaceMapWithLockGet-4 10000000 149 ns/op 0 B/op 0 allocs/op
Benchmark_InterfaceMapWithLockRemove-4 50000000 39.8 ns/op 0 B/op 0 allocs/op
Benchmark_IntMapWithLockWithLockSet-4 5000000 304 ns/op 53 B/op 0 allocs/op
Benchmark_IntMapWithLockGet-4 20000000 164 ns/op 0 B/op 0 allocs/op
Benchmark_IntMapWithLockRemove-4 50000000 33.1 ns/op 0 B/op 0 allocs/op
PASS
ok command-line-arguments 47.503s