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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容

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