golang語(yǔ)言之groupcache

groupcache存儲(chǔ)的是kv結(jié)構(gòu),同是memcache作者出品,官方github上說(shuō)明如下:

groupcache is a caching and cache-filling library, intended as a replacement for memcached in many cases.

也就是說(shuō)groupcache是一個(gè)kv緩存,用于在某些方面替代memcache,但是我在學(xué)習(xí)了這個(gè)框架之后,我發(fā)現(xiàn)這個(gè)框架的適用場(chǎng)景并不多,因?yàn)間roupcache只能get,不能update和delete,也不能設(shè)置過(guò)期時(shí)間,只能通過(guò)lru淘汰最近最少訪問(wèn)的數(shù)據(jù);有些數(shù)據(jù)如果長(zhǎng)時(shí)間不更改,那么可以用groupcache作為緩存;groupcache已經(jīng)在dl.Google.com、Blogger、Google

Code、Google Fiber、Google生產(chǎn)監(jiān)視系統(tǒng)等項(xiàng)目中投入使用。

但是groupcache還是有它的優(yōu)點(diǎn)的,groupcache既是服務(wù)器,也是客戶端,當(dāng)在本地groupcache緩存中沒(méi)有查找的數(shù)據(jù)時(shí),通過(guò)一致性哈希,查找到該key所對(duì)應(yīng)的peer服務(wù)器,在通過(guò)http協(xié)議,從該peer服務(wù)器上獲取所需要的數(shù)據(jù);還有一點(diǎn)就是當(dāng)多個(gè)客戶端同時(shí)訪問(wèn)memcache中不存在的鍵時(shí),會(huì)導(dǎo)致多個(gè)客戶端從mysql獲取數(shù)據(jù)并同時(shí)插入memcache中,而在相同情況下,groupcache只會(huì)有一個(gè)客戶端從mysql獲取數(shù)據(jù),其他客戶端阻塞,直到第一個(gè)客戶端獲取到數(shù)據(jù)之后,再返回給多個(gè)客戶端;

groupcache是一個(gè)緩存庫(kù),也就是說(shuō)不是一個(gè)完整的軟件,需要自己實(shí)現(xiàn)main函數(shù)??梢宰约簩?xiě)個(gè)測(cè)試程序,跑跑groupcache,我看了有些博客是直接引用Playing With Groupcache這篇博客的測(cè)試程序,這個(gè)測(cè)試程序,客戶端和groupcache通過(guò)rpc進(jìn)行通信,而groupcache peer之間通過(guò)http協(xié)議進(jìn)行通信;這是比較好的做法,因?yàn)槿绻蛻舳伺c服務(wù)器通信和groupcache之間通信采用的是同一個(gè)端口,那么在并發(fā)量上去的時(shí)候,會(huì)嚴(yán)重影響性能;下圖是這個(gè)測(cè)試程序的架構(gòu)圖:

這個(gè)原理就是如果客戶端用的是set或get命令時(shí),這時(shí)直接操作的是數(shù)據(jù)源(數(shù)據(jù)庫(kù)或文件),如果調(diào)用的是cget命令,則從groupcache中查找數(shù)據(jù);

groupcache內(nèi)部實(shí)現(xiàn)了lru和一致性哈希,我覺(jué)得大家可以看看,學(xué)習(xí)golang是如何實(shí)現(xiàn)lru和一致性哈希。下面簡(jiǎn)單分析groupcache Get函數(shù)的實(shí)現(xiàn)以及peer之間的通信;

groupcache Get函數(shù)實(shí)現(xiàn)

當(dāng)客戶端連上groupcache時(shí),能做的只有g(shù)et獲取數(shù)據(jù),如果本地有所需要的數(shù)據(jù),則直接返回,如果沒(méi)有,則通過(guò)一致性哈希函數(shù)判斷這個(gè)key所對(duì)應(yīng)的peer,然后通過(guò)http從這個(gè)peer上獲取數(shù)據(jù);如果這個(gè)peer上有需要的數(shù)據(jù),則通過(guò)http回復(fù)給之前的那個(gè)groupcache;groupcache收到之后,保存在本地hotCache中,并返回給客戶端;如果peer上也沒(méi)有所需要的數(shù)據(jù),則groupcache從數(shù)據(jù)源(數(shù)據(jù)庫(kù)或者文件)獲取數(shù)據(jù),并將數(shù)據(jù)保存在本地mainCache,并返回給客戶端;

