动手写分布式缓存 - GeeCache第五天 分布式节点
源代码/数据集已上传到 Github - 7days-golang
本文是7天用Go从零实现分布式缓存GeeCache的第五篇。
- 注册节点(Register Peers),借助一致性哈希算法选择节点。
- 实现 HTTP 客户端,与远程节点的服务端通信,代码约90行
1 流程回顾
1 | 是 |
我们在GeeCache 第二天 中描述了 geecache 的流程。在这之前已经实现了流程 ⑴ 和 ⑶,今天实现流程 ⑵,从远程节点获取缓存值。
我们进一步细化流程 ⑵:
1 | 使用一致性哈希选择节点 是 是 |
2 抽象 PeerPicker
day5-multi-nodes/geecache/peers.go - github
1 | package geecache |
- 在这里,抽象出 2 个接口,PeerPicker 的
PickPeer()
方法用于根据传入的 key 选择相应节点 PeerGetter。 - 接口 PeerGetter 的
Get()
方法用于从对应 group 查找缓存值。PeerGetter 就对应于上述流程中的 HTTP 客户端。
3 节点选择与 HTTP 客户端
在 GeeCache 第三天 中我们为 HTTPPool
实现了服务端功能,通信不仅需要服务端还需要客户端,因此,我们接下来要为 HTTPPool
实现客户端的功能。
首先创建具体的 HTTP 客户端类 httpGetter
,实现 PeerGetter 接口。
day5-multi-nodes/geecache/http.go - github
1 | type httpGetter struct { |
- baseURL 表示将要访问的远程节点的地址,例如
http://example.com/_geecache/
。 - 使用
http.Get()
方式获取返回值,并转换为[]bytes
类型。
第二步,为 HTTPPool 添加节点选择的功能。
1 | const ( |
- 新增成员变量
peers
,类型是一致性哈希算法的Map
,用来根据具体的 key 选择节点。 - 新增成员变量
httpGetters
,映射远程节点与对应的 httpGetter。每一个远程节点对应一个 httpGetter,因为 httpGetter 与远程节点的地址baseURL
有关。
第三步,实现 PeerPicker 接口。
1 | // Set updates the pool's list of peers. |
Set()
方法实例化了一致性哈希算法,并且添加了传入的节点。- 并为每一个节点创建了一个 HTTP 客户端
httpGetter
。 PickerPeer()
包装了一致性哈希算法的Get()
方法,根据具体的 key,选择节点,返回节点对应的 HTTP 客户端。
至此,HTTPPool 既具备了提供 HTTP 服务的能力,也具备了根据具体的 key,创建 HTTP 客户端从远程节点获取缓存值的能力。
4 实现主流程
最后,我们需要将上述新增的功能集成在主流程(geecache.go)中。
day5-multi-nodes/geecache/geecache.go - github
1 | // A Group is a cache namespace and associated data loaded spread over |
- 新增
RegisterPeers()
方法,将 实现了 PeerPicker 接口的 HTTPPool 注入到 Group 中。 - 新增
getFromPeer()
方法,使用实现了 PeerGetter 接口的 httpGetter 从访问远程节点,获取缓存值。 - 修改 load 方法,使用
PickPeer()
方法选择节点,若非本机节点,则调用getFromPeer()
从远程获取。若是本机节点或失败,则回退到getLocally()
。
5 main 函数测试。
day5-multi-nodes/main.go - github
1 | var db = map[string]string{ |
main 函数的代码比较多,但是逻辑是非常简单的。
startCacheServer()
用来启动缓存服务器:创建 HTTPPool,添加节点信息,注册到 gee 中,启动 HTTP 服务(共3个端口,8001/8002/8003),用户不感知。startAPIServer()
用来启动一个 API 服务(端口 9999),与用户进行交互,用户感知。main()
函数需要命令行传入port
和api
2 个参数,用来在指定端口启动 HTTP 服务。
为了方便,我们将启动的命令封装为一个 shell
脚本:
1 |
|
trap
命令用于在 shell 脚本退出时,删掉临时文件,结束子进程。
1 | $ ./run.sh |
此时,我们可以打开一个新的 shell,进行测试:
1 | $ curl "http://localhost:9999/api?key=Tom" |
测试的时候,我们并发了 3 个请求 ?key=Tom
,从日志中可以看到,三次均选择了节点 8001
,这是一致性哈希算法的功劳。但是有一个问题在于,同时向 8001
发起了 3 次请求。试想,假如有 10 万个在并发请求该数据呢?那就会向 8001
同时发起 10 万次请求,如果 8001
又同时向数据库发起 10 万次查询请求,很容易导致缓存被击穿。
三次请求的结果是一致的,对于相同的 key,能不能只向 8001
发起一次请求?这个问题下一次解决。
附 推荐阅读
last updated at 2023-11-15