缓存组件默认提供了一个高速的内存缓存,操作效率非常高效, CPU 性能损耗在 ns 纳秒级别。

使用示例

基本使用

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/os/gcache"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. )
  7. func main() {
  8. // 创建一个缓存对象,
  9. // 当然也可以便捷地直接使用gcache包方法
  10. var (
  11. ctx = gctx.New()
  12. cache = gcache.New()
  13. )
  14. // 设置缓存,不过期
  15. err := cache.Set(ctx, "k1", "v1", 0)
  16. if err != nil {
  17. panic(err)
  18. }
  19. // 获取缓存值
  20. value, err := cache.Get(ctx, "k1")
  21. if err != nil {
  22. panic(err)
  23. }
  24. fmt.Println(value)
  25. // 获取缓存大小
  26. size, err := cache.Size(ctx)
  27. if err != nil {
  28. panic(err)
  29. }
  30. fmt.Println(size)
  31. // 缓存中是否存在指定键名
  32. b, err := cache.Contains(ctx, "k1")
  33. if err != nil {
  34. panic(err)
  35. }
  36. fmt.Println(b)
  37. // 删除并返回被删除的键值
  38. removedValue, err := cache.Remove(ctx, "k1")
  39. if err != nil {
  40. panic(err)
  41. }
  42. fmt.Println(removedValue)
  43. // 关闭缓存对象,让GC回收资源
  44. if err = cache.Close(ctx); err != nil {
  45. panic(err)
  46. }
  47. }

执行后,输出结果为:

  1. 1
  2. true
  3. v1

过期控制

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/os/gcache"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. "time"
  7. )
  8. func main() {
  9. var (
  10. ctx = gctx.New()
  11. )
  12. // 当键名不存在时写入,设置过期时间1000毫秒
  13. _, err := gcache.SetIfNotExist(ctx, "k1", "v1", time.Second)
  14. if err != nil {
  15. panic(err)
  16. }
  17. // 打印当前的键名列表
  18. keys, err := gcache.Keys(ctx)
  19. if err != nil {
  20. panic(err)
  21. }
  22. fmt.Println(keys)
  23. // 打印当前的键值列表
  24. values, err := gcache.Values(ctx)
  25. if err != nil {
  26. panic(err)
  27. }
  28. fmt.Println(values)
  29. // 获取指定键值,如果不存在时写入,并返回键值
  30. value, err := gcache.GetOrSet(ctx, "k2", "v2", 0)
  31. if err != nil {
  32. panic(err)
  33. }
  34. fmt.Println(value)
  35. // 打印当前的键值对
  36. data1, err := gcache.Data(ctx)
  37. if err != nil {
  38. panic(err)
  39. }
  40. fmt.Println(data1)
  41. // 等待1秒,以便k1:v1自动过期
  42. time.Sleep(time.Second)
  43. // 再次打印当前的键值对,发现k1:v1已经过期,只剩下k2:v2
  44. data2, err := gcache.Data(ctx)
  45. if err != nil {
  46. panic(err)
  47. }
  48. fmt.Println(data2)
  49. }

执行后,输出结果为:

  1. [k1]
  2. [v1]
  3. v2
  4. map[k1:v1 k2:v2]
  5. map[k2:v2]

GetOrSetFunc*

GetOrSetFunc 获取一个缓存值,当缓存不存在时执行指定的 f func(context.Context) (interface{}, error),缓存该 f 方法的结果值,并返回该结果。

需要注意的是, GetOrSetFunc 的缓存方法参数 f 是在缓存的 锁机制外执行,因此在 f 内部也可以嵌套调用 GetOrSetFunc。但如果 f 的执行比较耗时,高并发的时候容易出现 f 被多次执行的情况(缓存设置只有第一个执行的 f 返回结果能够设置成功,其余的被抛弃掉)。而 GetOrSetFuncLock 的缓存方法 f 是在缓存的 锁机制内执行,因此可以保证当缓存项不存在时只会执行一次 f,但是缓存写锁的时间随着 f 方法的执行时间而定。