func(g *Group)Get(ctx Context, keystring, dest Sink)error{

g.peersOnce.Do(g.initPeers)

g.Stats.Gets.Add(1)//這是groupcache狀態(tài)數(shù)據(jù),即Get的次數(shù)+1

ifdest ==nil{

returnerrors.New("groupcache: nil dest Sink")

}

//查找本地緩存,包括mainCache和hotCache

value, cacheHit := g.lookupCache(key)

ifcacheHit {

//如果命中,直接返回

g.Stats.CacheHits.Add(1)

returnsetSinkView(dest, value)

}

// 如果本地沒(méi)有命中,則從peer獲取

destPopulated :=false

value, destPopulated, err := g.load(ctx, key, dest)

iferr !=nil{

returnerr

}

ifdestPopulated {

returnnil

}

//將value賦值給dest返回

returnsetSinkView(dest, value)

}

這個(gè)Get函數(shù)很簡(jiǎn)單,先檢查本地cache是否存在,存在即返回,不存在則向peer獲取,接下來(lái)看下load函數(shù)是如何實(shí)現(xiàn)的;

func(g *Group)load(ctx Context, keystring, dest Sink)(value ByteView, destPopulatedbool, err error){

g.Stats.Loads.Add(1)

//下面這個(gè)loadGroup是保證當(dāng)數(shù)據(jù)不存在時(shí),只有一個(gè)客戶端從peer或者數(shù)據(jù)源獲取數(shù)據(jù),

//其他客戶端阻塞,直到第一個(gè)客戶端數(shù)據(jù)之后,所有客戶端再返回;這個(gè)主要是通過(guò)sync.WaitGroup實(shí)現(xiàn)

viewi, err := g.loadGroup.Do(key,func()(interface{}, error){

ifvalue, cacheHit := g.lookupCache(key); cacheHit {

g.Stats.CacheHits.Add(1)

returnvalue,nil

}

g.Stats.LoadsDeduped.Add(1)

varvalue ByteView

varerr error

ifpeer, ok := g.peers.PickPeer(key); ok {

//從peer獲取數(shù)據(jù)

value, err = g.getFromPeer(ctx, peer, key)

iferr ==nil{

g.Stats.PeerLoads.Add(1)

returnvalue,nil

}

g.Stats.PeerErrors.Add(1)

}

//從數(shù)據(jù)源獲取數(shù)據(jù)

value, err = g.getLocally(ctx, key, dest)

iferr !=nil{

g.Stats.LocalLoadErrs.Add(1)

returnnil, err

}

g.Stats.LocalLoads.Add(1)

destPopulated =true

//將數(shù)據(jù)源獲取的數(shù)據(jù)存儲(chǔ)在本地mainCache中

g.populateCache(key, value, &g.mainCache)

returnvalue,nil

})

iferr ==nil{

value = viewi.(ByteView)

}

return

}

這個(gè)load函數(shù)先是從peer獲取數(shù)據(jù),如果peer沒(méi)有數(shù)據(jù),則直接從數(shù)據(jù)源(數(shù)據(jù)庫(kù)或文件)獲取數(shù)據(jù);ok,先看下groupcache是如何從數(shù)據(jù)源獲取數(shù)據(jù),然后再分析下如果從peer中獲取數(shù)據(jù);

func(g *Group)getLocally(ctx Context, keystring, dest Sink)(ByteView, error){

err := g.getter.Get(ctx, key, dest)

iferr !=nil{

returnByteView{}, err

}

returndest.view()

}

getLocallly函數(shù)主要是利用NewGroup創(chuàng)建Group時(shí)傳進(jìn)去的Getter,在調(diào)用這個(gè)Getter的Get函數(shù)從數(shù)據(jù)源獲取數(shù)據(jù)。

funcNewGroup(namestring, cacheBytesint64, getter Getter)*Group{

returnnewGroup(name, cacheBytes, getter,nil)

}

也就是說(shuō)當(dāng)groupcache以及peer不存在所需數(shù)據(jù)時(shí),用戶可以自己定義從哪獲取數(shù)據(jù)以及如何獲取數(shù)據(jù),即定義Getter的實(shí)例即可;

從peer獲取數(shù)據(jù)

當(dāng)本地groupcache中不存在數(shù)據(jù)時(shí),會(huì)先從peer處獲取數(shù)據(jù),我們來(lái)看下getFromPeer函數(shù)實(shí)現(xiàn)

