Golang in-memory kv store lib - go-cache
go-cache is a in-memory key value store that used to store cache data in our services.
How to use it to store/retrieve cache data.
import (
"time"
"github.com/patrickmn/go-cache"
)
var (
// create kvstore with default expiration: 5 mins, and clean up interval: 10 mins
kvstore = cache.New(5*time.Minute, 10*time.Minute)
)
func Set(key string, value interface{}, d time.Duration) {
kvstore.Set(key, value, d)
}
func Get(key string) (value interface{}, ok bool) {
return kvstore.Get(key)
}
// 1. first we init kvstore with 5min default expiration and 10min clean up interval
// 2. set a key-value pair by call Set method with a duration.
// 3. get a value by call Get method.
// New Cache
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
items := make(map[string]Item)
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}
..
// First create a items map which used for store the key-values pairs.
// Call newCacheWithJanitor to create the underlying cache strucuture.
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
c := newCache(de, m)
C := &Cache{c}
if ci > 0 {
runJanitor(c, ci)
runtime.SetFinalizer(C, stopJanitor)
}
return C
}
func newCache(de time.Duration, m map[string]Item) *cache {
if de == 0 {
de = -1
}
c := &cache{
defaultExpiration: de,
items: m,
}
return c
}
func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop: make(chan bool),
}
c.janitor = j
go j.Run(c)
}
Create the cache structure with default expiration value and key-value map. Create janitor and start to run janitor in another goroutine.
janitor.Run
- when the clean up interval triggered, calls DeleteExpired to delete the expired keys from cache.
- when received stop signal from stop channel the ticker stops and Run exits.
func (j *janitor) Run(c *cache) {
ticker := time.NewTicker(j.Interval)
for {
select {
case <-ticker.C:
c.DeleteExpired()
case <-j.stop:
ticker.Stop()
return
}
}
}
Set
if d equals default expiration then, we use the default expiration value passed to the New, then calculate the final expiration time of the key.
lock the items key-value map to write the key: k and value: x.
func (c *cache) Set(k string, x interface{}, d time.Duration) {
var e int64
if d == DefaultExpiration {
d = c.defaultExpiration
}
if d > 0 {
e = time.Now().Add(d).UnixNano()
}
c.mu.Lock()
c.items[k] = Item{
Object: x,
Expiration: e,
}
c.mu.Unlock()
}
Get
- Lock the items map to reading the key.
- If not found the key, then just return nil value of the key, and false to indicate that the key not exists.
- If the value has expiration and already expired then also return nil, false
- Unlock the items map, and returns the value.
func (c *cache) Get(k string) (interface{}, bool) {
c.mu.RLock()
item, found := c.items[k]
if !found {
c.mu.RUnlock()
return nil, false
}
if item.Expiration > 0 {
if time.Now().UnixNano() > item.Expiration {
c.mu.RUnlock()
return nil, false
}
}
c.mu.RUnlock()
return item.Object, true
}
thoughts of go-cache
- go-cache can not let us to limit the number of keys in the cache.
- go-cache also can not limit the memory usage that it consumes.