├── .gitignore
├── go.mod
├── demo
├── test.sh
├── go.mod
├── run.sh
├── main.go
└── go.sum
├── gocachepb
├── cache.proto
└── cache.pb.go
├── interface.go
├── byteview.go
├── consistenthash
├── chash_test.go
└── chash.go
├── .golangci.yml
├── cache.go
├── singlereq
└── single_req.go
├── gocache_test.go
├── lru
├── lru_test.go
└── lru.go
├── http_getter.go
├── README.md
├── http_pool.go
├── gocache.go
├── go.sum
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/devhg/gocache
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/golang/protobuf v1.4.3
7 | google.golang.org/protobuf v1.25.0
8 | )
9 |
--------------------------------------------------------------------------------
/demo/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | curl "http://localhost:9999/api?key=Tom" &
4 | curl "http://localhost:9999/api?key=Tom" &
5 | curl "http://localhost:9999/api?key=Tom" &
6 |
7 | wait
--------------------------------------------------------------------------------
/demo/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/devhg/gocache/demo
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/arl/statsviz v0.1.1
7 | github.com/cddgo/gocache v0.0.0
8 | )
9 |
10 | replace github.com/cddgo/gocache => ../
11 |
--------------------------------------------------------------------------------
/gocachepb/cache.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package gocachepb;
3 | //option go_package = "./";
4 |
5 | message Request {
6 | string group = 1;
7 | string key = 2;
8 | }
9 | message Response {
10 | bytes value = 1;
11 | }
12 | service Cache {
13 | rpc Get(Request) returns (Response);
14 | }
--------------------------------------------------------------------------------
/demo/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | trap "rm server;kill 0" EXIT
3 |
4 | go build -o server
5 |
6 | ./server -port=8001 &
7 | ./server -port=8002 &
8 | ./server -port=8003 -api=1 &
9 |
10 | sleep 2
11 | echo ">>> start test"
12 | curl "http://localhost:9999/api?key=Tom" &
13 | curl "http://localhost:9999/api?key=Tom" &
14 | curl "http://localhost:9999/api?key=Tom" &
15 |
16 | wait
--------------------------------------------------------------------------------
/interface.go:
--------------------------------------------------------------------------------
1 | package gocache
2 |
3 | // // 节点选择器
4 | // type NodePicker interface {
5 | // // 利用一致性哈希算法,根据传入的 key 选择相应节点
6 | // // 并返回节点处理器NodeGetter。
7 | // PickNode(key string) (NodeGetter, bool)
8 | // }
9 |
10 | // // 节点处理器
11 | // type NodeGetter interface {
12 | // // 用于从对应 group 查找对应key的缓存值
13 | // // Get(group, key string) ([]byte, error)
14 | // Get(*pb.Request, *pb.Response) error
15 | // }
16 |
--------------------------------------------------------------------------------
/byteview.go:
--------------------------------------------------------------------------------
1 | package gocache
2 |
3 | // A ByteView holds an immutable view of bytes.
4 | type ByteView struct {
5 | // 储存真正的缓存值,选择byte类型是为了支持所有的数据类型
6 | // b 是只读的,使用 ByteSlice() 方法返回一个拷贝,防止缓存值被外部程序修改
7 | b []byte
8 | }
9 |
10 | // Len returns the view's length
11 | func (b ByteView) Len() int {
12 | return len(b.b)
13 | }
14 |
15 | // ByteSlice returns a copy of the data as a byte slice.
16 | func (b ByteView) ByteSlice() []byte {
17 | return cloneBytes(b.b)
18 | }
19 |
20 | // String returns the data as a string, making a copy if necessary.
21 | func (b ByteView) String() string {
22 | return string(b.b)
23 | }
24 |
25 | func cloneBytes(b []byte) []byte {
26 | bytes := make([]byte, len(b))
27 | copy(bytes, b)
28 | return bytes
29 | }
30 |
--------------------------------------------------------------------------------
/consistenthash/chash_test.go:
--------------------------------------------------------------------------------
1 | package consistenthash
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "testing"
7 | )
8 |
9 | func TestMap_Add(t *testing.T) {
10 | chash := New(3, func(data []byte) uint32 {
11 | atoi, _ := strconv.Atoi(string(data))
12 | return uint32(atoi)
13 | })
14 |
15 | // 02/12/22 04/14/24 06/16/26
16 | // 2 4 6 12 14 16 22 26
17 | chash.Add("2", "4", "6")
18 |
19 | testCases := map[string]string{
20 | "2": "2",
21 | "11": "2",
22 | "23": "4",
23 | "27": "2",
24 | }
25 |
26 | for k, v := range testCases {
27 | get := chash.Get(k)
28 | fmt.Println(v, get)
29 | }
30 |
31 | chash.Add("8")
32 |
33 | // "27":"2" ==> "27":"8"
34 | get := chash.Get("27")
35 | fmt.Println("27", get)
36 |
37 | get = chash.Get("127")
38 | fmt.Println("127=2?", get) // 2
39 | }
40 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | disable-all: true
3 | enable:
4 | - bodyclose
5 | # - deadcode
6 | - depguard
7 | - dogsled
8 | - dupl
9 | - errcheck
10 | - exportloopref
11 | - exhaustive
12 | - funlen
13 | # - gochecknoinits
14 | - goconst
15 | - gocritic
16 | - gocyclo
17 | - gofmt
18 | - goimports
19 | # - gomnd
20 | - goprintffuncname
21 | - gosec
22 | - gosimple
23 | - govet
24 | # ??????
25 | # - ineffassign
26 | - revive
27 | - misspell
28 | - nakedret
29 | # - noctx # http.Get() must with context
30 | - nolintlint
31 | - rowserrcheck
32 | - staticcheck
33 | - structcheck
34 | - stylecheck
35 | - typecheck
36 | - unconvert
37 | - unparam
38 | # - unused
39 | # - varcheck # unused var check
40 | - whitespace
41 | - revive
42 |
--------------------------------------------------------------------------------
/cache.go:
--------------------------------------------------------------------------------
1 | package gocache
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/devhg/gocache/lru"
7 | )
8 |
9 | // cache 并发缓存,对核心lru进行封装
10 | type cache struct {
11 | sync.RWMutex
12 | lru *lru.Cache
13 | cacheBytes int64
14 | nhit, nget int64
15 | nevict int64 // number of evictions
16 | }
17 |
18 | // add 添加缓存
19 | func (c *cache) add(key string, val ByteView) {
20 | c.Lock()
21 | defer c.Unlock()
22 |
23 | // 延迟初始化(Lazy Initialization),一个对象的延迟初始化意味
24 | // 着该对象的创建将会延迟至第一次使用该对象时。主要用于提高性能,并减少程序内存要求。
25 | if c.lru == nil {
26 | c.lru = lru.New(&lru.CacheConfig{
27 | MaxBytes: c.cacheBytes,
28 | OnEvicted: func(s string, value lru.Value) {
29 | c.nevict++
30 | },
31 | })
32 | }
33 | c.lru.Add(key, val)
34 | c.cacheBytes += int64(val.Len())
35 | }
36 |
37 | // 获取缓存
38 | func (c *cache) get(key string) (val ByteView, ok bool) {
39 | c.RLock()
40 | defer c.RUnlock()
41 |
42 | if c.lru == nil {
43 | return
44 | }
45 |
46 | c.nget++
47 | if v, hit := c.lru.Get(key); hit {
48 | c.nhit++ // 命中返回true
49 | return v.(ByteView), hit
50 | }
51 | return
52 | }
53 |
--------------------------------------------------------------------------------
/singlereq/single_req.go:
--------------------------------------------------------------------------------
1 | package singlereq
2 |
3 | import "sync"
4 |
5 | // 解决缓存击穿,缓存雪崩
6 | type call struct {
7 | wg sync.WaitGroup
8 | val interface{}
9 | err error
10 | }
11 |
12 | type ReqGroup struct {
13 | sync.Mutex // 保护map
14 | keyCall map[string]*call
15 | }
16 |
17 | // 确保并发环境下,相同的key只会被请求一次
18 | // 使用 sync.WaitGroup锁 避免重入。
19 | // 无论Do并发被调用多少次,fn只会执行一次
20 | // 等待 fn 调用结束了,返回返回值或错误。
21 | // 同步锁mu的目的是保护map
22 | func (rg *ReqGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
23 | rg.Lock()
24 | if rg.keyCall == nil {
25 | rg.keyCall = make(map[string]*call)
26 | }
27 |
28 | // 已经有一个请求在进行
29 | if call, ok := rg.keyCall[key]; ok {
30 | rg.Unlock()
31 | call.wg.Wait() // 有请求正在进行中,等待已经进行的请求的结果
32 | return call.val, call.err // 所有的并发请求都会在此返回
33 | }
34 |
35 | c := new(call)
36 |
37 | c.wg.Add(1) // 发起请求前加入任务
38 | rg.keyCall[key] = c // 添加call, 表明key已经有请求在处理
39 | rg.Unlock()
40 |
41 | c.val, c.err = fn() // 发起请求
42 | c.wg.Done() // 请求结束
43 |
44 | rg.Lock()
45 | delete(rg.keyCall, key)
46 | rg.Unlock()
47 | return c.val, c.err
48 | }
49 |
--------------------------------------------------------------------------------
/gocache_test.go:
--------------------------------------------------------------------------------
1 | package gocache
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "reflect"
7 | "strings"
8 | "testing"
9 | )
10 |
11 | func TestGetterFunc_Get(t *testing.T) {
12 | var g DataGetter = GetterFunc(func(key string) ([]byte, error) {
13 | return []byte(key), nil
14 | })
15 |
16 | expect := []byte("222")
17 | if get, _ := g.Get("222"); reflect.DeepEqual(get, expect) {
18 | fmt.Println(string(get))
19 | }
20 | }
21 |
22 | func TestA(t *testing.T) {
23 | parts := strings.SplitN("/c/g/k", "/", 4)
24 | fmt.Println(parts[2])
25 | }
26 |
27 | // 用map模仿一个慢的数据库
28 | var db = map[string]string{
29 | "A": "1",
30 | "B": "2",
31 | "C": "3",
32 | }
33 |
34 | func TestGetGroup(t *testing.T) {
35 | // 统计慢数据库中没一个数据的访问次数,>1则表示调用了多次回调函数,没有缓存。
36 | loadCounts := make(map[string]int, len(db))
37 |
38 | group := NewGroup("test1", 2<<10, GetterFunc(
39 | func(key string) ([]byte, error) {
40 | log.Println("[slow DB] search key", key)
41 | if v, ok := db[key]; ok {
42 | if _, ok := loadCounts[key]; !ok {
43 | loadCounts[key] = 0
44 | }
45 | loadCounts[key]++
46 | return []byte(v), nil
47 | }
48 | return nil, fmt.Errorf("%s is not found", key)
49 | }))
50 |
51 | for k, v := range db {
52 | if get, err := group.Get(k); err != nil || get.String() != v {
53 | t.Fatal("failed to get value")
54 | }
55 |
56 | if i := loadCounts[k]; i > 1 {
57 | log.Fatal("cache miss", i)
58 | }
59 | }
60 |
61 | if get, err := group.Get("unknown"); err == nil {
62 | t.Fatalf("the value of unknow should be empty, but %s got", get)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lru/lru_test.go:
--------------------------------------------------------------------------------
1 | package lru
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "testing"
7 | )
8 |
9 | type String string
10 |
11 | func (s String) Len() int {
12 | return len(s)
13 | }
14 |
15 | func TestGet(t *testing.T) {
16 | lru := New(&CacheConfig{
17 | MaxBytes: 19,
18 | MaxEntries: 2,
19 | })
20 |
21 | lru.Add("key", String("value"))
22 | lru.Add("key2", String("value2"))
23 |
24 | fmt.Println(lru.Len())
25 |
26 | if val, ok := lru.Get("key"); !ok || string(val.(String)) != "value" {
27 | log.Fatal("get value error")
28 | }
29 |
30 | if _, ok := lru.Get("key2"); !ok {
31 | log.Fatal("cache find key2 failed")
32 | }
33 | }
34 |
35 | func TestCache_RemoveOldest(t *testing.T) {
36 | k1, k2, k3 := "k1", "k2", "k3"
37 | v1, v2, v3 := "v1", "v2", "v3"
38 |
39 | lru := New(&CacheConfig{
40 | MaxBytes: int64(len(k1 + k2 + v1 + v2)),
41 | MaxEntries: 4,
42 | OnEvicted: nil,
43 | })
44 | lru.Add(k1, String(v1))
45 | lru.Add(k2, String(v2))
46 | lru.Add(k3, String(v3))
47 |
48 | if _, ok := lru.Get("k1"); ok {
49 | log.Fatal("remove k1 failed")
50 | }
51 | }
52 |
53 | func TestOnEvicted(t *testing.T) {
54 | k1, k2, k3 := "k1", "k2", "k3"
55 | v1, v2, v3 := "v1", "v2", "v3"
56 |
57 | keys := make([]string, 0)
58 | callback := func(key string, val Value) {
59 | keys = append(keys, key)
60 | }
61 |
62 | lru := New(&CacheConfig{
63 | MaxBytes: int64(len(k1 + k2 + v1 + v2)),
64 | MaxEntries: 1,
65 | OnEvicted: callback,
66 | })
67 | lru.Add(k1, String(v1))
68 | lru.Add(k2, String(v2))
69 | lru.Add(k3, String(v3))
70 |
71 | fmt.Println(keys)
72 | }
73 |
--------------------------------------------------------------------------------
/http_getter.go:
--------------------------------------------------------------------------------
1 | package gocache
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "log"
7 | "net/http"
8 | "net/url"
9 |
10 | pb "github.com/devhg/gocache/gocachepb"
11 | "google.golang.org/protobuf/proto"
12 | )
13 |
14 | // NodeGetter 节点处理器
15 | type NodeGetter interface {
16 | // 用于从对应 group 查找对应key的缓存值
17 | HTTPGet(group, key string) ([]byte, error)
18 | Get(*pb.Request, *pb.Response) error
19 | }
20 |
21 | type httpGetter struct {
22 | baseURL string // http://10.0.0.1:9305/_cache/
23 | }
24 |
25 | // 普通http通信
26 | func (h *httpGetter) HTTPGet(group, key string) ([]byte, error) {
27 | url := fmt.Sprintf("%v%v/%v", h.baseURL, group, key)
28 | log.Println(url)
29 |
30 | resp, err := http.Get(url)
31 | if err != nil {
32 | return nil, err
33 | }
34 | defer resp.Body.Close()
35 |
36 | if resp.StatusCode != http.StatusOK {
37 | return nil, fmt.Errorf("server returned: %v", resp.Status)
38 | }
39 |
40 | bytes, err := ioutil.ReadAll(resp.Body)
41 | if err != nil {
42 | return nil, fmt.Errorf("reading response body: %v", err)
43 | }
44 | return bytes, nil
45 | }
46 |
47 | // protobuf通信
48 | func (h *httpGetter) Get(in *pb.Request, out *pb.Response) error {
49 | URL := fmt.Sprintf(
50 | "%v%v/%v",
51 | h.baseURL,
52 | url.QueryEscape(in.GetGroup()),
53 | url.QueryEscape(in.GetKey()),
54 | )
55 | res, err := http.Get(URL)
56 | if err != nil {
57 | return err
58 | }
59 | defer res.Body.Close()
60 | if res.StatusCode != http.StatusOK {
61 | return fmt.Errorf("server returned: %v", res.Status)
62 | }
63 |
64 | bytes, err := ioutil.ReadAll(res.Body)
65 | if err != nil {
66 | return fmt.Errorf("reading response body: %v", err)
67 | }
68 |
69 | if err = proto.Unmarshal(bytes, out); err != nil {
70 | return fmt.Errorf("decoding response body: %v", err)
71 | }
72 |
73 | return nil
74 | }
75 |
76 | var _ NodeGetter = (*httpGetter)(nil)
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 分布式缓存
2 |
3 | 用go语言实现的一个分布式缓存
4 |
5 |
6 | ### TODO
7 | - [x] LRU缓存淘汰策略
8 | - [x] 单机并发缓存
9 | - [x] http客户端及请求支持
10 | - [x] 实现一致性哈希算法
11 | - [x] 利用一致性哈希算法,从单一节点走向分布式
12 | - [x] 缓存击穿,缓存雪崩问题
13 | - [ ] 缓存穿透问题
14 | - [x] Protobuf通信
15 | - [ ] 支持统计信息展示
16 | - [ ] 其他问题
17 |
18 |
19 | * 缓存雪崩:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。
20 |
21 | 解决:不支持过期时间,只维护了一个最近使用的缓存队列,暂时无法解决雪崩问题
22 |
23 | * 缓存击穿:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。
24 |
25 | 解决:使用sync.WaitGroup锁来避免重入,保证并发的时候只有一个请求在工作,详见singlereq/single_req.go的Do()
26 |
27 | * 缓存穿透:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。
28 |
29 | 解决:布隆过滤器,存一个短期的空值
30 |
31 |
32 | ###
33 | 项目结构
34 | 
35 | 创建流程
36 | 
37 |
38 |
39 |
40 |
41 | ### 脚本测试
42 | 运行 /demo 文件下的 run.sh `bash run.sh` 会编译运行main.go 为可执行文件并执行
43 | ```shell script
44 | #!/bin/zsh
45 | trap "rm server;kill 0" EXIT
46 |
47 | go build -o server
48 |
49 | ./server -port=8001 &
50 | ./server -port=8002 &
51 | ./server -port=8003 -api=1 &
52 |
53 | sleep 2
54 | echo ">>> start test"
55 | curl "http://localhost:9999/api?key=Tom" &
56 | curl "http://localhost:9999/api?key=Tom" &
57 | curl "http://localhost:9999/api?key=Tom" &
58 |
59 | wait
60 | ```
61 | 然后浏览器访问 http://localhost:9999/api?key=Tom 连接
62 |
63 | 日志结果如下
64 | ```
65 | 2021/01/30 18:51:08 gocache is running at http://localhost:8002
66 | 2021/01/30 18:51:08 gocache is running at http://localhost:8001
67 | 2021/01/30 18:51:08 gocache is running at http://localhost:8003
68 | 2021/01/30 18:51:08 fontend server is running at http://localhost:9999
69 | >>> start test
70 | 2021/01/30 18:51:10 http_pool.go:50: [Server http://localhost:8003] pick node http://localhost:8001
71 | 2021/01/30 18:51:10 http_pool.go:50: [Server http://localhost:8001] GET /_cache/scores/Tom -- group=scores key=Tom
72 | 2021/01/30 18:51:10 main.go:22: [SlowDB] search key Tom
73 | 6306306302021/01/30 18:51:46 http_pool.go:50: [Server http://localhost:8003] pick node http://localhost:8001
74 | 2021/01/30 18:51:46 http_pool.go:50: [Server http://localhost:8001] GET /_cache/scores/Tom -- group=scores key=Tom
75 | 2021/01/30 18:51:46 gocache.go:81: read from local cache 0xc00000e6a0
76 | ```
77 |
78 |
79 | 仅用于学习
--------------------------------------------------------------------------------
/consistenthash/chash.go:
--------------------------------------------------------------------------------
1 | package consistenthash
2 |
3 | import (
4 | "hash/crc32"
5 | "sort"
6 | "strconv"
7 | )
8 |
9 | /**
10 | 一致性哈希 ---从单节点走向分布式节点的一个重要的环节
11 |
12 | 算法原理:
13 | 一致性hash算法将key映射到2^32的空间,一个环状结构 0 - 2^32-1
14 | * 计算节点/机器(通常为节点的编号、名称或IP地址)的哈希值,放到环上
15 | * 计算key的哈希值,放到环上,顺时针寻找到key后面的第一个节点就是key存储的位置,就是选取的机器
16 |
17 | 这样也就是说,在新增或者删除节点时,只需要重新定位节点附近的一小块数据区域,而不需要重新定位所有
18 | 数据
19 |
20 | 如果服务器的节点过少,容易引起 key 的倾斜。容易造成一大部分 分布在环的上半部分,下半部分是空的。
21 | 那么映射到环下半部分的 key 都会被分配给 peer2,key 过度向 peer2 倾斜,缓存节点间负载不均。
22 |
23 | 为了解决这个问题,引入了虚拟节点的概念,一个真实节点对应多个虚拟节点。
24 |
25 | 假设 1 个真实节点对应 3 个虚拟节点,那么 peer1 对应的虚拟节点是 peer1-1、 peer1-2、 peer1-3(通常以添加编号的方式实现),
26 | 其余节点也以相同的方式操作。
27 |
28 | 第一步,计算虚拟节点的 Hash 值,放置在环上。
29 | 第二步,计算 key 的 Hash 值,在环上顺时针寻找到应选取的虚拟节点,例如是 peer2-1,那么就对应真实节点 peer2。
30 | 虚拟节点扩充了节点的数量,解决了节点较少的情况下数据容易倾斜的问题。而且代价非常小,
31 | 只需要增加一个字典(map)维护真实节点与虚拟节点的映射关系即可。
32 |
33 |
34 | */
35 |
36 | // Hash a hash maps bytes to uint32
37 | type Hash func(data []byte) uint32
38 |
39 | // Map contains all hashed keys
40 | type Map struct {
41 | hash Hash // 注入哈希处理函数
42 | keys []uint32 // 哈希环 sorted
43 | virtualNum int // 每个节点对应虚拟节点的数目
44 |
45 | // 存放虚拟节点和真实节点的数目 key=hash(b"i-真实节点id") value=真实节点id
46 | hashMap map[uint32]string
47 | }
48 |
49 | func New(vNum int, hash Hash) *Map {
50 | m := &Map{
51 | hash: hash,
52 | virtualNum: vNum,
53 | hashMap: make(map[uint32]string),
54 | }
55 |
56 | if m.hash == nil {
57 | m.hash = crc32.ChecksumIEEE
58 | }
59 | return m
60 | }
61 |
62 | // 添加真实/虚拟节点函数
63 | func (m *Map) Add(keys ...string) {
64 | for _, key := range keys {
65 | for i := 0; i < m.virtualNum; i++ {
66 | // 计算虚拟节点的hash值
67 | hash := m.hash([]byte(strconv.Itoa(i) + key))
68 | // 将虚拟节点的hash值添加到换上
69 | m.keys = append(m.keys, hash)
70 | // 增加虚拟节点和真实节点的映射关系
71 | m.hashMap[hash] = key
72 | }
73 | }
74 |
75 | sort.Slice(m.keys, func(i, j int) bool {
76 | return m.keys[i] < m.keys[j]
77 | })
78 | }
79 |
80 | // Get 实现选择节点的get方法
81 | func (m *Map) Get(key string) string {
82 | if len(key) == 0 {
83 | return ""
84 | }
85 |
86 | hash := m.hash([]byte(key))
87 | // 采用二分查找顺时针查找第一个匹配的虚拟节点
88 | idx := sort.Search(len(m.keys), func(i int) bool {
89 | return m.keys[i] >= hash
90 | })
91 |
92 | // idx == len(m.keys) 在没有找到的情况下返回len(m.keys)
93 | // 说明应选择 m.keys[0],因为 m.keys 是一个环状结构,所以用取余数的方式来处理这种情况。
94 | return m.hashMap[m.keys[idx%len(m.keys)]]
95 | }
96 |
--------------------------------------------------------------------------------
/demo/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "net/http"
8 |
9 | "github.com/devhg/gocache"
10 | )
11 |
12 | //用map模仿一个慢的数据库
13 | var db = map[string]string{
14 | "Tom": "630",
15 | "Jack": "589",
16 | "Sam": "567",
17 | }
18 |
19 | func createGroup() *gocache.Group {
20 | // 创建一个名字为 scores的 group。并注册真正的回调函数
21 | return gocache.NewGroup("scores", 2<<10, gocache.GetterFunc(
22 | func(key string) ([]byte, error) {
23 | log.Println("[SlowDB] search key", key)
24 | if v, ok := db[key]; ok {
25 | return []byte(v), nil
26 | }
27 | log.Println("[SlowDB] key is not exist", key)
28 | return nil, fmt.Errorf("%s not exist", key)
29 | }))
30 | }
31 |
32 | // startCacheServer 开启一个缓存服务
33 | func startCacheServer(addr string, addrs []string, group *gocache.Group) {
34 | // 创建一个节点选择器
35 | pool := gocache.NewHTTPPool(addr)
36 | pool.SetNodes(addrs...) // 节点选择器设置添加节点
37 |
38 | // 注册节点选择器 到group
39 | group.RegisterPicker(pool)
40 |
41 | log.Println("gocache is running at", addr)
42 | log.Fatal(http.ListenAndServe(addr[7:], pool))
43 | }
44 |
45 | // startAPIServer 创建一个对外的 REST Full API 服务
46 | func startAPIServer(apiAddr string, group *gocache.Group) {
47 | // Register statsviz handlers on the default serve mux.
48 | //statsviz.RegisterDefault()
49 | //http.ListenAndServe(":8080", nil)
50 |
51 | //http.Handle("/api", http.HandlerFunc(
52 | // func(w http.ResponseWriter, r *http.Request) {
53 | // key := r.URL.Query().Get("key")
54 | // byteView, err := group.Get(key)
55 | // if err != nil {
56 | // http.Error(w, err.Error(), http.StatusInternalServerError)
57 | // }
58 | //
59 | // w.Header().Set("Content-Type", "application/octet-stream")
60 | // w.Write(byteView.ByteSlice())
61 | // }))
62 |
63 | http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
64 | key := r.URL.Query().Get("key")
65 | byteView, err := group.Get(key)
66 | if err != nil {
67 | http.Error(w, err.Error(), http.StatusInternalServerError)
68 | }
69 |
70 | w.Header().Set("Content-Type", "application/octet-stream")
71 | w.Write(byteView.ByteSlice())
72 | })
73 |
74 | log.Println("fontend server is running at", apiAddr)
75 | log.Fatal(http.ListenAndServe(apiAddr[7:], nil))
76 | }
77 |
78 | func main() {
79 | var port int
80 | var api bool
81 | // 分别读取端口 和 是否为api server
82 | // ./server -port=8003 -api=1 &
83 | flag.IntVar(&port, "port", 8001, "Geecache server port")
84 | flag.BoolVar(&api, "api", false, "Start a api server?")
85 | flag.Parse()
86 |
87 | apiAddr := "http://localhost:9999"
88 |
89 | // 缓存节点
90 | addrMap := map[int]string{
91 | 8001: "http://localhost:8001",
92 | 8002: "http://localhost:8002",
93 | 8003: "http://localhost:8003",
94 | }
95 |
96 | var addrs []string
97 | for _, v := range addrMap {
98 | addrs = append(addrs, v)
99 | }
100 |
101 | // 创建好的cacheGroup
102 | gfcache := createGroup()
103 |
104 | if api {
105 | go startAPIServer(apiAddr, gfcache)
106 | }
107 |
108 | startCacheServer(addrMap[port], addrs, gfcache)
109 | }
110 |
--------------------------------------------------------------------------------
/lru/lru.go:
--------------------------------------------------------------------------------
1 | package lru
2 |
3 | import (
4 | "container/list"
5 | )
6 |
7 | const (
8 | maxBytes = 1 << 32
9 | maxEntries = 10 << 10
10 | )
11 |
12 | type Cache struct {
13 | maxBytes int64 // 最大使用内存
14 | nowBytes int64 // 已经使用的内存
15 |
16 | maxEntries int
17 |
18 | ll *list.List
19 | cache map[string]*list.Element
20 |
21 | // 是某条记录被移除时的回调函数,可以为 nil
22 | onEvicted func(key string, value Value)
23 | }
24 |
25 | // a config for cache
26 | type CacheConfig struct {
27 | MaxBytes int64 // 最大使用内存
28 | MaxEntries int // 最大缓存数目
29 |
30 | // 淘汰回调函数
31 | OnEvicted func(string, Value)
32 | }
33 |
34 | // Value use Len to count how many bytes it takes
35 | type Value interface {
36 | Len() int
37 | }
38 |
39 | // 双向链表节点的数据类型,保存key的目的是淘汰队首节点时,
40 | type entry struct {
41 | key string
42 | value Value
43 | }
44 |
45 | func New(config *CacheConfig) *Cache {
46 | c := &Cache{
47 | maxBytes: maxBytes,
48 | maxEntries: maxEntries,
49 | ll: list.New(),
50 | cache: make(map[string]*list.Element),
51 | }
52 | if config == nil {
53 | return c
54 | }
55 |
56 | if config.MaxBytes != 0 {
57 | c.maxBytes = config.MaxBytes
58 | }
59 | if config.MaxEntries != 0 {
60 | c.maxEntries = config.MaxEntries
61 | }
62 | if config.OnEvicted != nil {
63 | c.onEvicted = config.OnEvicted
64 | }
65 | return c
66 | }
67 |
68 | // 按key 添加缓存
69 | func (c *Cache) Add(key string, val Value) {
70 | if c.cache == nil {
71 | c.cache = make(map[string]*list.Element)
72 | c.ll = list.New()
73 | }
74 |
75 | // 缓存命中
76 | if ele, ok := c.cache[key]; ok {
77 | c.ll.MoveToFront(ele)
78 | // 更新缓存内容
79 | kv := ele.Value.(*entry).value
80 | c.nowBytes += int64(val.Len()) - int64(kv.Len())
81 | return
82 | }
83 |
84 | // 缓存未命中
85 | ele := c.ll.PushFront(&entry{key: key, value: val})
86 | c.nowBytes += int64(val.Len() + len(key))
87 | c.cache[key] = ele
88 |
89 | // 超过最大缓存数目 淘汰
90 | if c.maxEntries != 0 && c.ll.Len() > c.maxEntries {
91 | c.RemoveOldest()
92 | }
93 |
94 | // 超过最大缓存容量 淘汰
95 | for c.maxBytes != 0 && c.nowBytes > c.maxBytes {
96 | c.RemoveOldest()
97 | }
98 | }
99 |
100 | // 删除最近最少使用的缓存
101 | func (c *Cache) RemoveOldest() {
102 | if c.cache == nil {
103 | return
104 | }
105 | lastEle := c.ll.Back()
106 | if lastEle != nil {
107 | c.removeElement(lastEle)
108 | }
109 | }
110 |
111 | // 按key 删除缓存
112 | func (c *Cache) Remove(key string) bool {
113 | if c.cache == nil {
114 | return false
115 | }
116 | if ele, ok := c.cache[key]; ok {
117 | c.removeElement(ele)
118 | return true
119 | }
120 | return false
121 | }
122 |
123 | func (c *Cache) removeElement(ele *list.Element) {
124 | c.ll.Remove(ele)
125 | val := ele.Value.(*entry)
126 |
127 | // 缓存容量减少
128 | c.nowBytes -= int64(len(val.key)) + int64(val.value.Len())
129 | delete(c.cache, val.key)
130 |
131 | if c.onEvicted != nil {
132 | c.onEvicted(val.key, val.value)
133 | }
134 | }
135 |
136 | // 获取缓存
137 | func (c *Cache) Get(key string) (val Value, ok bool) {
138 | if c.cache == nil {
139 | return nil, false
140 | }
141 | if ele, ok := c.cache[key]; ok {
142 | c.ll.PushFront(ele)
143 | val := ele.Value.(*entry)
144 | return val.value, true
145 | }
146 | return
147 | }
148 |
149 | // Len the number of cache entries
150 | func (c *Cache) Len() int {
151 | return c.ll.Len()
152 | }
153 |
--------------------------------------------------------------------------------
/http_pool.go:
--------------------------------------------------------------------------------
1 | package gocache
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "strings"
8 | "sync"
9 |
10 | "github.com/devhg/gocache/consistenthash"
11 | pb "github.com/devhg/gocache/gocachepb"
12 | "google.golang.org/protobuf/proto"
13 | )
14 |
15 | const (
16 | defaultBasePath = "/_cache/"
17 | defaultVirtualNum = 50
18 | )
19 |
20 | // NodePicker 节点选择器
21 | type NodePicker interface {
22 | // 利用一致性哈希算法,根据传入的 key 选择相应节点
23 | // 并返回节点处理器NodeGetter。
24 | PickNode(key string) (NodeGetter, bool)
25 | }
26 |
27 | // HTTPPool implements PeerPicker for a pool of HTTP peers.
28 | type HTTPPool struct {
29 | basePath string // 请求路径基础前缀/_cache/
30 | selfAddr string // 本节点自身的ip:port
31 |
32 | // 映射远程节点与对应的 httpGetter。每一个远程节点对应一个 httpGetter,
33 | // 因为 httpGetter 与远程节点的地址 baseURL 有关
34 | httpGetters map[string]*httpGetter
35 |
36 | // 一致性哈希存放节点,用来根据具体的 key 选择节点
37 | nodes *consistenthash.Map
38 | mu sync.Mutex
39 | }
40 |
41 | func NewHTTPPool(selfAddr string) *HTTPPool {
42 | return &HTTPPool{
43 | selfAddr: selfAddr,
44 | basePath: defaultBasePath,
45 | }
46 | }
47 |
48 | // print the Log of HTTPPool
49 | func (p *HTTPPool) Logf(format string, v ...interface{}) {
50 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
51 | log.Printf("[Server %s] %s\n", p.selfAddr, fmt.Sprintf(format, v...))
52 | }
53 |
54 | // ServeHTTP handle all http requests
55 | func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
56 | if r.URL.RequestURI() == "/favicon.ico" {
57 | return
58 | }
59 | if !strings.HasPrefix(r.URL.Path, p.basePath) {
60 | panic("HTTPPool serving unexpected path: " + r.URL.Path)
61 | }
62 |
63 | // /// required
64 | parts := strings.SplitN(r.URL.Path, "/", 5)
65 |
66 | groupName := parts[2]
67 | key := parts[3]
68 |
69 | p.Logf("%s %s -- group=%s key=%s", r.Method, r.URL.Path, groupName, key)
70 | group := GetGroup(groupName)
71 |
72 | if group == nil {
73 | http.Error(w, "no such group: "+groupName, http.StatusNotFound)
74 | return
75 | }
76 |
77 | byteView, err := group.Get(key)
78 |
79 | if err != nil {
80 | http.Error(w, err.Error(), http.StatusNotFound)
81 | return
82 | }
83 |
84 | resp, err := proto.Marshal(&pb.Response{Value: byteView.ByteSlice()})
85 | if err != nil {
86 | http.Error(w, err.Error(), http.StatusNotFound)
87 | return
88 | }
89 |
90 | w.Header().Set("Content-Type", "application/octet-stream")
91 | _, _ = w.Write(resp)
92 | }
93 |
94 | // Set the pool's list of nodes' key.
95 | // example: key=http://10.0.0.1:9305
96 | func (p *HTTPPool) SetNodes(nodeKeys ...string) {
97 | p.mu.Lock()
98 | defer p.mu.Unlock()
99 |
100 | // 创建添加节点到一致性哈希
101 | p.nodes = consistenthash.New(defaultVirtualNum, nil)
102 | p.nodes.Add(nodeKeys...)
103 |
104 | p.httpGetters = make(map[string]*httpGetter)
105 | for _, nodeKey := range nodeKeys {
106 | p.httpGetters[nodeKey] = &httpGetter{baseURL: nodeKey + p.basePath}
107 | }
108 | }
109 |
110 | // PickNode method picks a node according to key
111 | // 具体的 key,选择节点,返回节点对应的HTTP处理器(NodeGetter)。
112 | func (p *HTTPPool) PickNode(key string) (NodeGetter, bool) {
113 | p.mu.Lock()
114 | defer p.mu.Unlock()
115 |
116 | if nodeKey := p.nodes.Get(key); nodeKey != "" && nodeKey != p.selfAddr {
117 | p.Logf("pick node %s", nodeKey)
118 | return p.httpGetters[nodeKey], true
119 | }
120 | return nil, false
121 | }
122 |
123 | var _ NodePicker = (*HTTPPool)(nil)
124 |
--------------------------------------------------------------------------------
/gocache.go:
--------------------------------------------------------------------------------
1 | package gocache
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "sync"
7 |
8 | pb "github.com/devhg/gocache/gocachepb"
9 | "github.com/devhg/gocache/singlereq"
10 | )
11 |
12 | // DataGetter 对外回调函数接口
13 | // A Getter loads data from DB or etc. for a key.
14 | type DataGetter interface {
15 | Get(key string) ([]byte, error)
16 | }
17 |
18 | // 定义一个函数类型 F,并且实现接口 A 的方法,然后在这个方法中调用自己。
19 | // 这是 Go 语言中将其他函数(参数返回值定义与 F 一致)转换为接口 A 的常用技巧。
20 | // A GetterFunc implements DataGetter with a function.
21 | type GetterFunc func(string) ([]byte, error)
22 |
23 | // Get implements DataGetter interface function
24 | func (g GetterFunc) Get(key string) ([]byte, error) {
25 | return g(key)
26 | }
27 |
28 | // 一个group可以被认为一个缓存的命名空间
29 | // 每一个group拥有一个唯一的name,这样可以创建多个group
30 | type Group struct {
31 | name string
32 | cacheBytes int64
33 | dataGetter DataGetter // 缓存未命中时获取数据源的回调
34 |
35 | // main cache support safe concurrent
36 | mainCache cache
37 |
38 | // 保证并发只会请求一次
39 | singleReq *singlereq.ReqGroup
40 |
41 | // nodePicker 节点选择器
42 | picker NodePicker
43 | }
44 |
45 | var (
46 | mu sync.RWMutex
47 | groups = make(map[string]*Group)
48 | )
49 |
50 | func NewGroup(name string, cacheBytes int64, getter DataGetter) *Group {
51 | if getter == nil {
52 | panic("dataGetter is needed")
53 | }
54 | mu.Lock()
55 | defer mu.Unlock()
56 | g := &Group{
57 | name: name,
58 | mainCache: cache{cacheBytes: cacheBytes},
59 | dataGetter: getter,
60 | singleReq: &singlereq.ReqGroup{},
61 | }
62 | groups[name] = g
63 | return groups[name]
64 | }
65 |
66 | func GetGroup(name string) *Group {
67 | // 共享锁
68 | mu.RLock()
69 | defer mu.RUnlock()
70 | g := groups[name]
71 | return g
72 | }
73 |
74 | func (g *Group) Get(key string) (ByteView, error) {
75 | if key == "" {
76 | return ByteView{}, fmt.Errorf("key is required")
77 | }
78 |
79 | // 在本机缓存中查找
80 | if byteView, ok := g.mainCache.get(key); ok {
81 | log.Printf("read from local cache %p", &byteView)
82 | return byteView, nil
83 | }
84 |
85 | // 去其他节点查找或者从数据库从新缓存
86 | return g.load(key)
87 | }
88 |
89 | func (g *Group) load(key string) (byteView ByteView, err error) {
90 | // 每一个key只允许请求一次远程服务器或者db 防止缓存击穿
91 | val, err := g.singleReq.Do(key, func() (i interface{}, err error) {
92 | if g.picker != nil {
93 | if nodeGetter, ok := g.picker.PickNode(key); ok {
94 | if byteView, err = g.getFromNode(nodeGetter, key); err == nil {
95 | return byteView, nil
96 | }
97 | log.Println("[goCache] Failed to get from other node", err)
98 | }
99 | }
100 | return g.getLocally(key)
101 | })
102 | if err == nil {
103 | return val.(ByteView), nil
104 | }
105 | return
106 | }
107 |
108 | // getLocally 从自定义的回调函数中获取缓存中没有的资源
109 | func (g *Group) getLocally(key string) (ByteView, error) {
110 | bytes, err := g.dataGetter.Get(key)
111 | if err != nil {
112 | log.Println("[goCache] Failed to get from dataSource", err)
113 | return ByteView{}, err
114 | }
115 | byteView := ByteView{b: cloneBytes(bytes)}
116 | g.populateCache(key, byteView)
117 | return byteView, nil
118 | }
119 |
120 | // 缓存到当前节点的group
121 | func (g *Group) populateCache(key string, val ByteView) {
122 | g.mainCache.add(key, val)
123 | }
124 |
125 | // 将实现了 NodePicker 接口的 节点选择器 注入到 Group 中
126 | func (g *Group) RegisterPicker(picker NodePicker) {
127 | if g.picker != nil {
128 | panic("RegisterPeerPicker called more than once")
129 | }
130 | g.picker = picker
131 | }
132 |
133 | // 用实现了 NodeGetter 接口访问远程节点,获取缓存值
134 | func (g *Group) getFromNode(getter NodeGetter, key string) (ByteView, error) {
135 | request := &pb.Request{Group: g.name, Key: key}
136 | response := &pb.Response{}
137 | err := getter.Get(request, response)
138 | if err != nil {
139 | return ByteView{}, err
140 | }
141 | return ByteView{b: response.Value}, nil
142 | }
143 |
144 | // getLocally 从自定义的回调函数中获取缓存中没有的资源
145 | func (g *Group) GetCacheBytes(key string) int64 {
146 | return g.cacheBytes
147 | }
148 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
5 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
6 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
8 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
9 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
10 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
11 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
12 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
13 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
14 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
15 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
16 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
17 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
18 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
19 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
20 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
21 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
22 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
23 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
24 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
25 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
27 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
28 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
29 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
30 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
31 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
32 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
33 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
34 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
35 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
36 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
37 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
38 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
39 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
41 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
42 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
43 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
44 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
45 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
46 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
47 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
48 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
49 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
50 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
51 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
52 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
53 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
54 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
55 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
56 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
57 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
58 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
59 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
60 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
61 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
62 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
63 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
64 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
65 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
66 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
67 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
68 |
--------------------------------------------------------------------------------
/demo/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/arl/statsviz v0.1.1 h1:BLJ5hIN3Sf1cWtJ6YhMt0WH41+Z6LBmHG8YXJDJpGl8=
4 | github.com/arl/statsviz v0.1.1/go.mod h1:Dg/DhcWPSzBVk70gVbZWcymzHDkYRhVpeScx5l+Zj7o=
5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
7 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
8 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
12 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
13 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
14 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
15 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
16 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
17 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
18 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
19 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
20 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
21 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
22 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
23 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
24 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
25 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
26 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
27 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
28 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
29 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
30 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
31 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
32 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
33 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
34 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
35 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
36 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
37 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
38 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
39 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
40 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
41 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
42 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
44 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
45 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
46 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
47 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
48 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
49 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
50 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
51 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
52 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
53 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
54 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
55 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
56 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
57 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
58 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
59 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
60 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
61 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
62 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
63 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
64 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
65 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
66 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
67 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
68 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
69 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
70 |
--------------------------------------------------------------------------------
/gocachepb/cache.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.23.0
4 | // protoc v3.14.0
5 | // source: cache.proto
6 |
7 | package gocachepb
8 |
9 | import (
10 | proto "github.com/golang/protobuf/proto"
11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
13 | reflect "reflect"
14 | sync "sync"
15 | )
16 |
17 | const (
18 | // Verify that this generated code is sufficiently up-to-date.
19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
20 | // Verify that runtime/protoimpl is sufficiently up-to-date.
21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
22 | )
23 |
24 | // This is a compile-time assertion that a sufficiently up-to-date version
25 | // of the legacy proto package is being used.
26 | const _ = proto.ProtoPackageIsVersion4
27 |
28 | type Request struct {
29 | state protoimpl.MessageState
30 | sizeCache protoimpl.SizeCache
31 | unknownFields protoimpl.UnknownFields
32 |
33 | Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"`
34 | Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
35 | }
36 |
37 | func (x *Request) Reset() {
38 | *x = Request{}
39 | if protoimpl.UnsafeEnabled {
40 | mi := &file_cache_proto_msgTypes[0]
41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
42 | ms.StoreMessageInfo(mi)
43 | }
44 | }
45 |
46 | func (x *Request) String() string {
47 | return protoimpl.X.MessageStringOf(x)
48 | }
49 |
50 | func (*Request) ProtoMessage() {}
51 |
52 | func (x *Request) ProtoReflect() protoreflect.Message {
53 | mi := &file_cache_proto_msgTypes[0]
54 | if protoimpl.UnsafeEnabled && x != nil {
55 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
56 | if ms.LoadMessageInfo() == nil {
57 | ms.StoreMessageInfo(mi)
58 | }
59 | return ms
60 | }
61 | return mi.MessageOf(x)
62 | }
63 |
64 | // Deprecated: Use Request.ProtoReflect.Descriptor instead.
65 | func (*Request) Descriptor() ([]byte, []int) {
66 | return file_cache_proto_rawDescGZIP(), []int{0}
67 | }
68 |
69 | func (x *Request) GetGroup() string {
70 | if x != nil {
71 | return x.Group
72 | }
73 | return ""
74 | }
75 |
76 | func (x *Request) GetKey() string {
77 | if x != nil {
78 | return x.Key
79 | }
80 | return ""
81 | }
82 |
83 | type Response struct {
84 | state protoimpl.MessageState
85 | sizeCache protoimpl.SizeCache
86 | unknownFields protoimpl.UnknownFields
87 |
88 | Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
89 | }
90 |
91 | func (x *Response) Reset() {
92 | *x = Response{}
93 | if protoimpl.UnsafeEnabled {
94 | mi := &file_cache_proto_msgTypes[1]
95 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
96 | ms.StoreMessageInfo(mi)
97 | }
98 | }
99 |
100 | func (x *Response) String() string {
101 | return protoimpl.X.MessageStringOf(x)
102 | }
103 |
104 | func (*Response) ProtoMessage() {}
105 |
106 | func (x *Response) ProtoReflect() protoreflect.Message {
107 | mi := &file_cache_proto_msgTypes[1]
108 | if protoimpl.UnsafeEnabled && x != nil {
109 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
110 | if ms.LoadMessageInfo() == nil {
111 | ms.StoreMessageInfo(mi)
112 | }
113 | return ms
114 | }
115 | return mi.MessageOf(x)
116 | }
117 |
118 | // Deprecated: Use Response.ProtoReflect.Descriptor instead.
119 | func (*Response) Descriptor() ([]byte, []int) {
120 | return file_cache_proto_rawDescGZIP(), []int{1}
121 | }
122 |
123 | func (x *Response) GetValue() []byte {
124 | if x != nil {
125 | return x.Value
126 | }
127 | return nil
128 | }
129 |
130 | var File_cache_proto protoreflect.FileDescriptor
131 |
132 | var file_cache_proto_rawDesc = []byte{
133 | 0x0a, 0x0b, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x67,
134 | 0x6f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, 0x62, 0x22, 0x31, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75,
135 | 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01,
136 | 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
137 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x08, 0x52,
138 | 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
139 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x37, 0x0a,
140 | 0x05, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x2e, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x12, 0x2e,
141 | 0x67, 0x6f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
142 | 0x74, 0x1a, 0x13, 0x2e, 0x67, 0x6f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, 0x62, 0x2e, 0x52, 0x65,
143 | 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
144 | }
145 |
146 | var (
147 | file_cache_proto_rawDescOnce sync.Once
148 | file_cache_proto_rawDescData = file_cache_proto_rawDesc
149 | )
150 |
151 | func file_cache_proto_rawDescGZIP() []byte {
152 | file_cache_proto_rawDescOnce.Do(func() {
153 | file_cache_proto_rawDescData = protoimpl.X.CompressGZIP(file_cache_proto_rawDescData)
154 | })
155 | return file_cache_proto_rawDescData
156 | }
157 |
158 | var file_cache_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
159 | var file_cache_proto_goTypes = []interface{}{
160 | (*Request)(nil), // 0: gocachepb.Request
161 | (*Response)(nil), // 1: gocachepb.Response
162 | }
163 | var file_cache_proto_depIdxs = []int32{
164 | 0, // 0: gocachepb.Cache.Get:input_type -> gocachepb.Request
165 | 1, // 1: gocachepb.Cache.Get:output_type -> gocachepb.Response
166 | 1, // [1:2] is the sub-list for method output_type
167 | 0, // [0:1] is the sub-list for method input_type
168 | 0, // [0:0] is the sub-list for extension type_name
169 | 0, // [0:0] is the sub-list for extension extendee
170 | 0, // [0:0] is the sub-list for field type_name
171 | }
172 |
173 | func init() { file_cache_proto_init() }
174 | func file_cache_proto_init() {
175 | if File_cache_proto != nil {
176 | return
177 | }
178 | if !protoimpl.UnsafeEnabled {
179 | file_cache_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
180 | switch v := v.(*Request); i {
181 | case 0:
182 | return &v.state
183 | case 1:
184 | return &v.sizeCache
185 | case 2:
186 | return &v.unknownFields
187 | default:
188 | return nil
189 | }
190 | }
191 | file_cache_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
192 | switch v := v.(*Response); i {
193 | case 0:
194 | return &v.state
195 | case 1:
196 | return &v.sizeCache
197 | case 2:
198 | return &v.unknownFields
199 | default:
200 | return nil
201 | }
202 | }
203 | }
204 | type x struct{}
205 | out := protoimpl.TypeBuilder{
206 | File: protoimpl.DescBuilder{
207 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
208 | RawDescriptor: file_cache_proto_rawDesc,
209 | NumEnums: 0,
210 | NumMessages: 2,
211 | NumExtensions: 0,
212 | NumServices: 1,
213 | },
214 | GoTypes: file_cache_proto_goTypes,
215 | DependencyIndexes: file_cache_proto_depIdxs,
216 | MessageInfos: file_cache_proto_msgTypes,
217 | }.Build()
218 | File_cache_proto = out.File
219 | file_cache_proto_rawDesc = nil
220 | file_cache_proto_goTypes = nil
221 | file_cache_proto_depIdxs = nil
222 | }
223 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------