本地缓存
针对使用非常频繁的表,如某些配置表,往往查询非常频繁并且是表非常小。这个时候可以采取缓存到内存中,定时的去reload 数据,刷新缓存。
核心结构体
type LoadDataFunc func(ctx context.Context, c *cache.Cache) (map[string]interface{}, error)
type LocalCache struct {
*cache.Cache
name string // cache name
lastUpdateTime int64
ctx context.Context
ReloadIncrementInterval time.Duration // wait for reload data
loadFunc LoadDataFunc // how to load, load what
sync.Mutex
//flush bool
}
使用中还是主要依托于
"github.com/patrickmn/go-cache"
已经帮忙实现个很大一部分功能,没有必要重复造轮子。
关键实现就在于
- 如何检测时间到达然后调用reload 方法去重新load 数据。这个reload的时间间隔和load数据的时间都是应该客户应该注意的点。
- reload 方法的时间,返回全量数据,转为cache 的格式。
实现
package local_cache
import (
"context"
"fmt"
"github.com/patrickmn/go-cache"
"sync"
"time"
)
type LoadDataFunc func(ctx context.Context, c *cache.Cache) (map[string]interface{}, error)
type LocalCache struct {
*cache.Cache
name string // cache name
lastUpdateTime int64
ctx context.Context
ReloadIncrementInterval time.Duration // wait for reload data
loadFunc LoadDataFunc // how to load, load what
sync.Mutex
//flush bool
}
func (lc *LocalCache) GetName() string {
return lc.name
}
func (lc *LocalCache) String() string {
return fmt.Sprintf("name=%s lastUpdateTime:%s ReloadIncrementInterval:%d loadFunc:%s cache_len:%d", lc.name, utils.ConvertTimestampToString(lc.lastUpdateTime),
lc.ReloadIncrementInterval, utils.GetFuncName(lc.loadFunc), lc.Cache.ItemCount())
}
func (lc *LocalCache) run(ctx context.Context) {
ticker := time.NewTicker(lc.ReloadIncrementInterval)
for {
select {
case <-ticker.C:
lc.Reload(ctx)
}
}
}
func (lc *LocalCache) Run() {
// in case panic
defer func() {
if err := recover(); err != nil {
go lc.run(lc.ctx)
}
}()
// reload first
if err := lc.Reload(lc.ctx); err != nil {
panic(err)
}
go lc.run(lc.ctx)
}
func (lc *LocalCache) Clear() {
lc.Cache.Flush()
lc.lastUpdateTime = 0
}
func (lc *LocalCache) AllKey() []string {
//msg := "all key="
strList := []string{}
for k, _ := range lc.Cache.Items() {
strList = append(strList, k)
}
return strList
}
func (lc *LocalCache) Reload(ctx context.Context) error {
// add lock for reload
lc.Lock()
defer lc.Unlock()
dataMap, e := lc.loadFunc(ctx, lc.Cache)
if e != nil {
return e
}
cacheItems := transformGoCacheItem(dataMap)
lc.Cache = cache.NewFrom(-1, -1, cacheItems)
lc.lastUpdateTime = time.Now().Unix()
return nil
}
func transformGoCacheItem(m map[string]interface{}) map[string]cache.Item {
var ans = make(map[string]cache.Item)
for k, v := range m {
ans[k] = cache.Item{
Object: v,
Expiration: -1,
}
}
return ans
}
func NewLocalCache(ctx context.Context, name string, incrementInterval time.Duration, fn LoadDataFunc) *LocalCache {
c := &LocalCache{
name: name,
ctx: ctx,
ReloadIncrementInterval: incrementInterval,
loadFunc: fn,
//flush: true,
}
// need to init or get string method may panic for cache was nil
m := make(map[string]cache.Item)
c.Cache = cache.NewFrom(cache.NoExpiration, cache.NoExpiration, m)
return c
}
关键点主要就在于run 方法,开启协程,死循环去reload 数据到内存。在发生panic 时重新拉起。
使用
使用 New 方法返回实例。设置好loadFunc 以及检测时间间隔即可。再返回实例之后需要调用Run方法才算启动。
遇到的问题
- 这里注意加载的顺序问题,要等DB 连接后才能去做load 的操作,这是需要注意的地方。
- 时间间隔和load DB 时间的选择。
优化点
综合之前的检测db 更新机制。可以定时检测表结构的变化还决定reload 的时机。