基于HTTP的分布式缓存

目的:通过main 函数启动 HTTP Server测试API

Cache HTTP 服务端

分布式缓存需要实现节点间通信,建立基于 HTTP 的通信机制是比较常见和简单的做法。如果一个节点启动了 HTTP 服务,那么这个节点就可以被其他节点访问。

具体流程

  • 默认通信地址前缀是defaultBasePath = "/_gocache/"
  • 首先判断url路径中是否包含 basePath。
  • 把groupname/key字符截断为groupname和key。
  • 通过groupname获取Group对象。
  • 使用Group对象方法和key来获取key对应的缓存值。
  • 将缓存值作为http body进行响应。

代码http.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package Cache

import (
"net/http"
"strings"
)

const defaultBasePath = "/_gocache/"

type HttpPool struct {
self string // 记录本地地址和端口
basePath string // 基础路径
}

func NewHTTPPool(self string) *HttpPool {
return &HttpPool{
self: self,
basePath: defaultBasePath,
}
}

func (hp *HttpPool) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
// 1. 判断url路径中是否包含 basePath
url := req.URL.Path
if !strings.HasPrefix(url, hp.basePath) {
panic("http request path is invalid")
}

// 2. url路径为/_groupcache/<groupname>/<key> -> <groupname>/<key>
path := url[len(hp.basePath):]

// 3. 把<groupname>/<key>字符截断为groupname和key
parts := strings.SplitN(path, "/", 2)
groupName := parts[0]
key := parts[1]

// 4. 通过groupname获取Group对象
group := GetGroup(groupName)
if group == nil {
http.Error(resp, "no such group: "+groupName, http.StatusNotFound)
return
}

// 5. 使用Group对象方法和key来获取key对应的缓存值
byteview, err := group.Get(key)
if err != nil {
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}
// 6. 将缓存值作为http body进行响应
resp.Header().Set("Content-Type", "application/octet-stream")
resp.Write(byteview.ByteSlice())
}

测试

  • 使用 map 模拟了数据源 db。
  • 创建一个名为 scores 的 Group,若缓存为空,回调函数会从 db 中获取数据并返回。
  • 使用 http.ListenAndServe 在 9999 端口启动了 HTTP 服务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"Cache/Cache"
"fmt"
"log"
"net/http"
)

var db = map[string]string{
"Tom": "630",
"Jack": "589",
"Sam": "567",
}

func main() {
Cache.NewGroup("scores", 2<<10, Cache.GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := db[key]; ok {
return []byte(v), nil
}
return nil, fmt.Errorf("%s not exist", key)
}))

addr := "localhost:9999"
peers := Cache.NewHTTPPool(addr)
log.Println("gocache is running at", addr)
log.Fatal(http.ListenAndServe(addr, peers))
}

测试API

测试1

测试2

测试结果

总结

通过main 函数启动 HTTP Server测试API。