天天看點

gocache學習

初衷:

之前是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 

這個地方有個需要注意的事情,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是一種比較簡單的機制,适用于那些緩存資料量不大的本地緩存建構,而且防止記憶體洩漏的方式值得借鑒