前言

 是我模仿groupcache完成的一个分布式缓存,体积较小,适合学习分布式缓存思想。

groupcache的思想

cache通过内存加速数据访问,如果访问的数据不在缓存中,那么就需要去别处获取该数据,方式一般有从本地数据库获取,或是请求远程节点,从别的节点去拿数据。

groupcache本身想要的是每个节点存储专门的数据,比如对于一个指定的key,每次访问这个分布式集群,我都想让key的请求打到唯一的一台机器上,单个的节点就是为某些key提供服务的,不过多参与其他的数据缓存。这样的话,节点之间缓存的数据不同,整体上缓存的数据量也就大大增加。

当然实际上一个请求打来,不一定恰好打到为该key提供服务的节点上,这时该节点需要对key哈希一下,找到该key应该访问哪台机器,然后该节点直接去请求那台机器并将结果返回。这里直接将数据返回,不需要在本机进行再次的缓存。(当然也不是绝对不能缓存,如果是热点数据,那么提供这个key服务的节点压力是比较大的,在其他节点缓存一下反而有利于减少热点数据节点的压力)

剩余的就是一些常见缓存技术思路了,LRU、一致性哈希、singleflight等大杀器,详见下文。

实现思路

缓存技术有很多种,常见的redis、memcache等等都是利用缓存进行加速,既然是缓存加速就必然要和内存打交道,内存不是无限的,所以缓存淘汰是缓存技术必须实现的要点,对于缓存淘汰算法,可以看下我之前的博客:LRU 一种缓存淘汰算法 - 月一orange - 博客园

LRU

Janney/lru.go at master · k-si/Janney · GitHub
这里我们简要的实现LRU作为缓存淘汰算法,非常简单,队列+map即可。同时为了考虑内存数据的并发访问,需要对LRU进行一层封装,简便起见直接加读写锁即可,如果想进一步提升性能还需要对锁进行细化。如果缓存没有命中,那么需要接口,从外部获取数据。后面我们可以去本地db获取或是请求别的节点。

命名空间

https://github.com/k-si/Janney/blob/master/group.go
groupcache中的group可以看作是一种命名空间,也就是说,我们可以在所有节点中创建一个称叫zhangsan的group,这个group中就存放zhangsan相关的缓存,我们还可以再创建一个lisi group来存放lisi的信息。也可以理解为多个实例。

可访问的服务

https://github.com/k-si/Janney/blob/master/http.go
完成lru相当于完成了单机下的缓存,由于分布式机器需要rpc远程通信,为了简单期间,这里先使用http实现。每个节点上部署的cache服务都要有访问别的节点和被别的节点访问的能力,因此需要先实现一个简单的http服务,接受外界对该节点的访问。访问进来直接调用LRU的api即可。

一致性哈希

https://github.com/k-si/Janney/blob/master/consistenthash/hash.go
实际请求的key不会是一两个,对于上百万千万的key,哈希算法可以定位key应该访问哪台节点,在分布式场景下,一致性哈希必须要用,可以详见我之前的博客:一致性哈希算法 - 月一orange - 博客园

singleflight

Janney/singleflight.go at master · k-si/Janney · GitHub
极高的并发下,有很多重复的请求,这些请求打到缓存还好,如果缓存没有命中,就会不断的重复请求db,造成缓存穿透,那我们使用一种机制,让重复请求可以集中起来,其中一个请求获取结果后,这一堆请求直接返回该结果,不需要再去请求db。
详细可见我之前的博客:single flight 防止缓存击穿 - 月一orange - 博客园