gocache学习1

初衷:
以前是java工程师,最近在转go,简单学习了go的相关语言知识,想经过看些简单的源代码来提高下。go-cache是一套go语言实现的单机本地缓存的package,能够方便的构建内存缓存,代码也比较简单。java

基本的介绍:
下面这些都有一些详细的使用示例,能够去参考使用:
github代码地址:https://github.com/patrickmn/go-cache
godoc: https://godoc.org/github.com/patrickmn/go-cachegit

代码分析:
  构建缓存须要考虑几个基本点:
    1. 存储格式
    2. 替换策略
  3. 失效策略
  还有一些其余的考虑点,能够参考以前的文章,可是基本点就这些。
 一: 存储
  在gocache实现中,底层存储的key-value对类型作了基本限制,key要求是string,value内部封装了Item对象,结构以下,仅增长了当个value的实效时间

github

type Item struct {
    Object interface{}
    Expiration int64
}    


    核心的存储格式redis

type cache struct {
    defaultExpiration time.Duration //默认的通用key实效时长
    items map[string]Item //底层的map存储
    mu sync.RWMutex //因为map是非线程安全的,增长的全局锁
    onEvicted func(string, interface{})//失效key时,回触发,我本身命名为回收函数
    janitor *janitor //监视器,Goroutine,定时轮询用于失效key
}


    以上是cache的结构,set、get基本都是对items进行操做,写的时候用mu加锁,保证线程安全。
    这块有个很巧妙的设计,感受很赞,解决相互引用的问题
    首先,janitor是用于cleanup的策略对象,基本结构以下:缓存

type janitor struct {
    Interval time.Duration //定时器
    stop chan bool //goroutine的控制开关
}

    在构造cache的时候,若是有设置主动失效时间间隔,会在cache上绑定janitor线程,定时轮询items,对于失效的从items中剔除,以下:安全

//注意,janator会有cache的引用
func (j *janitor) Run(c *cache) {
    ticker := time.NewTicker(j.Interval)
    for {
        select {
             case <-ticker.C:
                   c.DeleteExpired() //剔除逻辑
             case <-j.stop:
                   ticker.Stop()
         return
    }
 }

    这个地方有个须要注意的事情,cache中绑定了janitor,而janitor run的流程中也有cache的引用,至关于循环引用了,go的垃圾回收策略是引用计数法,这种状况下,很容易形成内存泄漏。
    为了解决这个问题,引入了Cache对象(大写的),内嵌了*cache对象,对外暴露的是Cache,对cache进行一层包装。函数

func newCacheWithJanitor(de time.Duration, ci time.Duration, m 
       map[string]Item) *Cache {
       c := newCache(de, m)
       C := &Cache{c}
       if ci > 0 {
               runJanitor(c, ci)
               runtime.SetFinalizer(C, stopJanitor) //关键
       }
       return C
}

type Cache struct {
       *cache
}

 

  当外面的Cache对象指向发生变化时,Cache的引用数量为0,因此gc能够回收,可是对于cache而言,循环引用的问题依然存在,比较巧妙的是性能

    runtime.SetFinalizer(C, stopJanitor)
  在回收Cache时,stop了cleanup线程,断开了引用,是的cache也能够被正常回收,不会产生内存泄漏,感受这种写法很好玩。
学习

二:替换策略、失效策略
  gocache相对简单,用了map[string]Item来进行存储,没有限制大小,只要内存容许能够一直存,没有上限,这个在实际生产中须要注意。优化

  其余的说明:
  gocache很简单,可是也有很多问题没有作,简单列一些本身想到的,能够一块儿优化下:
    1. cache数量没有上限,这个在线上使用的时候仍是容易出问题
    2. 调用get获取对象的时候,若是对象不存在,get方法会直接返回nil,交给上层处理,实际的业务逻辑中,一般都会去redis或者db等持久化数据的地方去查,参考guava cache,感受能够写成loader的方式,if-not-exists时,直接回调loader方法
    3. 锁的粒度问题,为了保证线程安全,整个cache上锁,进行操做,会对性能有所影响,这块后续能够考虑用细粒度的锁,像concurrentHashMap或者guava cache那样,实现分段锁的机制
    4. 一些cache的命中指标没办法跟踪

总结下:gocache是一种比较简单的机制,适用于那些缓存数据量不大的本地缓存构建,并且防止内存泄漏的方式值得借鉴

相关文章
相关标签/搜索