groupcache的设计和实现分析
本文基于groupcache源码, 分析分布式缓存系统的设计和实现过程。本文代码大部分是来自groupcache的源码,但根据分析的需要做了少许改动。
1.本地缓存系统
本地缓存系统的基本结构如上图所示。在内存中维护一个cache。查询时,首先查询cache中是否已经缓存查询结果。如果已经缓存,直接返回缓存结果,如果没有缓存,将查询结果Load到cache中,然后返回结果。代码如下:
type Value interface{} type Cache interface { Get(key string) (Value, bool) Add(key string, val Value) } type Group struct { cache Cache } func (g *Group) Get(key string) Value { //look up cache first val, cacheHit := g.lookupCache(key) if cacheHit { return val } //if miss, load value to cache val = g.load(key) return val } func (g *Group) lookupCache(key) (Value, bool) { val, ok := g.cache.Get(key) return val, ok }
2.分布式缓存系统
在设计分布式缓存系统的时候,需要让key分布在不同的缓存节点上。当某节点收到查询请求时,如果该key归属于本节点,则在本节点获取查询结果;如果该key归属于其他节点,则本节点向归属节点获取查询结果 。如下图所示:
此分布式缓存架构引入了2个新的问题:
1. 如何判断查询的key归属哪个节点
2. 如何从其他节点获取数据
第1个问题实际上是一个路由问题,即给定key,路由到某个节点。这里忽略具体实现,将问题抽象出来,由2个接口表示(对应于groupcache的ProtoGetter和PeerPicker):
type Peer interface{ Get(key string) Value } type Router interface{ Route(key string) Peer }
接口Peer表示一个远端节点,Get方法从远端节点查询数据;接口Router表示一个路由器,Route方法将给定key路由到其归属的远端节点,如果key归属于本节点,Route方法返回nil。基于此,改写Group结构体和Group.load方法:
type Group struct { router Router cache Cache } func (g *Group) load(key string) Value { peer := g.router.Route(key) if peer == nil { val := g.getLocally(key) g.cache.Add(key, val) //store result in cache return val } return peer.Get(g.name, key) }
至此,一个分布式缓存框架搭建完成了,接下来分析具体细节的实现和功能的优化完善。
2.1. Cache的内存结构
Cache对象是内存中的一个容器,用来存放查询的结果。