Golang笔记

groupcache 使用入门

groupcache是memcached作者Brad Fitzpatrick用Go语言编写的缓存及缓存过滤库,作为memcached 许多场景下的替代版本。

groupcache不像其它的一些缓存数据库有个服务端,需要客户端去连接,换句话说,它本没有服务端或者人人都是服务端。相对于memcached,groupcache提供更小的功能集和更高的效率,以第三方库的形式提供服务

groupcache的代码结构也比较清晰,代码量也不是很大,很适合大家去阅读学习。主要分为了consistenthash(提供一致性哈希算法的支持),lru(提供了LRU方式清楚缓存的算法),singleflight(保证了多次相同请求只去获取值一次,减少了资源消耗),还有一些源文件:byteview.go 提供类似于一个数据的容器,http.go提供不同地址间的缓存的沟通的实现,peers.go节点的定义,sinks.go感觉就是一个开辟空间给容器,并和容器交互的一个中间人,groupcache.go整个源码里的大当家,其它人都是为它服务的。

groupcache 与 memcached 的不同之处:

  • 不需要对服务器进行单独的设置,这将大幅度减少部署和配置的工作量。groupcache 既是客户端库也是服务器库,并连接到自己的 peer 上。
  • 具有缓存过滤机制。众所周知,在 memcached 出现“Sorry,cache miss(缓存丢失)”时,经常会因为不受控制用户数量的请求而导致数据库(或者其它组件)产生“惊群效应(thundering herd)”;groupcache 会协调缓存填充,只会将重复调用中的一个放于缓存,而处理结果将发送给所有相同的调用者。
  • 不支持多个版本的值。如果“foo”键对应的值是“bar”,那么键“foo”的值永远都是“bar”。这里既没有缓存的有效期,也没有明确的缓存回收机制,因此同样也没有 CAS 或者 Increment/Decrement。
  • 基于上一点的改变,groupcache 就具备了自动备份“超热”项进行多重处理,这就避免了 memcached 中对某些键值过量访问而造成所在机器 CPU 或者 NIC 过载。

groupcache避免多次非缓存查询, 并且支持多节点缓存(即便多节点,也还支持同一个key同一时刻只查询一次)。除此之外还支持分组,不同分组有各自的查询方式和缓存,分组只是逻辑上的,互不影响,所以这里分组同一分组的情况。

groupcache没有支持cache的过期时间,而是限制cache的总内存大小,通过LRU的方法使用cache,到达上限后,最少被使用的会被最先清除出缓存。

groupcache在此基础上,添加了多节点的支持,每个节点(peer)都有缓存,只缓存属于它的key(通过将字符串key平均分到不同的peer)。

不同的peer只负责查询和缓存它的属于它的key。如果一个peer接收到不属于它的key,它就转发到别的peer上进行查询。

什么key属于哪个peer通过一个神奇的hash算法实现的。

只加载一次是groupcache最重要的特性,groupcache可以阻止加载两次,因为一次Load操作开始并未完成前,后面的其它查询都会等待这个结果,这其它查询包括从本地的查询和从别的节点的查询。

假设一种查询路经(这个例子中,耗时的操作只在peer2中进行了一次):

  • 查询节点peer1,查询的key不属于peer1
  • peer1发现这个key属于peer2,以同一key查询peer2
  • peer2发现该key属于自己,但没有自己的缓存中
  • peer2发起耗时查询(比如DB查询)并等待完成
  • 再次以同一key查询节点peer1,但是peer1还有同一个key的查询还未完成,所以阻塞等待
  • 以同一key查询peer2, peer2发现这个key属于自己,但没有自己的缓存中
  • peer2发起查询,但是peer2的同一个key还在查询,所以等待。
  • peer2的第一个查询完成,并放到peer2的main cache中 。peer2的第二个查询被通知,得到同一结果
  • peer2的第一个查询是peer1转发来的,所以结果被返回peer1,并被放到peer1的hot cache中,peer1的第一个查询结束。peer1的第二个被阻塞的查询也被通知而拿到了结果

下面是一个简单的例子:

package main

import (
	"fmt"

	"github.com/golang/groupcache"
)

type SlowDB struct {
	data map[string]string
}

func (db *SlowDB) Get(key string) string {
	fmt.Printf("getting %s\n", key)
	return db.data[key]
}

func (db *SlowDB) Set(key string, value string) {
	fmt.Printf("setting %s to %s\n", key, value)
	db.data[key] = value
}

func NewSlowDB() *SlowDB {
	ndb := new(SlowDB)
	ndb.data = make(map[string]string)
	return ndb
}

func main() {

	db := NewSlowDB()

	db.Set("foo", "bar")
	db.Set("one", "two")

	var stringcache = groupcache.NewGroup("SlowDBCache", 64<<20, groupcache.GetterFunc(
		func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
			result := db.Get(key)
			dest.SetBytes([]byte(result))
			return nil
		}))

	var data []byte

	err := stringcache.Get(nil, "foo", groupcache.AllocatingByteSliceSink(&data))

	err2 := stringcache.Get(nil, "one", groupcache.AllocatingByteSliceSink(&data))

	db.Set("foo", "bar2")
	err3 := stringcache.Get(nil, "foo", groupcache.AllocatingByteSliceSink(&data))

	if err != nil {
		fmt.Println("error")
	}

	if err2 != nil {
		fmt.Println("error2")
	}

	if err3 != nil {
		fmt.Println("error3")
	}

	fmt.Printf("data was %s\n", data)

}

输出:

setting foo to bar
setting one to two
getting foo
getting one
setting foo to bar2
data was bar
本文网址: https://golangnote.com/topic/175.html (转载注明出处)
关于GolangNote:记录在工作中使用golang 遇到、面临的相关问题及解决方法。如果你在这里获得一些知识或信息,解决你的编程问题,请考虑捐赠给不幸的人或者你喜欢的慈善机构,除捐赠外,种植树木、志愿服务或减少排碳的行为也很有益处。如果你有任何问题可以在下面 留言
Be the first to comment!
Captcha image
Relative Articles