groupcache源码解析

简介

groupcache是一个分布式kv缓存的library,能够实现对数据的get,而不能进行update和delete操作。groupcache采用的是P2P的架构,所有的节点都是同构的。当客户端程序查找某个值时,groupcache先在本地的cache中进行查找,如果不存在,则通过一致性哈希寻找该key-value所在的peer的地址,如果都不存在则从数据源(数据库)拉取。cache使用LRU策略淘汰最近不常用的数据。需要注意的是,如果很多请求都访问cache中不存在的值时,会导致groupcache向数据源请求数据,这时候需要阻塞请求,保证只有一个请求执行。
需要注意的是groupcache只是一个分布式的kv缓存库,我们需要将自己的规则填入,capotej 实现了一个了一个demo,下面将自顶向下分析整个库的源码。

Demo

客户端与groupcache之间通过RPC通信,而peer之间通过http进行数据的传输,而数据源通过延时模拟一个数据库的存取过程。

架构

Command line client 与前端之间通过RPC进行通信,而groupcache服务器之间通过http进行数据的传输,而DB Server作为数据源,当所有的cache中都不存在的时候,向数据源获取结果
Demo架构

var stringcache = groupcache.NewGroup("SlowDBCache", 64<<20, groupcache.GetterFunc(
		func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
   
			result := client.Get(key)
			fmt.Printf("asking for %s from dbserver\n", key)
			dest.SetBytes([]byte(result))
			return nil
		}))

上述代码指定了groupcache的名称,大小,以及获取源数据的方法,当客户端程序需要获取某个值时,就会调用Get函数,从groupcache集群中获取,代码如下

func (s *Frontend) Get(args *api.Load, reply *api.ValueResult) error {
   
	var data []byte
	fmt.Printf("cli asked for %s from groupcache\n", args.Key)
	err := s.cacheGroup.Get(nil, args.Key,
		groupcache.AllocatingByteSliceSink(&data))
	reply.Value = string(data)
	return err
}

LRU实现

代码中的cache可以基于mapcontainer/list实现,整个过程非常的简单,具体的实现过程如下:

  1. 如果一个k-v未在cache中出现,将其加入进listmap
  2. 如果这个k-v对已经出现过,则将其放在list的头部
  3. nums >maxEntries时,将它从listmap中移除出去

代码如下

type Cache struct {
   
	MaxEntries int
	// 当驱逐某个key时调用的函数,用于回调
	OnEvicted func(key Key, value interface{
   })
	ll	*list.List
	cache map[interface{
   }]*list.Element
}
type Key interface{
   }
type entry struct {
   
	key   Key
	value interface{
   }
}
func (c *Cache) Add(key Key, value interface{
   }) {
   
	if c.cache == nil {
   
		c.cache = make(map[interface{
   }]*list.Element)
		c.ll = list.New()
	}
	if ee, ok := c.cache[key]; ok {
   
		c.ll.MoveToFront(ee)
		// ee.Value.(*entry)类型断言成*entry并赋值
		ee.Value.(*entry).value = value
		return
	}
	ele := c.ll.PushFront(&entry{
   key, value})
	c.cache[key] = ele
	if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
   
		c.RemoveOldest()
	}
}

删除元素的函数如下所示:首先,从list中删除,然后从map中移除,最后触发onEvicted()函数

func (c *Cache) removeElement(e *list.Element) {
   
	c.ll.Remove(e)
	kv := e.Value.(*entry)
	delete(c.cache, kv.key)
	if c.OnEvicted != nil {
   
		c.OnEvicted(kv.key, kv.value)
	}
}

一致性哈希

数据的查找使用的是一致性哈希,简单的说就是计算每个key的Hash值,将其存放在hash值大于它的hash值的第一个map中
但是在项目中看不到任何关于迁移的代码,一致性哈希一个重要的特性就是迁移的代价小,增减节点只需要将临近节点的数据迁移
过程如下:

  1. 首先根据传入的key(ip+port+id,id为replica的编号),根据hash函数(提供了默认的hash函数)计算hash值,hash值作为hashMap的key,key作为value存储下来,并对key数组排序,方便后续的二分查找
  2. 在查找某个key所在的server,通过hash值的数组,使用二分法查找第一个hash值大于或等于这个key的hash值的服务器,返回该服务器的ip+port

代码如下所示

type Hash func(data []byte) uint32
type Map struct {
   
	hash     Hash
	replicas int
	keys     []int // Sorted
	hashMap  map[int]string
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值