• gocache学习1


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

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

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

    type Item struct {
        Object interface{}
        Expiration int64
    }    


        核心的存储格式

    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是一种比较简单的机制,适用于那些缓存数据量不大的本地缓存构建,而且防止内存泄漏的方式值得借鉴

  • 相关阅读:
    不常用函数总结
    高效update方案
    一次http完整的请求tcp报文分析
    类的初始化以及创建对象后的初始化
    [置顶] 编译背后的秘密
    html object元素
    JQuery初识
    Java多线程yield
    智能电视TV开发---直播视频客户端结构设计和实现
    以Android环境为例的多线程学习笔记(二)-----------------锁和条件机制
  • 原文地址:https://www.cnblogs.com/kakaxisir/p/10487901.html
Copyright © 2020-2023  润新知