gocache
Go cache library that brings you multiple ways of managing your caches.
Features
- Provides a cache store abstraction layer.
- Synchronize memory cache across multiple servers.
- Multi-stage cache.
Installation
This library is currently in alpha version.
go get github.com/knocknote/gocache@develop
Install additional libraries depending on the cache store you want to use.
Cache Store
Store |
Local/Remote |
Module |
Sync.Map |
Local |
go get github.com/knocknote/gocache/store/memcache@develop |
Ristretto |
Local |
go get github.com/knocknote/gocache/store/ristretto@develop |
Memcached |
Remote |
go get github.com/knocknote/gocache/store/memcache@develop |
Redis |
Remote |
go get github.com/knocknote/gocache/store/redis@develop |
Synchronizer
Synchronizer provides local cache synchronization across multiple servers.
If the cache exists with the same key, the cache occurrence time is compared and the received data is updated if it is new.
Synchronizer |
Module |
Redis Pub/Sub |
go get github.com/knocknote/gocache/synchronizer/redis@develop |
Google Cloud Pub/Sub |
go get github.com/knocknote/gocache/synchronizer/cloudpubsub@develop |
API
- gocache has three core interfaces
Cache
, Synchronizer
, Marshaler
.
- You can create any cache behavior by implementing these interfaces.
Cache
Method |
Description |
Set |
Set data into cache. |
Get |
Get data from cache. Even if it is redis or memcached, you can restore it to [] byte struct and get it. |
Delete |
Delete the data corresponding to the specified key. |
Clear |
Delete all data in the cache. |
Synchronizer
Synchronizer is required to use local cache synchronization.
Method |
Description |
Start |
Synchronization with other servers or initialization of the pubsub mechanism. |
Stop |
Synchronization with other servers or termination processing of the pubsub mechanism. |
AddSubscriber |
Add the processing when the published data is received. |
RemoveSubscriber |
Remove the processing when the published data is received. |
Publish |
Send data to another server's cache store or pubsub mechanism |
Marshaler
- Marshaler is needless to use local cache.
- Use marshaler to marshal/unmarshal data from redis or memcached.
- Standard library includes json and msgpack marshaler.
Method |
Description |
Marshal |
struct -> []byte |
Unmarshal |
[]byte -> struct |
Usage
Here is the repository example.
var defaultOption = gocache.Option{
TTL: 30 * time.Minute,
}
type RepositoryProxy struct {
cache gocache.Cache
target repository.Repository
}
func NewRepositoryProxy(cache gocache.Cache, target repository.Repository) *RepositoryProxy {
return &RepositoryProxy{
target: target,
cache: cache,
}
}
func (r *RepositoryProxy) FindByID(ctx context.Context, userID string) (*model.User, error) {
key := r.cacheKey(ctx, userID)
var result model.User
if v, err, unmarshalFn := r.cache.Get(ctx, key); err == nil {
// if the cache is redis or memcached unmarshalFn is not null. `v` is the raw byte array
if unmarshalFn != nil {
return &result, unmarshalFn(&result)
// if the cache is not redis and memcached unmarshalFn is null
} else {
result = v.(model.User)
return &result, nil
}
}
user, err := r.target.FindByID(ctx, userID)
if err != nil {
return nil, err
}
return user, r.cache.Set(ctx, key, user, &defaultOption)
}
func (r *RepositoryProxy) Insert(ctx context.Context, target *model.User) error {
key := r.cacheKey(ctx, target.UserID)
if err := r.cache.Set(ctx, key, *target, &defaultOption); err != nil {
return err
}
return r.target.Insert(ctx, target)
}
func (r *RepositoryProxy) cacheKey(_ context.Context, userID string) string {
return fmt.Sprintf("RepositoryProxy.FindByID(userId=%s)", userID)
}
Sync.Map (without cache synchronization)
import (
gocache "github.com/knocknote/gocache/store/syncmap"
)
func DependencyInjection() {
// first args = synchronizer
// second args is cleanup interval (-1 means no cleanup job start)
// third args is default option(such as TTL)
cache := gocache.NewCache(nil, -1, nil)
repository := NewDefaultRepository()
repositoryProxy := NewRepositoryProxy(cache, repository)
// start cleanup worker
_ = cache.Start(ctx)
}
Sync.Map (with cache synchronization)
import (
gocache "github.com/knocknote/gocache/store/syncmap"
)
func DependencyInjection() {
client, _ := pubsub.NewClient(context.Background(), "local-project")
topic := client.Topic("topic")
subscription := client.Subscription("subs")
serializer := marshaler.NewMsgpack()
synchronizer := cloudpubsub.NewSynchronizer(serializer, topic, subscription)
// `cache.Set(key,value,..)` set local value and publish message to topic)
cache := syncmap.NewCache(synchronizer, -1, nil)
_ = cache.Start(context.Backend())
// start to subscribe topic. when it receive message then update cache.
_ = synchronizer.Start(context.Backend())
}
Ristretto(without cache synchronization)
import (
store "github.com/dgraph-io/ristretto"
gocache "github.com/knocknote/gocache/store/ristretto"
)
func DependencyInjection() {
targetStore, err := store.NewCache(&store.Config{
NumCounters: 100,
MaxCost: 1 << 30,
BufferItems: 64,
})
// second args = synchronizer
// third args is default option(such as TTL / Cost)
cache := gocache.NewCache(targetStore, nil, nil)
repository := NewDefaultRepository()
repositoryProxy := NewRepositoryProxy(cache, repository)
}
Ristretto (with cache synchronization)
import (
"github.com/dgraph-io/ristretto"
gocache "github.com/knocknote/gocache/store/ristretto"
)
func DependencyInjection() {
targetStore, err := ristretto.NewCache(&store.Config{
NumCounters: 100,
MaxCost: 1 << 30,
BufferItems: 64,
})
client, _ := pubsub.NewClient(context.Background(), "local-project")
topic := client.Topic("topic")
subscription := client.Subscription("subs")
serializer := marshaler.NewMsgpack()
synchronizer := cloudpubsub.NewSynchronizer(serializer, topic, subscription)
// `cache.Set(key,value,..)` set local value and publish message to topic)
cache := gocache.NewCache(targetStore, nil, nil)
_ = cache.Start(context.Backend())
// start to subscribe topic. when it receive message then update cache.
_ = synchronizer.Start(context.Backend())
}
Memcached
import (
"github.com/bradfitz/gomemcache/memcache"
gocache "github.com/knocknote/gocache/store/memcache"
)
func DependencyInjection() {
client := memcache.New("localhost:11211")
clientProvider := func(ctx context.Context) *memcache.Client {
return client
}
cache := gocache.NewCache(marshaler.NewMsgpack(), clientProvider, nil)
repository := NewDefaultRepository()
repositoryProxy := NewRepositoryProxy(cache, repository)
}
Redis
import (
"github.com/go-redis/redis"
gocache "github.com/knocknote/gocache/store/redis"
)
func DependencyInjection() {
client := redis.NewClient(&redis.Options{
Addr: opt.Addr,
Password: opt.Password,
DB: opt.DB,
})
clientProvider := func(ctx context.Context) (reader redis.Cmdable, writer redis.Cmdable) {
return client, client
}
cache := gocache.NewCache(marshaler.NewMsgpack(), clientProvider, nil)
repository := NewDefaultRepository()
repositoryProxy := NewRepositoryProxy(cache, repository)
}
Composite Cache
Composite Cache provides cache chain.
import (
"github.com/dgraph-io/ristretto"
"github.com/go-redis/redis"
"github.com/knocknote/gocache"
gocacheristretto "github.com/knocknote/gocache/store/ristretto"
gocacheredis "github.com/knocknote/gocache/store/redis"
)
func DependencyInjection() {
// create L2 cache
redisClient := redis.NewClient(&redis.Options{
Addr: opt.Addr,
Password: opt.Password,
DB: opt.DB,
})
redisClientProvider := func(ctx context.Context) (reader redis.Cmdable, writer redis.Cmdable) {
return client, client
}
l2cache := gocacheredis.NewCache(marshaler.NewMsgpack(), clientProvider, nil)
// create L1 cache
ristrettoStore, err := ristretto.NewCache(&store.Config{
NumCounters: 100,
MaxCost: 1 << 30,
BufferItems: 64,
}
l1cache := ristretto.NewCache(ristrettoStore, nil, nil)
// create composite cache
cache := gocache.NewCompositeCache(l1cache, l2cache)
repository := NewDefaultRepository()
repositoryProxy := NewRepositoryProxy(cache, repository)
}
Change Date Provider and Logger
You can change default Logger and Date provider.
import (
"github.com/knocknote/gocache"
)
func DependencyInjection() {
gocache.Logger = any logger(such as *zap.SugaredLogger)
gocache.Now = func(ctx context.Context) time.Time {
return appContext.RequestScopedTime(ctx)
}
}