我们来看一个示例:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/os/gcache"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. "time"
  7. )
  8. func main() {
  9. var (
  10. ch = make(chan struct{}, 0)
  11. ctx = gctx.New()
  12. key = `key`
  13. value = `value`
  14. )
  15. for i := 0; i < 10; i++ {
  16. go func(index int) {
  17. <-ch
  18. _, err := gcache.GetOrSetFuncLock(ctx, key, func(ctx context.Context) (interface{}, error) {
  19. fmt.Println(index, "entered")
  20. return value, nil
  21. }, 0)
  22. if err != nil {
  23. panic(err)
  24. }
  25. }(i)
  26. }
  27. close(ch)
  28. time.Sleep(time.Second)
  29. }

执行后,终端输出(带有随机性,但是只会输出一条信息):

  1. 9 entered

可以看到,多个 goroutine 同时调用 GetOrSetFuncLock 方法时,由于该方法有并发安全控制,因此最终只有一个 goroutine 的数值生成函数执行成功,成功之后其他 goroutine 拿到数据后则立即返回不再执行对应的数值生成函数。

LRU 缓存淘汰控制

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/os/gcache"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. "time"
  7. )
  8. func main() {
  9. var (
  10. ctx = gctx.New()
  11. cache = gcache.New(2) // 设置LRU淘汰数量
  12. )
  13. // 添加10个元素,不过期
  14. for i := 0; i < 10; i++ {
  15. if err := cache.Set(ctx, i, i, 0); err != nil {
  16. panic(err)
  17. }
  18. }
  19. size, err := cache.Size(ctx)
  20. if err != nil {
  21. panic(err)
  22. }
  23. fmt.Println(size)
  24. keys, err := cache.Keys(ctx)
  25. if err != nil {
  26. panic(err)
  27. }
  28. fmt.Println(keys)
  29. // 读取键名1,保证该键名是优先保留
  30. value, err := cache.Get(ctx, 1)
  31. if err != nil {
  32. panic(err)
  33. }
  34. fmt.Println(value)
  35. // 等待一定时间后(默认1秒检查一次),
  36. // 元素会被按照从旧到新的顺序进行淘汰
  37. time.Sleep(3 * time.Second)
  38. size, err = cache.Size(ctx)
  39. if err != nil {
  40. panic(err)
  41. }
  42. fmt.Println(size)
  43. keys, err = cache.Keys(ctx)
  44. if err != nil {
  45. panic(err)
  46. }
  47. fmt.Println(keys)
  48. }

执行后,输出结果为:

  1. [2 3 5 6 7 0 1 4 8 9]
  2. 1
  3. 2
  4. [1 9]

性能测试

测试环境

  1. CPU: Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz
  2. MEM: 8GB
  3. SYS: Ubuntu 16.04 amd64

测试结果

  1. john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcache$ go test *.go -bench=".*" -benchmem
  2. goos: linux
  3. goarch: amd64
  4. Benchmark_CacheSet-4 2000000 897 ns/op 249 B/op 4 allocs/op
  5. Benchmark_CacheGet-4 5000000 202 ns/op 49 B/op 1 allocs/op
  6. Benchmark_CacheRemove-4 50000000 35.7 ns/op 0 B/op 0 allocs/op
  7. Benchmark_CacheLruSet-4 2000000 880 ns/op 399 B/op 4 allocs/op
  8. Benchmark_CacheLruGet-4 3000000 212 ns/op 33 B/op 1 allocs/op
  9. Benchmark_CacheLruRemove-4 50000000 35.9 ns/op 0 B/op 0 allocs/op
  10. Benchmark_InterfaceMapWithLockSet-4 3000000 477 ns/op 73 B/op 2 allocs/op
  11. Benchmark_InterfaceMapWithLockGet-4 10000000 149 ns/op 0 B/op 0 allocs/op
  12. Benchmark_InterfaceMapWithLockRemove-4 50000000 39.8 ns/op 0 B/op 0 allocs/op
  13. Benchmark_IntMapWithLockWithLockSet-4 5000000 304 ns/op 53 B/op 0 allocs/op
  14. Benchmark_IntMapWithLockGet-4 20000000 164 ns/op 0 B/op 0 allocs/op
  15. Benchmark_IntMapWithLockRemove-4 50000000 33.1 ns/op 0 B/op 0 allocs/op
  16. PASS
  17. ok command-line-arguments 47.503s