这个仓库是对GeeCache【geektutu.com】的学习,通过七天,逐步完成了一个分布式缓存系统:
- 第一天:LRU缓存策略,学习了缓存的数据更新方法和存储的底层实现,用双向链表存储数据,保证对数据增加删除时的时间复杂度O(1),再用一个map来保存key和对应存储value的链表element的关系。
FIFO(First In First Out) 先进先出,也就是淘汰缓存中最老(最早添加)的记录。FIFO 认为,最早添加的记录,其不再被使用的可能性比刚添加的可能性大。这种算法的实现也非常简单,创建一个队列,新增记录添加到队尾,每次内存不够时,淘汰队首。但是很多场景下,部分记录虽然是最早添加但也最常被访问,而不得不因为呆的时间太长而被淘汰。这类数据会被频繁地添加进缓存,又被淘汰出去,导致缓存命中率降低。
LFU(Least Frequently Used) 最少使用,也就是淘汰缓存中访问频率最低的记录。LFU 认为,如果数据过去被访问多次,那么将来被访问的频率也更高。LFU 的实现需要维护一个按照访问次数排序的队列,每次访问,访问次数加1,队列重新排序,淘汰时选择访问次数最少的即可。LFU 算法的命中率是比较高的,但缺点也非常明显,维护每个记录的访问次数,对内存的消耗是很高的;另外,如果数据的访问模式发生变化,LFU 需要较长的时间去适应,也就是说 LFU 算法受历史数据的影响比较大。例如某个数据历史上访问次数奇高,但在某个时间点之后几乎不再被访问,但因为历史访问次数过高,而迟迟不能被淘汰。
LRU(Least Recently Used) 最近最少使用,相对于仅考虑时间因素的 FIFO 和仅考虑访问频率的 LFU,LRU 算法可以认为是相对平衡的一种淘汰算法。LRU 认为,如果数据最近被访问过,那么将来被访问的概率也会更高。LRU 算法的实现非常简单,维护一个队列,如果某条记录被访问了,则移动到队尾,那么队首则是最近最少访问的数据,淘汰该条记录即可。
- 第二天:单机并发缓存,学习了mutex的使用,通过新结构体封装mutex和之前的LRU存储,保证对并发的支持。学习了接口型函数【函数也是一等公民,可以实现接口,这样在参数是这个接口的时候,可以传入结构体也可以传入函数】【https://geektutu.com/post/7days-golang-q1.html】
- 第三天:HTTP服务端,分布式节点的通信也是通过HTTP访问的,所以每个节点都是一个HTTP服务端,根据GET请求,调用自己的缓存查找策略,访问的缓存和key是通过GET的URL区分的。
- 第四天:一致性哈希,学习到了一致性哈希算法,对同一个数据的访问不会随机节点,而是映射到同一个节点上,通过哈希环实现。
- 第五天:分布式节点,实现了分布式节点的HTTP客户端,
- 第六天:防止缓存击穿,学习了缓存雪崩、缓存穿透、缓存击穿,使用waitgroup来保证只有一个goroutine执行动作,其他的goroutine等待。避免同时发送请求。
缓存雪崩:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。
缓存击穿:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。
缓存穿透:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。
- 第七天:Protobuf 通信,学习了用protobuf通信的in、out方法
先实现一个不支持并发的底层模型,再新建一个结构体,封装mutex和这个底层模型,在调用底层方法前用mutex保护住。