今天咱一次講3個吧,趕一下進度,好早點開始聊kubernetes!git
從groupcache的項目目錄結構看,咱們今天要學習groupcachepb、lru、singleflight這3個package:github
1、protobuf
這個目錄咋一看有2個文件:go和proto後綴的。proto後綴的文件和protocol buffers有關,因此先看看protocol buffers是什麼吧。golang
在github上能夠看到這個項目:https://github.com/google/protobuf算法
google的,是否是瞬間來了興趣?c#
官方介紹是:Protocol Buffers (a.k.a., protobuf) are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data.簡單說就是跨語言跨平臺的可拓展的結構數據序列化用的。翻譯着有點彆扭,仍是直接看英文好理解。。。行,如今你們知道這個是用來作數據序列化的了,你們是否記得Golang自帶的一個數據結構序列化編碼/解碼工具gob?以前咱們有專門介紹過:《golang - gob與rpc》。緩存
ok,看過gob這篇文章,你們就知道protobuf須要解決的基本問題了,下面咱們結合源碼來看protobuf的知識點。安全
$GOPATH\src\github.com\golang\groupcache\groupcachepb\groupcache.proto內容以下:數據結構
1syntax = "proto2";
2
3package groupcachepb;
4
5message GetRequest {
6 required string group = 1;
7 required string key = 2; // not actually required/guaranteed to be UTF-8
8}
9
10message GetResponse {
11 optional bytes value = 1;
12 optional double minute_qps = 2;
13}
14
15service GroupCache {
16 rpc Get(GetRequest) returns (GetResponse) {
17 };
18}
能夠看到這是某種語法的數據定義格式,咱們先介紹一下這裏涉及的概念:併發
protobuf中主要數據類型有:app
-
標準數據類型:整型,浮點,字符串等
-
複合數據類型:枚舉和message類型
看message部分:
message GetResponse {
optional bytes value = 1;
optional double minute_qps = 2;
}
-
每一個字段末尾有一個tag,這個tag要求不重複,如這裏的一、2;
-
每一個字段有一個類型,如這裏的bytes、double;
-
每一個字段開頭的optional含義爲:
-
required: 必須賦值,不能爲空
-
optional:能夠賦值,也能夠不賦值
-
repeated: 該字段能夠重複任意次數,包括0次
如今咱們能夠看懂這個message的名字是GetResponse,有2個可選字段value和minute_qps,兩個字段的類型分別爲bytes和double,2個字段都是optional的。
protobuf也提供了包的定義,只要在文件開頭定義package關鍵字便可,因此這裏的package groupcachepb;這行也好理解;第一行syntax = "proto2";明顯是聲明版本的,除了proto2外還有proto3版本,相似與py2後有了py3。
到這裏就剩下最後幾行有點疑惑了:
service GroupCache {
rpc Get(GetRequest) returns (GetResponse) {
};
}
這裏能夠看到打頭的是service,中間的字段是一個rpc相關的相似函數的東西,參數和返回值都是上面定義的message:GetRequest和GetResponse,明顯這裏和rpc要有關係了,細節咱們先不講,到後面調用到的地方咱再結合業務代碼來理解這裏的細節。
2、LRU
查一下百度百科,能夠獲得LRU的解釋以下:
內存管理的一種頁面置換算法,對於在內存中但又不用的數據塊(內存塊)叫作LRU,操做系統會根據哪些數據屬於LRU而將其移出內存而騰出空間來加載另外的數據。
什麼是LRU算法? LRU是Least Recently Used的縮寫,即最近最少使用,經常使用於頁面置換算法,是爲虛擬頁式存儲管理服務的。
因此這裏的lru包也就是用來實現lru算法的,詳細的解釋我放在註釋中:$GOPATH\src\github.com\golang\groupcache\lru\lru.go:
1// Package lru implements an LRU cache.
2//【lru包用於實現LRU cache】
3package lru
4
5import "container/list"
6
7// Cache is an LRU cache. It is not safe for concurrent access.
8//【Cache結構用於實現LRU cache算法;併發訪問不安全】
9type Cache struct {
10 // MaxEntries is the maximum number of cache entries before
11 // an item is evicted. Zero means no limit.
12 //【最大入口數,也就是緩存中最多存幾條數據,超過了就觸發數據淘汰;0表示沒有限制】
13 MaxEntries int
14
15 // OnEvicted optionally specificies a callback function to be
16 // executed when an entry is purged from the cache.
17 //【銷燬前回調】
18 OnEvicted func(key Key, value interface{})
19
20 //【鏈表】
21 ll *list.List
22 //【key爲任意類型,值爲指向鏈表一個結點的指針】
23 cache map[interface{}]*list.Element
24}
25
26// A Key may be any value that is comparable.
27// See http://golang.org/ref/spec#Comparison_operators
28//【任意可比較類型】
29type Key interface{}
30
31//【訪問入口結構,包裝鍵值】
32type entry struct {
33 key Key
34 value interface{}
35}
36
37// New creates a new Cache.
38// If maxEntries is zero, the cache has no limit and it's assumed
39// that eviction is done by the caller.
40//【初始化一個Cache類型實例】
41func New(maxEntries int) *Cache {
42 return &Cache{
43 MaxEntries: maxEntries,
44 ll: list.New(),
45 cache: make(map[interface{}]*list.Element),
46 }
47}
48
49// Add adds a value to the cache.
50//【往緩存中增長一個值】
51func (c *Cache) Add(key Key, value interface{}) {
52 //【若是Cache尚未初始化,先初始化,建立cache和l1】
53 if c.cache == nil {
54 c.cache = make(map[interface{}]*list.Element)
55 c.ll = list.New()
56 }
57 //【若是key已經存在,則將記錄前移到頭部,而後設置value】
58 if ee, ok := c.cache[key]; ok {
59 c.ll.MoveToFront(ee)
60 ee.Value.(*entry).value = value
61 return
62 }
63 //【key不存在時,建立一條記錄,插入鏈表頭部,ele是這個Element的指針】
64 //【這裏的Element是一個*entry類型,ele是*list.Element類型】
65 ele := c.ll.PushFront(&entry{key, value})
66 //cache這個map設置key爲Key類型的key,value爲*list.Element類型的ele
67 c.cache[key] = ele
68 //【鏈表長度超過最大入口值,觸發清理操做】
69 if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
70 c.RemoveOldest()
71 }
72}
73
74// Get looks up a key's value from the cache.
75//【根據key查找value】
76func (c *Cache) Get(key Key) (value interface{}, ok bool) {
77 if c.cache == nil {
78 return
79 }
80 //【若是存在】
81 if ele, hit := c.cache[key]; hit {
82 //【將這個Element移動到鏈表頭部】
83 c.ll.MoveToFront(ele)
84 //【返回entry的值】
85 return ele.Value.(*entry).value, true
86 }
87 return
88}
89
90// Remove removes the provided key from the cache.
91//【若是key存在,調用removeElement刪除鏈表and緩存中的元素】
92func (c *Cache) Remove(key Key) {
93 if c.cache == nil {
94 return
95 }
96 if ele, hit := c.cache[key]; hit {
97 c.removeElement(ele)
98 }
99}
100
101// RemoveOldest removes the oldest item from the cache.
102//【刪除最舊的元素】
103func (c *Cache) RemoveOldest() {
104 if c.cache == nil {
105 return
106 }
107 //【ele爲*list.Element類型,指向鏈表的尾結點】
108 ele := c.ll.Back()
109 if ele != nil {
110 c.removeElement(ele)
111 }
112}
113
114func (c *Cache) removeElement(e *list.Element) {
115 //【鏈表中刪除一個element】
116 c.ll.Remove(e)
117 //【e.Value本質是*entry類型,entry結構體就包含了key和value2個屬性】
118 //【Value自己是interface{}類型,經過類型斷言轉成*entry類型】
119 kv := e.Value.(*entry)
120 //【刪除cache這個map中key爲kv.key這個元素;也就是鏈表中刪了以後緩存中也得刪】
121 delete(c.cache, kv.key)
122 if c.OnEvicted != nil {
123 c.OnEvicted(kv.key, kv.value)
124 }
125}
126
127// Len returns the number of items in the cache.
128//【返回緩存中的item數,經過鏈表的Len()方法獲取】
129func (c *Cache) Len() int {
130 if c.cache == nil {
131 return 0
132 }
133 return c.ll.Len()
134}
135
136// Clear purges all stored items from the cache.
137//【刪除緩存中全部條目,若是有回調函數OnEvicted(),則先調用全部回調函數,而後置空】
138func (c *Cache) Clear() {
139 if c.OnEvicted != nil {
140 for _, e := range c.cache {
141 kv := e.Value.(*entry)
142 c.OnEvicted(kv.key, kv.value)
143 }
144 }
145 c.ll = nil
146 c.cache = nil
147}
3、singleflight
這個package主要實現了這樣一個功能:抑制同一個函數調用重複執行。舉個例子:給一個常規程序輸入一個函數調用A()須要10s返回結果,這時候有10個客戶端都調用了這個A(),可能就須要100s才能完成全部的計算結果,可是這個計算是重複的,結果也是同樣的。因此能夠想個辦法,判斷是同一個計算過程的狀況,不須要重複執行,直接等待上一次計算完成,而後一會兒返回結果就好了。下面看一下groupcache中是如何實現這個算法的吧:
1// Package singleflight provides a duplicate function call suppression
2// mechanism.
3//【「單航班」提供重複調用函數的抑制機制】
4package singleflight
5
6import "sync"
7
8// call is an in-flight or completed Do call
9//【在執行的或者已經完成的Do過程】
10type call struct {
11 wg sync.WaitGroup
12 val interface{}
13 err error
14}
15
16// Group represents a class of work and forms a namespace in which
17// units of work can be executed with duplicate suppression.
18//【表示一類工做,組成一個命名空間的概念,一個group的調用會有「重複抑制」】
19type Group struct {
20 mu sync.Mutex // protects m
21 //【懶惰地初始化;這個map的value是*call,call是上面那個struct】
22 m map[string]*call // lazily initialized
23}
24
25// Do executes and returns the results of the given function, making
26// sure that only one execution is in-flight for a given key at a
27// time. If a duplicate comes in, the duplicate caller waits for the
28// original to complete and receives the same results.
29
30//【Do接收一個函數,執行並返回結果,
31// 這個過程當中確保同一個key在同一時間只有一個執行過程;
32// 重複的調用會等待最原始的調用過程完成,而後接收到相同的結果】
33func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
34 g.mu.Lock()
35 if g.m == nil {
36 g.m = make(map[string]*call)
37 }
38 //【若是這個call存在同名過程,等待初始調用完成,而後返回val和err】
39 if c, ok := g.m[key]; ok {
40 g.mu.Unlock()
41 c.wg.Wait()
42 //【當全部goroutine執行完畢,call中就存儲了執行結果val和err,而後這裏返回】
43 return c.val, c.err
44 }
45 //【拿到call結構體類型的指針】
46 c := new(call)
47 //【一個goroutine開始,Add(1),這裏最多隻會執行到一次,也就是不會併發調用下面的fn()】
48 c.wg.Add(1)
49 //【相似設置一個函數調用的名字「key」對應調用過程c】
50 g.m[key] = c
51 g.mu.Unlock()
52
53 //【函數調用過程】
54 c.val, c.err = fn()
55 //【這裏的Done對應上面if裏面的Wait】
56 c.wg.Done()
57
58 g.mu.Lock()
59 //【執行完成,刪除這個key】
60 delete(g.m, key)
61 g.mu.Unlock()
62
63 return c.val, c.err
64}
今天講的可能有點多,其中設計到的List之類的沒有細講,但願你們經過互聯網掌握這類我沒有仔細提到的小知識點,完全吃透這幾個package中的源碼。
回過頭看一下項目結果,除了testpb包外其餘包咱們都講完了,testpb是groupcachepb對應的測試程序,下一講咱們就能夠把這幾個包外的全部程序分析完,包括對protobuf部分的調用邏輯。
今天就到這裏,groupcache源碼解析還剩最後一講!