groupcache是用於dl.google.com的一個memcached的替代品,相對於memcached,提供更小的功能集和更高的效率,以第三方庫的形式提供服務。
groupcache的常見部署可參考下圖:
不同於memcached,groupcache面向的是靜態的緩存系統,比如google家的下載站,用來緩存對應的文件區塊。一經set操作,key對應的value就不再變化。使用的時候,需要自定義緩存缺失時使用的set操作。當發生miss的時候,首先會從根據key的consist hash值找到對應的peer,去peer里尋找對應的value。如果找不到,則使用自定義的get函數從慢緩存(比如db,文件讀取)獲取alue,並填充對應peer。下一次獲取,就直接從cache里取出來,不再訪問慢緩存。另外,為避免網絡變成瓶頸,本地peer獲取cache后,會存在本地的localCache里,通過LRU算法進行管理。
groupcache的代碼分為consistenhash, groupcachepb, lru, singleflight等幾個目錄,分別存放一致性哈希,groupcache的protobuf協議,lru算法實現,用來保證求值操作只執行一次的singleflight。本篇主要看看singleflight。
23 // call is an in-flight or completed Do call
24 type call struct {
25 wg sync.WaitGroup
26 val interface{}
27 err error
28 }
這里定義了一個call結構,包含了用來同步的wg等待組,一個用於承載任意值的val,以及err錯誤信息。
32 type Group struct {
33 mu sync.Mutex // protects m
34 m map[string]*call // lazily initialized
35 }
這里定義的是Group結構,一個Group內key是唯一的,類似於命名空間。groupcache使用了Mutex保護map變量。
41 func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
42 g.mu.Lock()
43 if g.m == nil {
44 g.m = make(map[string]*call)
45 }
46 if c, ok := g.m[key]; ok {
47 g.mu.Unlock()
48 c.wg.Wait()
49 return c.val, c.err
50 }
51 c := new(call)
52 c.wg.Add(1)
53 g.m[key] = c
54 g.mu.Unlock()
55
56 c.val, c.err = fn()
57 c.wg.Done()
58
59 g.mu.Lock()
60 delete(g.m, key)
61 g.mu.Unlock()
62
63 return c.val, c.err
64 }
這里Do定義的就是一個帶鎖保護的保證單次執行的函數。因為consistent hash定位到的peer是唯一的,因此這里只需要同步該進程內的goroutine即可。為了減少鎖的影響,鎖控制的范圍被切成了好幾段。整個完整流程包括3步:1. 初始化g.m變量 2. 賦值g.m[key]變量為val 3.刪除g.m的key。第一步會加鎖,第一個進入的go程會走到第二步進行賦值操作,到其他go程進入1的時候,第二步key對應的值已經有了,可以直接釋放鎖了。然后到第三步,第一個進入的go程會加鎖,然后刪除key,再解鎖。前面盡早釋放鎖,就是為了第三步這里不需要長久的等待。