GolangNote

Golang笔记

groupcache 使用入门

Permalink

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

下面是一个简单的例子:

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
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)

}

输出:

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

相关项目

本文网址: https://golangnote.com/topic/175.html 转摘请注明来源

Related articles

Golang quicktemplate 模版快速入门

Golang 有很多的模版引擎,自带的 `html/template` 也很好,大多数情况都能满足需求,只是有些逻辑、条件判断不好在模版里实现, `quicktemplate` 是个很好的选择。...

Write a Comment to "groupcache 使用入门"

Submit Comment Login
Based on Golang + fastHTTP + sdb | go1.22.3 Processed in 1ms