GolangNote

Golang笔记

groupcache 使用入门

Permalink

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

groupcache 使用入门

groupcache 简介

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的第二个被阻塞的查询也被通知而拿到了结果

下面是一个简单的例子:

Go: groupcache 例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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)

}

输出:

plaintext: output
1
2
3
4
5
6
setting foo to bar
setting one to two
getting foo
getting one
setting foo to bar2
data was bar

相关项目

Related articles

Go Modules 使用备忘

简单说 Go Modules 就是包管理,从 go1.11 开始支持,可以不需要gopath存在,环境变量`GO111MODULE`,默认为 `auto` 项目存在 `go.mod` 则使用 go module ,否则使用GOPATH 和 vendor 机制。...

golang Selenium WebDriver 使用记录

Selenium WebDriver 直接通过浏览器自动化的本地接口来调用浏览器,以达到模拟浏览器行为的操作,如点击、选择、鼠标移动等。下面是记录个人使用golang 驱动的记录。...

golang snappy 的使用场合

google 自家的 snappy 压缩优点是非常高的速度和合理的压缩率。压缩率比 gzip 小,CPU 占用小。...

Write a Comment to "groupcache 使用入门"

Submit Comment Login
Based on Golang + fastHTTP + youdb | go1.16 Processed in 1ms