├── .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 | ![](https://cdn.jsdelivr.net/gh/QXQZX/CDN@latest/images/go/gfcache/framework.png) 35 | 创建流程 36 | ![](https://cdn.jsdelivr.net/gh/QXQZX/CDN@latest/images/go/gfcache/runAndUse.png) 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 | --------------------------------------------------------------------------------