LRUCache的作用以及在vitess中扮演的角色,文档上是这样描述的:
vtocc builds a plan for every query that requires bind variables. It stores these plans in an LRU cache
今天先来分析LRUCache本身的具体实现
提到cache就不能少了key-value(以下简称kv),提到kv就不能不提到两个必须实现的接口。get和set,LRUCache也不例外。
kv最便捷的实现方法就是map,至于map底层实现是hash表还是红黑树,暂时无需关心,LRUCache大概也是这样想的,使用
了map作为kv的主要数据结构,同时使用一个list(双链表)来存放所有的元素,总是将最近访问的放到list的头部。
另外为了线程安全还用到了lock
先上数据结构,这个是算法实现的灵魂
type LRUCache struct {
mu sync.Mutex
// list & table of *entry objects
list *list.List //LQ: 所有的kv被组织成entry都放到list中,方便实现lru算法
table map[string]*list.Element //LQ: 用map保持对其的引用来实现快速查找
// Our current size, in bytes. Obviously a gross simplification and low-grade
// approximation.
size uint64
// How many bytes we are limiting the cache to.
capacity uint64
}
既然是kv,那么key是什么类型呢,从table的定义可以看到key的类型是string,那么value又是什么类型呢?*list.Element
这里能看到的是个指针。这个指针指向什么呢? entry上场
type entry struct {
key string
value Value
size int
time_accessed time.Time
}
为了统计LRUCache中元素占用内存大小,所有放到LRUCache中的元素都必须实现接口Value,即要实现函数Size() int
type Value interface {
Size() int
}
所有放入LRUCache的元素都会被包装成entry,见函数addNew
func (self *LRUCache) addNew(key string, value Value) {
newEntry := &entry{key, value, value.Size(), time.Now()} //构造一个新的entry,对结构体的成员依次赋值
element := self.list.PushFront(newEntry) //最近的总是放到list的头部
self.table[key] = element //kv
self.size += uint64(newEntry.size) //当前LRUCache的实际大小
self.checkCapacity() //检查是否超过了LRUCache的容量
}
其它的函数想必大体也可以猜到如何实现,这里就不一而足了。我们在vitess源码阅读笔记笔记之起因和编码风格中提到
vitess的多个模块都通过http的方式导出内部状态(json格式)来辅助调试和监控系统的运行状况 LRUCache虽然本身没有提供http服务,但提供了StatsJSON函数来导出自身的运行状态。
func (self *LRUCache) StatsJSON() string {
l, s, c, o := self.Stats()
return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"OldestAccess\": \"%v\"}", l, s, c, o)
}
导出的分别是:LRUCache中元素的个数, 当前占用的内存大小, 总容量, 最老的元素的上次访问时间
前面提到分析代码时一定要知道代码在架构中扮演的角色 就像谍战剧中的情节一样,
找不到组织的后果是危险的,LRUCache 的组织在哪里呢?
其在架构图中扮演的角色是作为consolidator和schema中的基本组件,具体职责嘛,叫啥名,就做啥事。