本地緩存
針對使用非常頻繁的表,如某些配置表,往往查詢非常頻繁並且是表非常小。這個時候可以採取緩存到內存中,定時的去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 的時機。