golang语言之groupcache

groupcache存储的是kv结构,同是memcache作者出品,官方github上说明如下:

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

也就是说groupcache是一个kv缓存,用于在某些方面替代memcache,但是我在学习了这个框架之后,我发现这个框架的适用场景并不多,因为groupcache只能get,不能update和delete,也不能设置过期时间,只能通过lru淘汰最近最少访问的数据;有些数据如果长时间不更改,那么可以用groupcache作为缓存;groupcache已经在dl.Google.com、Blogger、Google

Code、Google Fiber、Google生产监视系统等项目中投入使用。

但是groupcache还是有它的优点的,groupcache既是服务器,也是客户端,当在本地groupcache缓存中没有查找的数据时,通过一致性哈希,查找到该key所对应的peer服务器,在通过http协议,从该peer服务器上获取所需要的数据;还有一点就是当多个客户端同时访问memcache中不存在的键时,会导致多个客户端从mysql获取数据并同时插入memcache中,而在相同情况下,groupcache只会有一个客户端从mysql获取数据,其他客户端阻塞,直到第一个客户端获取到数据之后,再返回给多个客户端;

groupcache是一个缓存库,也就是说不是一个完整的软件,需要自己实现main函数。可以自己写个测试程序,跑跑groupcache,我看了有些博客是直接引用Playing With Groupcache这篇博客的测试程序,这个测试程序,客户端和groupcache通过rpc进行通信,而groupcache peer之间通过http协议进行通信;这是比较好的做法,因为如果客户端与服务器通信和groupcache之间通信采用的是同一个端口,那么在并发量上去的时候,会严重影响性能;下图是这个测试程序的架构图:

这个原理就是如果客户端用的是set或get命令时,这时直接操作的是数据源(数据库或文件),如果调用的是cget命令,则从groupcache中查找数据;

groupcache内部实现了lru和一致性哈希,我觉得大家可以看看,学习golang是如何实现lru和一致性哈希。下面简单分析groupcache Get函数的实现以及peer之间的通信;

groupcache Get函数实现

当客户端连上groupcache时,能做的只有get获取数据,如果本地有所需要的数据,则直接返回,如果没有,则通过一致性哈希函数判断这个key所对应的peer,然后通过http从这个peer上获取数据;如果这个peer上有需要的数据,则通过http回复给之前的那个groupcache;groupcache收到之后,保存在本地hotCache中,并返回给客户端;如果peer上也没有所需要的数据,则groupcache从数据源(数据库或者文件)获取数据,并将数据保存在本地mainCache,并返回给客户端;

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

g.peersOnce.Do(g.initPeers)

g.Stats.Gets.Add(1)//这是groupcache状态数据,即Get的次数+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)

}

// 如果本地没有命中,则从peer获取

destPopulated :=false

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

iferr !=nil{

returnerr

}

ifdestPopulated {

returnnil

}

//将value赋值给dest返回

returnsetSinkView(dest, value)

}

这个Get函数很简单,先检查本地cache是否存在,存在即返回,不存在则向peer获取,接下来看下load函数是如何实现的;

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

g.Stats.Loads.Add(1)

//下面这个loadGroup是保证当数据不存在时,只有一个客户端从peer或者数据源获取数据,

//其他客户端阻塞,直到第一个客户端数据之后,所有客户端再返回;这个主要是通过sync.WaitGroup实现

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获取数据

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

iferr ==nil{

g.Stats.PeerLoads.Add(1)

returnvalue,nil

}

g.Stats.PeerErrors.Add(1)

}

//从数据源获取数据

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

iferr !=nil{

g.Stats.LocalLoadErrs.Add(1)

returnnil, err

}

g.Stats.LocalLoads.Add(1)

destPopulated =true

//将数据源获取的数据存储在本地mainCache中

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

returnvalue,nil

})

iferr ==nil{

value = viewi.(ByteView)

}

return

}

这个load函数先是从peer获取数据,如果peer没有数据,则直接从数据源(数据库或文件)获取数据;ok,先看下groupcache是如何从数据源获取数据,然后再分析下如果从peer中获取数据;

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函数主要是利用NewGroup创建Group时传进去的Getter,在调用这个Getter的Get函数从数据源获取数据。

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

returnnewGroup(name, cacheBytes, getter,nil)

}

也就是说当groupcache以及peer不存在所需数据时,用户可以自己定义从哪获取数据以及如何获取数据,即定义Getter的实例即可;

从peer获取数据

当本地groupcache中不存在数据时,会先从peer处获取数据,我们来看下getFromPeer函数实现

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

//为了减少传输数据量,在peer之间,通过pb来传输数据

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获取的数据存储在本地hotCache

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

}

returnvalue,nil

}

这个ProtoGetter是个接口,httpGetter结构体实现了这个接口,而上述传进getFromPeer函数的peer就是httpGetter,因此,我们可以来看下httpGet这个结构体的Get函数

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类型的对象池

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

b.Reset()

defer bufferPool.Put(b)

_, err = io.Copy(b, res.Body)//将获取的数据copy给b

iferr != nil {

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

}

err = proto.Unmarshal(b.Bytes(), out)//将数据存在out中

iferr != nil {

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

}

return nil

}

这个函数首先向peer发起一个http请求,然后将请求得到的封装在out *pb.GetResponse,返回给getFromPeer,并最终返回给客户端;

总结

这篇文章主要是聊聊我对学习golang的一些看法,以及分析下groupcache的实现原理,分析的不是很细,主要是对这个框架进行了分析,对groupcache有了整体的认识之后,再去看细节部分,会简单很多。

这几天再看sqlmock开源框架,这个主要作用就是,在单元测试时用来模拟数据库操作;主要原理就是实现一个驱动程序。在看这个sqlmock过程中,首先必须把database/sql以及go-sql-driver看懂,知道这两个是如何一起运作的,这样才能了解sqlmock的实现;过几天再把database/sql以及go-sql-driver的实现原理发出来。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,571评论 18 139
  • https://nodejs.org/api/documentation.html 工具模块 Assert 测试 ...
    KeKeMars阅读 6,296评论 0 6
  • 充满鲜花的世界到底在哪里 如果它真的存在那么我一定会去 你哭着对我说 童话里都是骗人的 我不可能是你的王子
    YesFiona阅读 124评论 0 0
  • 这些天的黄冈天气明媚的不得了 网上对高温的调侃就数不胜数 所以 姨妈和姨夫为兑现高考之前对我的承...
    三号球服阅读 380评论 0 1
  • 继续,沉醉于艺术的世界,耕读于真实的世界。
    红得远阅读 110评论 0 0