GoFrame 缓存组件

GoFrame 缓存组件

GoFrame 缓存组件采用了接口化设计,通过 Adapter 接口实现,支持灵活的自定义实现和扩展。

接口设计

Adapter 接口定义

任何实现了 Adapter 接口的对象均可注册到缓存管理对象中。接口定义请参考:

https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache#Adapter

接口操作方法

注册接口实现
go 复制代码
// 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 缓存适配器章节。

获取接口实现
go 复制代码
// GetAdapter returns the adapter that is set in current Cache.
func (c *Cache) GetAdapter() Adapter

内存缓存实现

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

基本使用

go 复制代码
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

过期控制

go 复制代码
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 在锁机制内执行,保证只执行一次,但会占用锁的时间

示例代码:

go 复制代码
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 缓存淘汰

go 复制代码
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 共享
  • 数据库查询缓存

使用示例

go 复制代码
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
  • ClearSize 等操作会影响整个 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
相关推荐
2301_793086873 小时前
Redis 04 Reactor
数据库·redis·缓存
189228048617 小时前
NY243NY253美光固态闪存NY257NY260
大数据·网络·人工智能·缓存
青鱼入云8 小时前
redis怎么做rehash的
redis·缓存
FFF-X8 小时前
Vue3 路由缓存实战:从基础到进阶的完整指南
vue.js·spring boot·缓存
夜影风1 天前
Nginx反向代理与缓存实现
运维·nginx·缓存
编程(变成)小辣鸡1 天前
Redis 知识点与应用场景
数据库·redis·缓存
菜菜子爱学习2 天前
Nginx学习笔记(八)—— Nginx缓存集成
笔记·学习·nginx·缓存·运维开发
魏波.2 天前
常用缓存软件分类及详解
缓存
yh云想2 天前
《多级缓存架构设计与实现全解析》
缓存·junit
白仑色2 天前
Redis 如何保证数据安全?
数据库·redis·缓存·集群·主从复制·哨兵·redis 管理工具