groupcache 是 memcached 作者 Brad Fitzpatrick 用 Go 语言编写的缓存及缓存过滤库,作为 memcached 许多场景下的替代版本。
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的第二个被阻塞的查询也被通知而拿到了结果
下面是一个简单的例子:
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
67
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)
}
输出:
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
相关项目
- golang/groupcache https://github.com/golang/groupcache
- mailgun/groupcache https://github.com/mailgun/groupcache 是 mailgun fork 官版并添加 TTL 和 删除key 功能
本文网址: https://golangnote.com/topic/175.html 转摘请注明来源