目录
  • 支持功能
  • 开发缘由
  • 如何使用
    • 安装
  • 存储适配器
    • 缓存适配器
    • 编写自己的缓存或存储
  • 压缩

    前言:

    Gocache是一个基于Go语言编写的多存储驱动的缓存扩展组件。它为您带来了许多缓存数据的功能。

    支持功能

    多个缓存驱动存储:支持内存、redis或您自定义存储驱动。

    支持如下功能:

    • 链式缓存:使用具有优先级顺序的多个缓存(例如,内存然后回退到redis共享缓存)。
    • 可加载缓存:允许您调用回调函数将数据放回缓存中。
    • 指标缓存,可让您存储有关缓存使用情况的指标(命中、未命中、设置成功、设置错误……)。
    • 自动编组/解组缓存值作为结构的编组器。
    • 在存储中定义默认值并在设置数据时覆盖它们。
    • 通过过期时间和/或使用标签缓存失效。
    • 泛型的使用。

    默认情况下,Gocache支持如下几种缓存驱动:

    • 内存 (bigcache) (allegro/bigcache)。
    • 内存 (ristretto) (dgraph-io/ristretto)。
    • 内存 (go-cache) (patrickmn/go-cache)。
    • 内存缓存(bradfitz/memcache)。
    • Redis (go-redis/redis)。
    • 空闲缓存(coocood/freecache)。
    • Pegasus ( apache/incubator-pegasus )基准测试。

    开发缘由

    在作者的官网博客中提到这样的几句话:

    当我开始在 GraphQL Go 项目上实现缓存时,它已经有一个内存缓存,它使用了一个具有简单 API 的小库,但也使用了另一个内存缓存库来使用具有不同库和 API 的批处理模式加载数据,做同样的事情:缓存项目。后来,我们还有一个需求:除了这个内存缓存之外,我们还想使用 Redis 添加一层分布式缓存,主要是为了避免我们的新 Kubernetes pod 在将新版本的应用程序投入生产时出现空缓存。

    因此,作者想到是时候拥有一个统一的API来管理多个缓存存储:内存、redis、memcache 或任何你想要的。

    如何使用

    安装

    要开始使用最新版本的 go-cache,您可以使用以下命令:

    go get github.com/eko/gocache/v3

    为避免尝试导入库时出现任何错误,请使用以下导入语句:

    import (
    	"github.com/eko/gocache/v3/cache"
    	"github.com/eko/gocache/v3/store"
    )

    如果您遇到任何错误,请务必运行go mod tidy以清理您的 go.mod 文件。

    存储适配器

    首先,当您要缓存项目时,您必须选择要缓存项目的位置:在内存中?在共享的 redis 或 memcache 中?或者可能在另一个存储中。

    目前,Gocache 实现了以下存储:

    • BigCache:内存中的存储。
    • Ristretto : DGraph 提供的另一个内存存储。
    • Memcache:基于 bradfitz/gomemcache 客户端库的 memcache 存储。
    • Redis:基于 go-redis/redis 客户端库的 redis 存储。

    所有这些商店都实现了一个非常简单的 API,它遵循以下接口:

    type StoreInterface interface {
    	Get(key interface{}) (interface{}, error)
    	Set(key interface{}, value interface{}, options *Options) error
    	Delete(key interface{}) error
    	Invalidate(options InvalidateOptions) error
    	Clear() error
    	GetType() string
    }

    此接口代表您可以在商店中执行的所有操作,并且每个操作都调用客户端库中的必要方法。所有这些存储都有不同的配置,具体取决于您要使用的客户端库,

    例如,初始化 Memcache 存储:

    store := store.NewMemcache(
    	memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212"),
    	&store.Options{
    		Expiration: 10*time.Second,
    	},
    )

    然后,必须将初始化的存储传递给缓存对象构造函数。

    缓存适配器

    一个缓存接口来统治它们。

    缓存接口与存储接口完全相同,因为基本上,缓存将对存储执行操作:

    type CacheInterface interface {
    	Get(key interface{}) (interface{}, error)
    	Set(key, object interface{}, options *store.Options) error
    	Delete(key interface{}) error
    	Invalidate(options store.InvalidateOptions) error
    	Clear() error
    	GetType() string
    }

    使用这个界面,我可以对缓存项执行所有必要的操作:设置、获取、删除、无效数据、清除所有缓存和另一个方法 (GetType),它可以让我知道当前缓存项是什么,很有用在某些情况下。

    从这个接口开始,实现的缓存类型如下:

    • Cache:允许操作来自给定存储的数据的基本缓存。
    • Chain:一个特殊的缓存适配器,允许链接多个缓存(可能是因为你有一个内存缓存,一个redis缓存等……)。
    • Loadable: 一个特殊的缓存适配器,允许指定一种回调函数,如果过期或失效,自动将数据重新加载到缓存中。
    • Metric:一个特殊的缓存适配器,允许存储有关缓存数据的指标:设置、获取、失效、成功与否的项目数。 当所有这些缓存都实现相同的接口并且可以相互包装时,美妙之处就出现了:一个指标缓存可以采用一个可加载的缓存,该缓存可以采用一个可以采用多个缓存的链式缓存。

    下面是一个简单的 Memcache 示例:

    memcacheStore := store.NewMemcache(
    	memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212"),
    	&store.Options{
    		Expiration: 10*time.Second,
    	},
    )
    cacheManager := cache.New(memcacheStore)
    err := cacheManager.Set("my-key", []byte("my-value"), &cache.Options{
    	Expiration: 15*time.Second, // Override default value of 10 seconds defined in the store
    })
    if err != nil {
        panic(err)
    }
    value := cacheManager.Get("my-key")
    cacheManager.Delete("my-key")
    cacheManager.Clear() 
    // Clears the entire cache, in case you want to flush all cache

    现在,假设您想要一个链式缓存,其中包含一个内存 Ristretto 存储和一个分布式 Redis 存储作为后备,

    并带有一个 marshaller 和指标作为结果:

    // Initialize Ristretto cache and Redis client
    ristrettoCache, err := ristretto.NewCache(&ristretto.Config{NumCounters: 1000, MaxCost: 100, BufferItems: 64})
    if err != nil {
        panic(err)
    }
    redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
    
    // Initialize stores
    ristrettoStore := store.NewRistretto(ristrettoCache, nil)
    redisStore := store.NewRedis(redisClient, &cache.Options{Expiration: 5*time.Second})
    
    // Initialize Prometheus metrics
    promMetrics := metrics.NewPrometheus("my-amazing-app")
    // Initialize chained cache
    cacheManager := cache.NewMetric(promMetrics, cache.NewChain(
        cache.New(ristrettoStore),
        cache.New(redisStore),
    ))
    // Initializes a marshaler
    marshal := marshaler.New(cacheManager)
    
    key := BookQuery{Slug: "my-test-amazing-book"}
    value := Book{ID: 1, Name: "My test amazing book", Slug: "my-test-amazing-book"}
    
    // Set the value in cache using given key
    err = marshal.Set(key, value)
    if err != nil {
        panic(err)
    }
    returnedValue, err := marshal.Get(key, new(Book))
    if err != nil {
        panic(err)
    }
    // Then, do what you want with the value

    我们还没有谈到 Marshaler,但它是 Gocache 的另一个功能:我们提供了一项服务来帮助您自动编组/解组您的对象从/到您的存储。

    这在使用 struct 对象作为键而不是内存存储时很有用,因为您必须将对象转换为字节。

    所有这些功能:带有内存和 redis 的链式缓存、Prometheus 指标和封送处理程序只需大约 20 行代码即可完成。

    编写自己的缓存或存储

    如果您想实现自己的专有缓存,也很容易做到。

    这是一个简单的示例,以防您想要记录在缓存中完成的每个操作(这不是一个好主意,但很好,这是一个简单的 todo 示例):

    package customcache
    import (
    	"log"
    
    	"github.com/eko/gocache/cache"
    	"github.com/eko/gocache/store"
    )
    const LoggableType = "loggable"
    type LoggableCache struct {
    	cache cache.CacheInterface
    }
    func NewLoggable(cache cache.CacheInterface) *LoggableCache {
    	return &LoggableCache{
    		cache: cache,
    	}
    }
    
    func (c *LoggableCache) Get(key interface{}) (interface{}, error) {
    	log.Print("Get some data...")
    	return c.cache.Get(key)
    }
    
    func (c *LoggableCache) Set(key, object interface{}, options *store.Options) error {
    	log.Print("Set some data...")
    	return c.cache.Set(key, object, options)
    }
    func (c *LoggableCache) Delete(key interface{}) error {
    	log.Print("Delete some data...")
    	return c.cache.Delete(key)
    }
    func (c *LoggableCache) Invalidate(options store.InvalidateOptions) error {
    	log.Print("Invalidate some data...")
    	return c.cache.Invalidate(options)
    }
    func (c *LoggableCache) Clear() error {
    	log.Print("Clear some data...")
    	return c.cache.Clear()
    }
    func (c *LoggableCache) GetType() string {
    	return LoggableType
    }

    同样,您也可以实现自定义存储。如果您认为其他人可以使您的缓存或存储实现受益,请不要犹豫,打开拉取请求并直接为项目做出贡献,以便我们一起讨论您的想法并带来更强大的缓存库。

    压缩

    生成模拟测试数据:

    go get github.com/golang/mock/mockgen
    make mocks

    测试套件可以运行:

    make test # run unit test
    make benchmark-store # run benchmark test

    Golang实现多存储驱动设计SDK案例

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。