Cache是一个非常常用的模块,在beego中提供了Redis/MemCached/SSDB/File/MemoryCache等。
Cache的实现.png
type Cache interface {
// get cached value by key.
Get(key string) interface{}
// GetMulti is a batch version of Get.
GetMulti(keys []string) []interface{}
// set cached value with key and expire time.
Put(key string, val interface{}, timeout time.Duration) error
// delete cached value by key.
Delete(key string) error
// increase cached int value by key, as a counter.
Incr(key string) error
// decrease cached int value by key, as a counter.
Decr(key string) error
// check if cached value exists or not.
IsExist(key string) bool
// clear all cache.
ClearAll() error
// start gc routine based on config string settings.
StartAndGC(config string) error
}
除了常见的方法,有一个StartAndGC
很特别,我们来看一下。
先来看看Redis版本的实现。
// StartAndGC start redis cache adapter.
// config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
// the cache item in redis are stored forever,
// so no gc operation.
the cache item in redis are stored forever, so no gc operation.
所以Redis版本没有GC操作。
// Put put cache to redis.
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
_, err := rc.do("SETEX", key, int64(timeout/time.Second), val)
return err
}
利用了redis的SETEX
超时功能,无需GC啦。
PS: 代码中有一段,有待商榷啊。
// ClearAll clean all cache in redis. delete this redis collection.
func (rc *Cache) ClearAll() error {
c := rc.p.Get()
defer c.Close()
cachedKeys, err := redis.Strings(c.Do("KEYS", rc.key+":*"))
if err != nil {
return err
}
for _, str := range cachedKeys {
if _, err = c.Do("DEL", str); err != nil {
return err
}
}
return err
}
直接使用了KEYS
指令,这个命令不应该是洪水猛兽吗?当然,加了pattern会好一些。
KEYS 洪水猛兽,Redis的KEYS命令引起RDS数据库雪崩
Memcached版本的实现
// Put put value to memcache.
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
if v, ok := val.([]byte); ok {
item.Value = v
} else if str, ok := val.(string); ok {
item.Value = []byte(str)
} else {
return errors.New("val only support string and []byte")
}
return rc.conn.Set(&item)
}
Memcached版本也是使用了Memcached的超时功能,没有单独实现。
Memchached版本的ClearAll使用的是FlushAll
操作,这么不负责的吗?Redis版本进行了Pattern过滤,但是Memcached简单粗暴直接FlushAll
了,完全两种实现思路,看来不是出自一人之手。
SSDB版本的实现
GC依旧没有进行操作,在Put时,使用setx
指令进行了超时设置,由SSDB来控制数据的清理。
// ClearAll clear all cached in memcache.
func (rc *Cache) ClearAll() error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
keyStart, keyEnd, limit := "", "", 50
resp, err := rc.Scan(keyStart, keyEnd, limit)
for err == nil {
size := len(resp)
if size == 1 {
return nil
}
keys := []string{}
for i := 1; i < size; i += 2 {
keys = append(keys, resp[i])
}
_, e := rc.conn.Do("multi_del", keys)
if e != nil {
return e
}
keyStart = resp[size-2]
resp, err = rc.Scan(keyStart, keyEnd, limit)
}
return err
}
这个版本的ClearAll貌似又是一种新思路,进行分页清理,但是也是Scan
了所有的key。
自有缓存的版本
MemoryCache
看到了一把读写锁。
// MemoryCache is Memory cache adapter.
// it contains a RW locker for safe map storage.
type MemoryCache struct {
sync.RWMutex
dur time.Duration
items map[string]*MemoryItem
Every int // run an expiration check Every clock time
}
其实直接使用sync.Map
的实现,性能会更高一些。
锁是性能的大忌,一看到锁就必须精神为之一振。 这个版本的实现好像没什么特别的,就是一个map做缓存。
这个版本的GC使用了一个单独协程扫描,而且还是fixedDelay的方式扫描,真心不能算是一个好的实现。
// StartAndGC start memory cache. it will check expiration in every clock time.
func (bc *MemoryCache) StartAndGC(config string) error {
var cf map[string]int
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["interval"]; !ok {
cf = make(map[string]int)
cf["interval"] = DefaultEvery
}
dur := time.Duration(cf["interval"]) * time.Second
bc.Every = cf["interval"]
bc.dur = dur
go bc.vacuum()
return nil
}
// check expiration.
func (bc *MemoryCache) vacuum() {
bc.RLock()
every := bc.Every
bc.RUnlock()
if every < 1 {
return
}
for {
<-time.After(bc.dur)
if bc.items == nil {
return
}
if keys := bc.expiredKeys(); len(keys) != 0 {
bc.clearItems(keys)
}
}
}
FileCache版本
当看到
// Put value into file cache.
// timeout means how long to keep this file, unit of ms.
// if timeout equals fc.EmbedExpiry(default is 0), cache this item forever.
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
gob.Register(val)
item := FileCacheItem{Data: val}
if timeout == time.Duration(fc.EmbedExpiry) {
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
} else {
item.Expired = time.Now().Add(timeout)
}
item.Lastaccess = time.Now()
data, err := GobEncode(item)
if err != nil {
return err
}
return FilePutContents(fc.getCacheFileName(key), data)
}
// FilePutContents Put bytes to file.
// if non-exist, create this file.
func FilePutContents(filename string, content []byte) error {
return ioutil.WriteFile(filename, content, os.ModePerm)
}
时,好吧,这个没必要看了,浪费时间。