func(g *Group)getFromPeer(ctx Context, peer ProtoGetter, keystring)(ByteView, error){

//為了減少傳輸數(shù)據(jù)量,在peer之間,通過(guò)pb來(lái)傳輸數(shù)據(jù)

req := &pb.GetRequest{

Group: &g.name,

Key:? &key,

}

res := &pb.GetResponse{}

err := peer.Get(ctx, req, res)

iferr !=nil{

returnByteView{}, err

}

value := ByteView{b: res.Value}

ifrand.Intn(10) ==0{//10%的概率將從peer獲取的數(shù)據(jù)存儲(chǔ)在本地hotCache

g.populateCache(key, value, &g.hotCache)

}

returnvalue,nil

}

這個(gè)ProtoGetter是個(gè)接口,httpGetter結(jié)構(gòu)體實(shí)現(xiàn)了這個(gè)接口,而上述傳進(jìn)getFromPeer函數(shù)的peer就是httpGetter,因此,我們可以來(lái)看下httpGet這個(gè)結(jié)構(gòu)體的Get函數(shù)

func (h *httpGetter) Get(context Context,in*pb.GetRequest, out *pb.GetResponse) error {

u := fmt.Sprintf(

"%v%v/%v",

h.baseURL,

url.QueryEscape(in.GetGroup()),

url.QueryEscape(in.GetKey()),

)

req, err := http.NewRequest("GET", u, nil)

iferr != nil {

return err

}

tr:= http.DefaultTransport

ifh.transport!= nil {

tr= h.transport(context)

}

res, err :=tr.RoundTrip(req)

iferr != nil {

return err

}

defer res.Body.Close()

ifres.StatusCode!= http.StatusOK{

return fmt.Errorf("server returned: %v", res.Status)

}

//bufferPool是bytes.Buffer類(lèi)型的對(duì)象池

b:= bufferPool.Get().(*bytes.Buffer)

b.Reset()

defer bufferPool.Put(b)

_, err = io.Copy(b, res.Body)//將獲取的數(shù)據(jù)copy給b

iferr != nil {

return fmt.Errorf("reading response body: %v", err)

}

err = proto.Unmarshal(b.Bytes(), out)//將數(shù)據(jù)存在out中

iferr != nil {

return fmt.Errorf("decoding response body: %v", err)

}

return nil

}

這個(gè)函數(shù)首先向peer發(fā)起一個(gè)http請(qǐng)求,然后將請(qǐng)求得到的封裝在out *pb.GetResponse,返回給getFromPeer,并最終返回給客戶端;

總結(jié)

這篇文章主要是聊聊我對(duì)學(xué)習(xí)golang的一些看法,以及分析下groupcache的實(shí)現(xiàn)原理,分析的不是很細(xì),主要是對(duì)這個(gè)框架進(jìn)行了分析,對(duì)groupcache有了整體的認(rèn)識(shí)之后,再去看細(xì)節(jié)部分,會(huì)簡(jiǎn)單很多。

這幾天再看sqlmock開(kāi)源框架,這個(gè)主要作用就是,在單元測(cè)試時(shí)用來(lái)模擬數(shù)據(jù)庫(kù)操作;主要原理就是實(shí)現(xiàn)一個(gè)驅(qū)動(dòng)程序。在看這個(gè)sqlmock過(guò)程中,首先必須把database/sql以及go-sql-driver看懂,知道這兩個(gè)是如何一起運(yùn)作的,這樣才能了解sqlmock的實(shí)現(xiàn);過(guò)幾天再把database/sql以及go-sql-driver的實(shí)現(xiàn)原理發(fā)出來(lái)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,602評(píng)論 19 139
  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測(cè)試 ...
    KeKeMars閱讀 6,608評(píng)論 0 6
  • 充滿鮮花的世界到底在哪里 如果它真的存在那么我一定會(huì)去 你哭著對(duì)我說(shuō) 童話里都是騙人的 我不可能是你的王子
    YesFiona閱讀 159評(píng)論 0 0
  • 這些天的黃岡天氣明媚的不得了 網(wǎng)上對(duì)高溫的調(diào)侃就數(shù)不勝數(shù) 所以 姨媽和姨夫?yàn)閮冬F(xiàn)高考之前對(duì)我的承...
    三號(hào)球服閱讀 444評(píng)論 0 1
  • 繼續(xù),沉醉于藝術(shù)的世界,耕讀于真實(shí)的世界。
    紅得遠(yuǎn)閱讀 120評(píng)論 0 0

友情鏈接更多精彩內(nèi)容