失去了文法高量,更好閱讀體驗參見原文:
https://www.cnblogs.com/cloudgeek/p/9497801.html
今天咱一次講3個吧,趕一下進度,好早點開始聊kubernetes!
從groupcache的項目目錄結構看,我們今天要學習groupcachepb、lru、singleflight這3個package:
一、protobuf
這個目錄咋一看有2個檔案:go和proto尾碼的。proto尾碼的檔案和protocol buffers有關,所以先看看protocol buffers是什麼吧。
在github上可以看到這個項目:https://github.com/google/protobuf
google的,是不是瞬間來了興趣?
官方介紹是: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} 910message GetResponse {11 optional bytes value = 1;12 optional double minute_qps = 2;13}1415service GroupCache {16 rpc Get(GetRequest) returns (GetResponse) {17 };18}
可以看到這是某種文法的資料定義格式,我們先介紹一下這裡涉及的概念:
protobuf中主要資料類型有:
標準資料類型:整型,浮點,字串等
複合資料型別:枚舉和message類型
看message部分:
message GetResponse {
optional bytes value = 1;
optional double minute_qps = 2;
}
現在我們可以看懂這個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要有關係了,細節我們先不講,到後面調用到的地方咱再結合業務代碼來理解這裡的細節。
二、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}100101// RemoveOldest removes the oldest item from the cache.102//【刪除最舊的元素】103func (c *Cache) RemoveOldest() {104 if c.cache == nil {105 return106 }107 //【ele為*list.Element類型,指向鏈表的尾結點】108 ele := c.ll.Back()109 if ele != nil {110 c.removeElement(ele)111 }112}113114func (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}126127// Len returns the number of items in the cache.128//【返回緩衝中的item數,通過鏈表的Len()方法擷取】129func (c *Cache) Len() int {130 if c.cache == nil {131 return 0132 }133 return c.ll.Len()134}135136// 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 = nil146 c.cache = nil147}
三、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.WaitGroup12 val interface{}13 err error14}1516// Group represents a class of work and forms a namespace in which17// units of work can be executed with duplicate suppression.18//【表示一類工作,組成一個命名空間的概念,一個group的調用會有“重複抑制”】19type Group struct {20 mu sync.Mutex // protects m21 //【懶惰地初始化;這個map的value是*call,call是上面那個struct】22 m map[string]*call // lazily initialized23}2425// Do executes and returns the results of the given function, making26// sure that only one execution is in-flight for a given key at a27// time. If a duplicate comes in, the duplicate caller waits for the28// original to complete and receives the same results.2930//【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.err44 }45 //【拿到call結構體類型的指標】46 c := new(call)47 //【一個goroutine開始,Add(1),這裡最多隻會執行到一次,也就是不會並發調用下面的fn()】48 c.wg.Add(1)49 //【類似設定一個函數調用的名字“key”對應調用過程c】50 g.m[key] = c51 g.mu.Unlock()5253 //【函數調用過程】54 c.val, c.err = fn()55 //【這裡的Done對應上面if裡面的Wait】56 c.wg.Done()5758 g.mu.Lock()59 //【執行完成,刪除這個key】60 delete(g.m, key)61 g.mu.Unlock()6263 return c.val, c.err64}
今天講的可能有點多,其中設計到的List之類的沒有細講,希望大家通過互連網掌握這類我沒有仔細提到的小知識點,徹底吃透這幾個package中的源碼。
回過頭看一下項目結果,除了testpb包外其他包我們都講完了,testpb是groupcachepb對應的測試程式,下一講我們就可以把這幾個包外的所有程式分析完,包括對protobuf部分的調用邏輯。
今天就到這裡,groupcache源碼解析還剩最後一講!
92 次點擊