Golang GroupCache源码分析

从我们之前使用的代码可以看出,我们要使用 GroupCache 首先必须要使用 NewGroup 接口来创建一个 Group 结构,Group 结构定义在 groupcache.go 文件中,具体代码如下:

// A Group is a cache namespace and associated data loaded spread over // a group of 1 or more machines. type Group struct { name string getter Getter peersOnce sync.Once peers PeerPicker cacheBytes int64 // limit for sum of mainCache and hotCache size // mainCache is a cache of the keys for which this process // (amongst its peers) is authoritative. That is, this cache // contains keys which consistent hash on to this process's // peer number. mainCache cache // hotCache contains keys/values for which this peer is not // authoritative (otherwise they would be in mainCache), but // are popular enough to warrant mirroring in this process to // avoid going over the network to fetch from a peer. Having // a hotCache avoids network hotspotting, where a peer's // network card could become the bottleneck on a popular key. // This cache is used sparingly to maximize the total number // of key/value pairs that can be stored globally. hotCache cache // loadGroup ensures that each key is only fetched once // (either locally or remotely), regardless of the number of // concurrent callers. loadGroup flightGroup _ int32 // force Stats to be 8-byte aligned on 32-bit platforms // Stats are statistics on the group. Stats Stats }

所有的字段解释如下:

字段 说明
name 创建的 Group 名
getter 从 Group 中获取数据的接口实现
peersOnce peersOnce 用于单次初始化集群中节点的函数
peers peers 用于判断查找的 key 是否属于当前节点。如果当前 key 属于当前节点,那么返回 nil 和 false,如果不属于当前节点,那么返回 true
cacheBytes 单个 Group 能够缓存的键的数目
mainCache 当前节点中所有缓存的键值数据
hotCache 存储了不属于当前节点,但是比较热的数据,以防止每次热数据都要从别的节点获取,需要占用网络资源,影响效率
loadGroup 用于保证每一个键都只会被加载一次
Stats 对 Group 的统计

接下来,我们来看 NewGroup 接口,具体代码如下:

// NewGroup creates a coordinated group-aware Getter from a Getter. // // The returned Getter tries (but does not guarantee) to run only one // Get call at once for a given key across an entire set of peer // processes. Concurrent callers both in the local process and in // other processes receive copies of the answer once the original Get // completes. // // The group name must be unique for each getter. func NewGroup(name string, cacheBytes int64, getter Getter) *Group { return newGroup(name, cacheBytes, getter, nil) }

Golang 中,NewXXX 就是构造函数,这里我们使用 NewGroup 传入缓存的名字、缓存的大小以及获取数据的接口创建一个缓存对象。在 NewGroup 里面,调用了 newGroup,具体代码如下:

// If peers is nil, the peerPicker is called via a sync.Once to initialize it. func newGroup(name string, cacheBytes int64, getter Getter, peers PeerPicker) *Group { if getter == nil { panic("nil Getter") } mu.Lock() defer mu.Unlock() initPeerServerOnce.Do(callInitPeerServer) if _, dup := groups[name]; dup { panic("duplicate registration of group " + name) } g := &Group{ name: name, getter: getter, peers: peers, cacheBytes: cacheBytes, loadGroup: &singleflight.Group{}, } if fn := newGroupHook; fn != nil { fn(g) } groups[name] = g return g }

在 newGroup 中,首先调用全局的 读写锁 进行加锁,最后,使用 defer 进行解锁,接着,我们使用 sync.Once 对象来进行接待你的初始化工作,并查看当前的 Group Name 是否已经在全局的 groups 中了,如果已经存在了,则直接 panic,否则,就创建 Group 对象。

最后,如果有创建 Group 的回调函数,那么直接调用,并且将创建好的 group 对象存放到全局的 groups 的 map 中。groups 的结构定义如下:

groups = make(map[string]*Group)

就是一个 group 名和 Group 对象的一个 map,还提供了一个全局的 GetGroup 接口,具体定义如下:

// GetGroup returns the named group previously created with NewGroup, or // nil if there's no such group. func GetGroup(name string) *Group { mu.RLock() g := groups[name] mu.RUnlock() return g }

首先,使用读加锁,然后从 map 中获取数据,最后解锁,并返回获取的 Group 对象即可。