初衷:
以前是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是一種比較簡單的機制,適用於那些緩存數據量不大的本地緩存構建,並且防止內存泄漏的方式值得借鑑