blog icon indicating copy to clipboard operation
blog copied to clipboard

动手写分布式缓存 - GeeCache第五天 分布式节点 | 极客兔兔

Open geektutu opened this issue 5 years ago • 93 comments

https://geektutu.com/post/geecache-day5.html

7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了为 GeeCache 添加了注册节点与选择节点的功能,并实现了 HTTP 客户端,与远程节点的服务端通信。

geektutu avatar Feb 16 '20 17:02 geektutu

感谢博主,一系列的博文看完后,感觉自己的知识架构更加清楚了。 想请教一下博主,resp是否需要先判空后再defer resp.Body.Close()?如果不判空的话,会留下隐患

ylwangs avatar Mar 02 '20 05:03 ylwangs

main.go 58 行。addrs := make([]string, 3) 应该是 addrs := make([]string, 0) 吧,如果一开始长度是 3 ,后边再 append 会得到前边是三个空字符串的切片。

PegasusWang avatar Mar 02 '20 08:03 PegasusWang

@ylwang1122 如果 err != nil,前面已经返回了,不会到 defer 语句,我看了下 http.Get 的实现,如果 err == nil,resp 是不会为空的。

geektutu avatar Mar 02 '20 12:03 geektutu

@PegasusWang,感谢纠错,已经更正。因为后面用了 append,而不是按索引赋值,所以可以不用初始化,改成了:var addrs []string

geektutu avatar Mar 02 '20 12:03 geektutu

感谢博主的文章,是不是还没有实现节点异常的处理。

Kingpie avatar Mar 08 '20 14:03 Kingpie

@Kingpie 仿照的 groupcache 的实现,假定节点是可用的,不包含异常的处理。

geecache 对一致性没有要求,所以也没有必要。需要异常处理的一般是实现 CAP 理论中的 CP,比如分布式数据库,消息系统等。有节点宕机时,需要重新选举 master 以保证一致性。后续会考虑此类系统的实现。

geektutu avatar Mar 08 '20 16:03 geektutu

感谢博主的文章, 这里有一个疑惑想问一下博主,如果要将GeeCache进行横向扩展的话,应该如何部署,可不可以将peer部署到其他机器上

catwithtudou avatar Mar 12 '20 15:03 catwithtudou

@catwithtudou 把 IP 和端口换成部署机器的 IP 和端口就可以了。只是测试用例中,在本机启动了三个实例,基于网络通信,部署在哪里都可以。

geektutu avatar Mar 12 '20 15:03 geektutu

是不是漏掉了从远程节点拿到缓存后更新本地缓存这一步?

walkmiao avatar Mar 26 '20 05:03 walkmiao

是不是漏掉了从远程节点拿到缓存后更新本地缓存这一步?

groupcache 中缓存值只淘汰不更新,也没有超时淘汰机制,这样取舍简化了设计。

geektutu avatar Mar 26 '20 06:03 geektutu

@geektutu

是不是漏掉了从远程节点拿到缓存后更新本地缓存这一步?

groupcache 中缓存值只淘汰不更新,也没有超时淘汰机制,这样取舍简化了设计。

我的意思是当请求当前缓存服务器时 此服务器本地没有缓存 接着由当前服务器去请求其他节点服务器 当拿回来缓存值后 不应该更新此服务器的本地缓存吗 ?

walkmiao avatar Mar 26 '20 07:03 walkmiao

@walkmiao 分布式缓存的目的是不同key缓存在不同的节点上,增加总的吞吐量。如果大家转发请求后,都再备份一次,每台机器上都缓存了相同的数据,就失去意义了。每个节点缓存1G数据,理论上10个节点总共可以缓存10G不同的数据。

当然对于热点数据,每个节点拿到值后,本机备份一次是有价值的,增加热点数据的吞吐量。groupcache 的原生实现中,有1/10的概率会在本机存一次。这样10个节点,理论上可以缓存9G不同的数据,算是一种取舍。

geektutu avatar Mar 26 '20 07:03 geektutu

@geektutu @walkmiao 分布式缓存的目的是不同key缓存在不同的节点上,增加总的吞吐量。如果大家转发请求后,都再备份一次,每台机器上都缓存了相同的数据,就失去意义了。每个节点缓存1G数据,理论上10个节点总共可以缓存10G不同的数据。

当然对于热点数据,每个节点拿到值后,本机备份一次是有价值的,增加热点数据的吞吐量。groupcache 的原生实现中,有1/10的概率会在本机存一次。这样10个节点,理论上可以缓存9G不同的数据,算是一种取舍。

学到很多 谢谢

walkmiao avatar Mar 26 '20 08:03 walkmiao

var _ PeerPicker = (*HTTPPool)(nil) var _ PeerGetter = (*httpGetter)(nil) 请问这两个操作有什么意义呢, 不是很明白

mjyuser avatar Apr 01 '20 09:04 mjyuser

@mjyuser var _ PeerPicker = (*HTTPPool)(nil) var _ PeerGetter = (*httpGetter)(nil) 请问这两个操作有什么意义呢, 不是很明白

确保这个类型实现了这个接口 如果没有实现会报错的

walkmiao avatar Apr 01 '20 12:04 walkmiao

请教博主: 如何才能缓存多个group? 是通过go 开辟新goroutine吗?或者是给多个group注册相同的HTTPPool?

201732110125 avatar Apr 12 '20 13:04 201732110125

如何才能缓存多个group?

都可以,group 和 HTTPPool 是解耦的,可以复用,也可以各自搭配各自的。

geektutu avatar Apr 13 '20 03:04 geektutu

博主你好,请问回调函数中获取数据的数据源,是不是可以不一定来自节点本地数据库。

