You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
3.9 KiB
180 lines
3.9 KiB
package cache |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"go-common/library/cache" |
|
"go-common/library/cache/memcache" |
|
"go-common/library/log" |
|
"go-common/library/net/metadata" |
|
"go-common/library/stat/prom" |
|
"time" |
|
) |
|
|
|
//DataLoader cache interface |
|
type DataLoader interface { |
|
Key() (key string) |
|
Value() (value interface{}) |
|
|
|
// LoadValue return value need cache |
|
// if err, nothing will cache |
|
// if value == nil, and IsNullCached is true, empty will be cached |
|
LoadValue(c context.Context) (value interface{}, err error) |
|
Expire() time.Duration |
|
Desc() string |
|
} |
|
|
|
// Get |
|
// Delete |
|
// Add |
|
|
|
//MCWrapper wrapper for mc |
|
type MCWrapper struct { |
|
mc *memcache.Pool |
|
cache *cache.Cache |
|
|
|
// 是否缓存空值,防止缓存穿透 |
|
IsNullCached bool |
|
} |
|
|
|
// null definition |
|
const ( |
|
IsNull = 1 |
|
NotNull = 0 |
|
) |
|
|
|
type cacheValue struct { |
|
Null int8 `json:"n"` // not 0 means null |
|
Value interface{} `json:"v"` |
|
} |
|
|
|
//IsNull return true is it's null |
|
func (s *cacheValue) IsNull() bool { |
|
return s.Null != NotNull |
|
} |
|
|
|
//New new memcache wrapper |
|
func New(mc *memcache.Pool) *MCWrapper { |
|
return &MCWrapper{ |
|
mc: mc, |
|
cache: cache.New(10, 1024), |
|
} |
|
} |
|
func (m *MCWrapper) addRaw(c context.Context, data DataLoader, cacheV *cacheValue) (err error) { |
|
if data == nil { |
|
return |
|
} |
|
conn := m.mc.Get(c) |
|
defer conn.Close() |
|
key := data.Key() |
|
|
|
item := &memcache.Item{Key: key, Object: cacheV, Expiration: int32(data.Expire() / time.Second), Flags: memcache.FlagJSON} |
|
if err = conn.Set(item); err != nil { |
|
actionDesc := "Add" + data.Desc() |
|
prom.BusinessErrCount.Incr("mc:" + actionDesc) |
|
log.Errorv(c, log.KV(actionDesc, fmt.Sprintf("%+v", err)), log.KV("key", key)) |
|
return |
|
} |
|
log.Info("Add key ok, key=%s, null=%d", key, cacheV.Null) |
|
return |
|
} |
|
|
|
//Add add cache data |
|
func (m *MCWrapper) Add(c context.Context, data DataLoader) (err error) { |
|
var cacheV = &cacheValue{ |
|
Value: data.Value(), |
|
} |
|
return m.addRaw(c, data, cacheV) |
|
} |
|
|
|
//Delete delete cache data |
|
func (m *MCWrapper) Delete(c context.Context, data DataLoader) (err error) { |
|
conn := m.mc.Get(c) |
|
defer conn.Close() |
|
key := data.Key() |
|
if err = conn.Delete(key); err != nil { |
|
if err == memcache.ErrNotFound { |
|
err = nil |
|
return |
|
} |
|
actionDesc := "Del" + data.Desc() |
|
prom.BusinessErrCount.Incr("mc:" + actionDesc) |
|
log.Errorv(c, log.KV(actionDesc, fmt.Sprintf("%+v", err)), log.KV("key", key)) |
|
return |
|
} |
|
return |
|
} |
|
|
|
//Get get data |
|
func (m *MCWrapper) Get(c context.Context, data DataLoader) (err error) { |
|
_, err = m.getRaw(c, data) |
|
return |
|
} |
|
|
|
func (m *MCWrapper) getRaw(c context.Context, data DataLoader) (v *cacheValue, err error) { |
|
conn := m.mc.Get(c) |
|
defer conn.Close() |
|
key := data.Key() |
|
value, err := conn.Get(key) |
|
if err != nil { |
|
if err == memcache.ErrNotFound { |
|
err = nil |
|
return |
|
} |
|
actionDesc := "Cache" + data.Desc() |
|
prom.BusinessErrCount.Incr("mc:" + actionDesc) |
|
log.Errorv(c, log.KV(actionDesc, fmt.Sprintf("%+v", err)), log.KV("key", key)) |
|
return |
|
} |
|
var cacheV = cacheValue{ |
|
Value: data.Value(), |
|
} |
|
err = conn.Scan(value, &cacheV) |
|
if err != nil { |
|
actionDesc := "Cache" + data.Desc() |
|
prom.BusinessErrCount.Incr("mc:" + actionDesc) |
|
log.Errorv(c, log.KV(actionDesc, fmt.Sprintf("%+v", err)), log.KV("key", key)) |
|
return |
|
} |
|
v = &cacheV |
|
return |
|
} |
|
|
|
//GetOrLoad get from cache, if not found, then call data.LoadValue to load |
|
func (m *MCWrapper) GetOrLoad(c context.Context, data DataLoader) (err error) { |
|
var v *cacheValue |
|
v, err = m.getRaw(c, data) |
|
if err != nil { |
|
return |
|
} |
|
|
|
if v != nil && !v.IsNull() { |
|
prom.CacheHit.Incr(data.Desc()) |
|
return |
|
} |
|
|
|
// 没有找到对应的缓存,需求去拉取 |
|
prom.CacheMiss.Incr(data.Desc()) |
|
res, err := data.LoadValue(c) |
|
if err != nil { |
|
return |
|
} |
|
|
|
// 没有查到值,并且不缓存空值 |
|
if res == nil && !m.IsNullCached { |
|
return |
|
} |
|
|
|
var cacheV = &cacheValue{ |
|
Value: res, |
|
} |
|
|
|
if res == nil { |
|
cacheV.Null = IsNull |
|
} |
|
|
|
m.cache.Save(func() { |
|
m.addRaw(metadata.WithContext(c), data, cacheV) |
|
}) |
|
return |
|
}
|
|
|