2019-09-03, beego代码走读,四、Cache缓存

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)
}

时,好吧,这个没必要看了,浪费时间。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • NOSQL类型简介键值对:会使用到一个哈希表,表中有一个特定的键和一个指针指向特定的数据,如redis,volde...
    MicoCube阅读 4,078评论 2 27
  • 包含的重点内容:JAVA基础JVM 知识开源框架知识操作系统多线程TCP 与 HTTP架构设计与分布式算法数据库知...
    消失er阅读 4,378评论 1 10
  • 一、简介 本节将按照单个键、遍历键、数据库管理三个维度对一些通用命令进行介绍。 二、单个键管理 针对单个键的命令,...
    四月不见阅读 757评论 0 1
  • Ubuntu下安装redis 安装redis 在 Ubuntu 系统安装 Redi 可以使用以下命令: 启动 Re...
    riverstation阅读 975评论 0 0
  • Redis是一个基于内存的键值对存储系统,常用作数据库、缓存和消息代理。它支持字符串、字典、列表、集合、有序集合、...
    Four__years阅读 629评论 0 0