- Created by 郭强, last modified on Nov 06, 2021
缓存组件默认提供了一个高速的内存缓存,操作效率非常高效,CPU
性能损耗在ns
纳秒级别。
使用示例
基本使用
package main import ( "fmt" "github.com/gogf/gf/v2/os/gcache" "github.com/gogf/gf/v2/os/gctx" ) func main() { // 创建一个缓存对象, // 当然也可以便捷地直接使用gcache包方法 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) } }
执行后,输出结果为:
v1 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() ) // 当键名不存在时写入,设置过期时间1000毫秒 _, 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已经过期,只剩下k2:v2 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() (interface{}, error)
,缓存该f
方法的结果值,并返回该结果。
需要注意的是,GetOrSetFunc
的缓存方法参数f
是在缓存的锁机制外执行,因此在f
内部也可以嵌套调用GetOrSetFunc
。但如果f
的执行比较耗时,高并发的时候容易出现f
被多次执行的情况(缓存设置只有第一个执行的f
返回结果能够设置成功,其余的被抛弃掉)。而GetOrSetFuncLock
的缓存方法f
是在缓存的锁机制内执行,因此可以保证当缓存项不存在时只会执行一次f
,但是缓存写锁的时间随着f
方法的执行时间而定。
我们来看一个示例:
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` ) for i := 0; i < 10; i++ { go func(index int) { <-ch _, err := gcache.GetOrSetFuncLock(ctx, key, func() (interface{}, error) { fmt.Println(index, "entered") return value, nil }, 0) if err != nil { panic(err) } }(i) } close(ch) time.Sleep(time.Second) }
执行后,终端输出(带有随机性,但是只会输出一条信息):
9 entered
可以看到,多个goroutine
同时调用GetOrSetFuncLock
方法时,由于该方法有并发安全控制,因此最终只有一个goroutine
的数值生成函数执行成功,成功之后其他goroutine
拿到数据后则立即返回不再执行对应的数值生成函数。
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淘汰数量 ) // 添加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) }
执行后,输出结果为:
10 [2 3 5 6 7 0 1 4 8 9] 1 2 [1 9]
性能测试
测试环境
CPU: Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz MEM: 8GB SYS: 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
Content Menu
- No labels
12 Comments
ayamzh
支持超时时间么
郭强
支持
赵会南
一定要 close 吗 会导致内存泄露吗
郭强
一般不需要
Close
,因为缓存对象不会创建太多,占用的资源不会很多,可以随着进程销毁而销毁。赵会南
👌 感谢
风一样的男子
使用过程中,单个func内,gcache set key后,get(key)可以获取值,在另外func内无法获取。gcache需要config配置或者全局的Cache对象?
郭强
不会
风一样的男子
是可以获取的,对照了下参数,gcache.Sets(mapData, 86400),第二个参数错误应该是time.Duration类型
kobe
在单个func中 gcache set key后,想在Hook()回调函数的get获取。如果多个携程同时set,会不会存在并发问题,如果会该怎么解决?
海亮
不会,默认的内存实现方式有并发锁
https://github.com/gogf/gf/blob/b316b9c0736ed6e08ec132d9d57ca8087e2120a2/os/gcache/gcache_adapter_memory_data.go
sanrentai
是否有必要增加一个RemoveAll 删除全部缓存内容的函数?
海亮
为什么LRU缓存淘汰控制,需要做定时删除?感觉这样的程序复杂度高了很多,也存在数据冗余的情况,背离淘汰的初衷。