乐趣区

关于go:Golang实现多存储驱动设计SDK

本文已收录编程学习笔记。涵盖 PHP、JavaScript、Linux、Golang、MySQL、Redis 和开源工具等等相干内容。

意识 Gocache

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

反对性能

多个缓存驱动存储:反对内存、redis 或您自定义存储驱动。反对如下性能:

✅链式缓存:应用具备优先级程序的多个缓存(例如,内存而后回退到 redis 共享缓存)。

✅可加载缓存:容许您调用回调函数将数据放回缓存中。

✅指标缓存,可让您存储无关缓存应用状况的指标(命中、未命中、设置胜利、设置谬误……)。

✅主动编组 / 解组缓存值作为构造的编组器。

✅在存储中定义默认值并在设置数据时笼罩它们。

✅通过过期工夫和 / 或应用标签缓存生效。

✅泛型的应用。

默认状况下,Gocache 反对如下几种缓存驱动:

  1. 内存 (bigcache) (allegro/bigcache)。
  2. 内存 (ristretto) (dgraph-io/ristretto)。
  3. 内存 (go-cache) (patrickmn/go-cache)。
  4. 内存缓存(bradfitz/memcache)。
  5. Redis (go-redis/redis)。
  6. 闲暇缓存(coocood/freecache)。
  7. 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 实现了以下存储:

  1. BigCache:内存中的存储。
  2. Ristretto : DGraph 提供的另一个内存存储。
  3. Memcache:基于 bradfitz/gomemcache 客户端库的 memcache 存储。
  4. 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
退出移动版