Assiduous-donkey avatar Jun 14 '20 14:06 Assiduous-donkey

请教一下: 之前在RegisterPeers中如果已经有注册的peers,则不能注册,在main函数中,如果设置了三个端口号那不是在同一个group上注册了三个不同端口的HTTPPool吗?这里是怎么实现的呀?

Leoooo-tqp avatar Aug 09 '20 14:08 Leoooo-tqp

请教一下, 整片文章看了一遍之后, 我还是有点不明白. 用户只知晓API:9999.那么API与分布式缓存是一个1对3的关系. 那么当用户查询的时候,首先查询的get是三个缓存中的哪一个呢? 还是说API本身是一个group 本地有, 没有再去三个缓存中找呢? 这个模型实在是没搞清晰 希望指点

wangy2333 avatar Aug 29 '20 02:08 wangy2333

请教一下, 整片文章看了一遍之后, 我还是有点不明白. 用户只知晓API:9999.那么API与分布式缓存是一个1对3的关系. 那么当用户查询的时候,首先查询的get是三个缓存中的哪一个呢? 还是说API本身是一个group 本地有, 没有再去三个缓存中找呢? 这个模型实在是没搞清晰 希望指点

API用consistent hasher 去算出应该去哪个port,每个port都被放到了hash环上。

ghost avatar Aug 29 '20 03:08 ghost

第一步就抽象出接口,感觉不是很好理解。根据这篇文章 https://blog.chewxy.com/2018/03/18/golang-interfaces/ ,是否可以考虑延迟定义接口。即首先定义类型/struct,在会使用到接口的时候再定义接口。

Willendless avatar Sep 05 '20 17:09 Willendless

成功被大佬精湛的技术绕晕了

wilgx0 avatar Oct 13 '20 07:10 wilgx0

@Willendless 一般抽象出接口是为了扩展性,很多场景下需要优先抽象接口。比如 RPC 通信需要支持不同的编解码方式,那首先想到的是一个支持编解码的结构体需要支持哪些方法,即接口。GeeRPC第一天 服务端与消息编码 在这篇文章中体现了这种思考方式。

我觉得你说的也是有道理的,如果是比较确定的业务,优先 struct,需要扩展时再抽象接口更符合直觉一些。不过对于实现框架的童鞋来说,可扩展性是第一位的,所以一般都会优先设计接口。比如 go-micro 这个微服务框架,所有的参数都是接口类型的,这个框架是完全可插拔的,允许用户替换任意的类,只要 struct 实现了接口就行。

geektutu avatar Oct 13 '20 15:10 geektutu

@wilgx0 成功被大佬精湛的技术绕晕了

group代表一类资源,一个group结构体代表的是一个节点,存储了一部分缓存数据,具体一个值是到哪个具体节点读取则是由算法决定的(这里是一致性哈希算法,一致性哈希算法可以理解为把地址空间化成一个圈,键值的Key先遇到谁就是谁,具体的看代码实现)。group中注册了一个peers数组,相当于是其他节点的电话簿,是用作在缓存miss的时候向其他节点请求数据用。而这个peer其实就是先前定义的httppool,定义了节点所在的Ip、端口。 以上个人理解,有错误请帮忙指出。

Pissssofshit avatar Nov 12 '20 07:11 Pissssofshit

我觉得难理解的地方是从 getFromPeer开始获取bytes, 过程中调用的(h *httpGetter) Get(group string, key string) ([]byte, error)方法 其中res, err := http.Get(u) 是直接到了ServeHTTP。 这个流程中又走了一遍 (g *Group) load(key string) (value ByteView, err error) 这里有点绕。我是加了好多好多trace才看明白的。 感觉作者有必要整体梳理一下流程 主要是各种Get都Get迷了

limaoxiaoer avatar Nov 19 '20 07:11 limaoxiaoer

@limaoxiaoer 感谢你的建议,这部分相比 groupcache 的原生实现还是简化了很多了。确实是需要一定的基础,不然看上去会比较费力。比如 http.Get(u) 直接到了 ServeHTTP,这一块如果没有用 Go 实现过网站,就挺不好理解的。ServeHTTPnet/http 的接口,实现了这个接口,请求就会被路由到实现函数来。

另外,geecache 既作为存储的实例,提供 http 接口,又可以作为 API 层,供应用程序直接调用,这两个功能的切换也可能会比较绕。

这个系列最大程度还原 groupcache 的实现,而且做了大量的简化,不过博客的解释可能确实不够清晰,见谅。

geektutu avatar Nov 22 '20 14:11 geektutu

是我自己理解错了 因为consistenthash.Map.Add(keys) 这里的keys运行时是用的URL而不是"Tom"之类数据的"key", 所以实际的数据的key还是能被分配到不同主机上的,只是作者这里的例子Tom和Jack运气不好恰好分到一台主机上了。 ———————————————————————————— 有个疑问,一致性哈希这章里使用 idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash }) 这样的寻找方式,不会导致任何查询(不管是不是同一个key)永远都停在8001这第一台主机吗

UnbearableFate avatar Dec 01 '20 01:12 UnbearableFate

我怎么感觉这个代码有点问题,因为节点之间可能会循环请求对方的缓存

wanboyan avatar Dec 10 '20 13:12 wanboyan

@wanboyan 我怎么感觉这个代码有点问题,因为节点之间可能会循环请求对方的缓存

每个节点的hash算法都是一致的,对于一个key,所有节点算出来的远程节点都是同一个。所以应该不存在循环请求的问题

FinaLone avatar Dec 16 '20 14:12 FinaLone