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

groupcache 与 memcached 的不同之处

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

一旦写进去后就不会变动。在放弃update/delete 的特性后,换来的是:

  • Cluster 的能力。
  • 处理热点的能力。

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

使用示例

  • groupcache由于是框架,需要导入在编写业务代码才能运作
  • 缓存方式可自定义:db,文件等
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
package main

import (
"database/sql"
"fmt"
"github.com/golang/groupcache"
"log"
"net/http"
"os"
)

type TblCache struct {
Id int
Key string
Value string
}

func main() {
//定义节点数量以及地址
peers_addrs := []string{"http://127.0.0.1:8001", "http://127.0.0.1:8002"}
db, _ := sql.Open("sqlite3", "./console.db")

if len(os.Args) != 2 {
fmt.Println("local_addr must in(127.0.0.1:8001,127.0.0.1:8002)")
os.Exit(1)
}

local_addr := os.Args[1]
peers := groupcache.NewHTTPPool("http://" + local_addr)
peers.Set(peers_addrs...)

// 获取group对象
image_cache := groupcache.NewGroup("testGroup", 8<<30,
// 自定义数据获取来源
groupcache.GetterFunc(
func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
rows, _ := db.Query("SELECT key, value FROM tbl_cache_map where key = ?", key)
for rows.Next() {
p := new(TblCache)
err := rows.Scan(&p.Key, &p.Value)
if err != nil {
fmt.Println(err)
}
fmt.Printf("get %s of value from tbl_cache_map\n", key)
dest.SetString("tbl_cache_map.value : " + p.Value)
}
return nil
},
),
)

// 定义返回方式
http.HandleFunc("/get", func(rw http.ResponseWriter, r *http.Request) {
var data []byte
k := r.URL.Query().Get("key")
fmt.Printf("user get %s of value from groupcache\n", k)
image_cache.Get(nil, k, groupcache.AllocatingByteSliceSink(&data))
rw.Write([]byte(data))
})

log.Fatal(http.ListenAndServe(local_addr, nil))
}
  • groupcache.NewGroup() 获取group对象
  • groupcache.GetterFunc() 定义数据缓存方式
  • http.HandleFunc() 定义返回方式