存储(store)
首先,当你准备缓存一些数据时,你必须选择缓存的存储方式:简单的直接放进内存?使用 Redis 或者 Memcache?或者其它某种形式的存储。
目前,Gocache 已经实现了以下存储方案:
- Bigcache: 简单的内存存储。
- Ristretto: 由 DGraph 提供的内存存储。
- Memcache: 基于 bradfitz/gomemcache 的 Memcache 存储。
- Redis: 基于 go-redis/redis 的 Redis 存储。
所有的存储方案都实现了一个非常简单的接口:
1
2
3
4
5
6
7
8
|
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 存储器的示例:
1
2
3
4
5
6
|
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,
},
)
|
然后,必须将初始化存储器的代码放进缓存的构造函数中。
缓存(cache)
以下为缓存接口,缓存接口和存储接口是一样的,毕竟,缓存就是对存储器做一些操作。
1
2
3
4
5
6
7
8
|
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
}
|
该接口包含了需要对缓存数据进行的所有必要操作:Set,Get,Delete,使某条缓存失效,清空缓存。如果需要的话,还可以使用 GetType 方法获取缓存类型。
缓存接口已有以下实现:
- Cache: 基础版,直接操作存储器。
- Chain: 链式缓存,它允许使用多级缓存(项目中可能同时存在内存缓存,Redis 缓存等等)。
- Loadable: 可自动加载数据的缓存,它可以指定回调函数,在缓存过期或失效的时候,会自动通过回调函数将数据加载进缓存中。
- Metric: 内嵌监控的缓存,它会收集缓存的一些指标,比如 Set、Get、失效和成功的缓存数量。
最棒的是:这些缓存器都实现了相同的接口,所以它们可以很容易地相互组合。你的缓存可以同时具有链式、可自动加载数据、包含监控等特性。
还记得吗?我们想要简单的 API,以下为使用 Memcache 的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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, // 设置过期时间
})
if err != nil {
panic(err)
}
value := cacheManager.Get("my-key")
cacheManager.Delete("my-key")
cacheManager.Clear() // 清空缓存
|
现在,假设你想要将已有的缓存修改为一个链式缓存,该缓存包含 Ristretto(内存型)和 Redis 集群,并且具备缓存数据序列化和监控特性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// 初始化 Ristretto 和 Redis 客户端
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"})
// 初始化存储器
ristrettoStore := store.NewRistretto(ristrettoCache, nil)
redisStore := store.NewRedis(redisClient, &cache.Options{Expiration: 5*time.Second})
// 初始化 Prometheus 监控
promMetrics := metrics.NewPrometheus("my-amazing-app")
// 初始化链式缓存
cacheManager := cache.NewMetric(promMetrics, cache.NewChain(
cache.New(ristrettoStore),
cache.New(redisStore),
))
// 初始化序列化工具
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"}
// 插入缓存
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
|
我们不对序列化做过多的讨论,因为这个是 Gocache 的另外一个特性:提供一套在存储和取出缓存对象时可以自动序列化和反序列化缓存对象的工具。
该特性在使用对象作为缓存 Key 时会很有用,它省去了在代码中手动转换对象的操作。
所有的这些特性:包含内存型和 Redis 的链式缓存、包含 Prometheus 监控功能和自动序列化功能,都可以在 20 行左右的代码里完成。
简单缓存
Memcache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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"), &store.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
|
Bigcache
1
2
3
4
5
6
7
8
9
10
|
bigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))
bigcacheStore := store.NewBigcache(bigcacheClient, nil) // No otions provided (as second argument)
cacheManager := cache.New(bigcacheStore)
err := cacheManager.Set("my-key", []byte("my-value"), nil)
if err != nil {
panic(err)
}
value := cacheManager.Get("my-key")
|
Ristretto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1000,
MaxCost: 100,
BufferItems: 64,
})
if err != nil {
panic(err)
}
ristrettoStore := store.NewRistretto(ristrettoCache, nil)
cacheManager := cache.New(ristrettoStore)
err := cacheManager.Set("my-key", "my-value", &store.Options{Cost: 2})
if err != nil {
panic(err)
}
value := cacheManager.Get("my-key")
cacheManager.Delete("my-key")
|
Redis
1
2
3
4
5
6
7
8
9
10
11
|
redisStore := store.NewRedis(redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
}), nil)
cacheManager := cache.New(redisStore)
err := cacheManager.Set("my-key", "my-value", &store.Options{Expiration: 15*time.Second})
if err != nil {
panic(err)
}
value := cacheManager.Get("my-key")
|
链式缓存
在这里,我们将按以下顺序链接缓存:首先在具有Ristretto存储的内存中,然后在Redis中(作为备用):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 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, &store.Options{Expiration: 5*time.Second})
// Initialize chained cache
cacheManager := cache.NewChain(
cache.New(ristrettoStore),
cache.New(redisStore),
)
// ... Then, do what you want with your cache
|
Chain
缓存还会在找到数据后将数据放回以前的高速缓存中,因此,在这种情况下,如果ristretto在其高速缓存中没有数据,但是redis在其中,则数据也将重新设置到ristretto(内存)高速缓存中。
可加载缓存
cache提供load函数, 在数据不可用时将数据重新设置在缓存中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient, nil)
// Initialize a load function that loads your data from a custom source
loadFunction := func(key interface{}) (interface{}, error) {
// ... retrieve value from available source
return &Book{ID: 1, Name: "My test amazing book", Slug: "my-test-amazing-book"}, nil
}
// Initialize loadable cache
cacheManager := cache.NewLoadable(
loadFunction,
cache.New(redisStore),
)
// ... Then, you can get your data and your function will automatically put them in cache(s)
|
当然,您也可以将一个Chain缓存传递到一个缓存中,如果您的数据在所有缓存中都不可用,Loadable将把它带回到所有缓存中。
监控
该缓存将记录metric,具体取决于您传递给metric的metric提供者。在这里,我们提供一个Prometheus提供程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient, nil)
// Initializes Prometheus metrics service
promMetrics := metrics.NewPrometheus("my-test-app")
// Initialize metric cache
cacheManager := cache.NewMetric(
promMetrics,
cache.New(redisStore),
)
// ... Then, you can get your data and metrics will be observed by Prometheus
|
序列化
某些缓存(例如Redis)存储并以字符串形式返回值,因此如果要缓存对象,则必须封送/解组结构。这就是为什么我们提供打包您的缓存并为您完成工作的封送处理程序服务的原因:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient, nil)
// Initialize chained cache
cacheManager := cache.NewMetric(
promMetrics,
cache.New(redisStore),
)
// Initializes 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"}
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
marshal.Delete("my-key")
|
您唯一需要做的就是指定调用该.Get()方法时要在其中将值解编为第二个参数的结构。
标签
您可以将一些标签附加到您创建的项目上,以便以后可以轻松使其中一些无效。
标签将使用为缓存选择的相同存储进行存储。
这是有关如何使用它的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
// Initialize Redis client and store
redisClient := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
redisStore := store.NewRedis(redisClient, nil)
// Initialize chained cache
cacheManager := cache.NewMetric(
promMetrics,
cache.New(redisStore),
)
// Initializes 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 an item in the cache and attach it a "book" tag
err = marshal.Set(key, value, store.Options{Tags: []string{"book"}})
if err != nil {
panic(err)
}
// Remove all items that have the "book" tag
err := marshal.Invalidate(store.InvalidateOptions{Tags: []string{"book"}})
if err != nil {
panic(err)
}
returnedValue, err := marshal.Get(key, new(Book))
if err != nil {
// Should be triggered because item has been deleted so it cannot be found.
panic(err)
}
|
将其与缓存上的过期时间混合使用,可以对如何缓存数据进行微调。
定制缓存
定制cache
cache遵循以下接口,因此您可以通过实现以下接口来编写自己的(专有的)缓存逻辑:
1
2
3
4
5
6
7
8
|
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
}
|
或者,如果您使用setter缓存,还可以实现以下GetCodec()方法:
1
2
3
4
5
|
type SetterCacheInterface interface {
CacheInterface
GetCodec() codec.CodecInterface
}
|
当该库中所有可用的缓存实现CacheInterface,您将能够将自己的缓存与自己的缓存混合使用。
以下示例展示了如何给对缓存的每个操作添加日志(这不是一个好主意,只是作为示例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
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
}
|
定制store
您还可以通过实现以下接口来编写自己的定制store:
1
2
3
4
5
6
7
8
|
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
}
|
当然,我建议您看看当前的缓存或存储以实现自己的缓存。
Benchmarks
