geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
168 stars 21 forks source link

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

Open geektutu opened 4 years ago

geektutu commented 4 years ago

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

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

cuglaiyp commented 2 years ago

@cuglaiyp

@fy403

// Overall flow char                                           requsets                           local
// gee := createGroup() --------> /api Service : 9999 ------------------------------> gee.Get(key) ------> g.mainCache.Get(key)
//                        |                                           ^                   |
//                        |                                           |                   |remote
//                        v                                           |                   v
//                cache Service : 800x                                |           g.peers.PickPeer(key)
//                        |create hash ring & init peerGetter         |                   |
//                        |registry peers write in g.peer             |                   |p.httpGetters[p.hashRing(key)]
//                        v                                           |                   |
//            httpPool.Set(otherAddrs...)                             |                   v
//        g.peers = gee.RegisterPeers(httpPool)                       |           g.getFromPeer(peerGetter, key)
//                        |                                           |                   |
//                        |                                           |                   |
//                        v                                           |                   v
//        http.ListenAndServe("localhost:800x", httpPool)<------------+--------------peerGetter.Get(key)
//                        |                                           |
//                        |requsets                                   |
//                        v                                           |
//                    p.ServeHttp(w, r)                               |
//                        |                                           |
//                        |url.parse()                                |
//                        |--------------------------------------------

请问这个是用啥工具画的呀

同问呀,有知道的小伙伴么?

我整明白了,https://asciiflow.com/

imtzer commented 2 years ago

@zyyingbb 有一个问题不懂,感觉apiServer提供的服务更像是负载均衡,为什么要先检查是否被缓存?先找到key对应的节点,再从那个节点从找到需要的值可以吗?

这里的apiServer和一个缓存服务绑定在一起了,所以先检查这个绑定的缓存服务器,如果key不是属于这个缓存服务器再从其他peer获取,因为这里只是向外暴露了一个api,所以兔老大实现的版本无法先找到key再直接从那个节点找需要的值

wanhhe commented 2 years ago

萌新想请教一下,如果8002端口给group命名为class,其他两个端口命名为scores,我如果在8003端口请求8002端口的数据,在getFromPeer方法中的peer.Get(g.name, key)将传入的group名就是scores,但是应该传入的group名应该是class吧?本来应该请求的url是/class/key,现在变成了/scores/key,是不是有点问题呢还是我的理解有点错误。。

018429 commented 2 years ago

day5-multi-nodes/geecache/geecache.go - github中load方法

if value, err = g.getFromPeer(peer, key); err == nil {
                return value, nil
            }

是否是

value, err := g.getFromPeer(peer, key); err == nil
liujing-siyang commented 2 years ago

@walkmiao

@geektutu

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

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

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

应该是将数据缓存到g.mainCache.add(key, value),lru缓存就相当于查询排行榜,这样经常被查询的数据就不用去节点取了

liujing-siyang commented 2 years ago

@walkmiao

@geektutu

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

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

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

意思是请求分布式节点后得到的数据后放到Group下的maincache中?

liujing-siyang commented 2 years ago

萌新想请教一下,如果8002端口给group命名为class,其他两个端口命名为scores,我如果在8003端口请求8002端口的数据,在getFromPeer方法中的peer.Get(g.name, key)将传入的group名就是scores,但是应该传入的group名应该是class吧?本来应该请求的url是/class/key,现在变成了/scores/key,是不是有点问题呢还是我的理解有点错误。。

感觉是有问题,startAPIServer的gee.Get(key)中group就已经确定了,一路下来getFromPeer中peer.Get(g.name, key),g.name没有改变过,只有改变group才会改变。和PickPeer方法中获取节点没有关系。getFromPeer发起Get请求时,g.name不变,那么对应的ServeHTTP中GetGroup获取的Group就不变,也就无法到得到8002的缓存。是不是设计的时候就只能在在一个缓存命名空间,否则就要中途通过GetGroup换Goup,单单只有ServeHTTP中GetGroup好像不起作用。不知道理解的对不对?

liujing-siyang commented 2 years ago

peersUrl != p.self为什么不可以是自己

DurantVivado commented 2 years ago

@liujing-siyang peersUrl != p.self为什么不可以是自己

说了是找另一个服务器,如果是自己的话就是GetLocally()

ndzuki commented 2 years ago

cache/http.go:127:5: cannot use (HTTPPool)(nil) (type HTTPPool) as type PeerPicker in assignment: *HTTPPool does not implement PeerPicker (missing PeerPicker method) 抄的代码,不能实现PeerPicker接口,没想懂

yinhuanyi commented 2 years ago

博主,Group的getFromPeer方法返回的ByteView必须是指针,好像写错了

func (g *Group) getFromPeer(peer PeerGetter, key string) (*ByteView, error) {
    bytes, err := peer.Get(g.name, key)
    if err != nil {
        return &ByteView{}, err
    }
    return &ByteView{b: bytes}, nil
}
Kaimar666 commented 2 years ago

博主您好,我看完整个项目后在这里有一点疑问,望解答,谢谢。 在实际的分布式部署中,每个节点部署一个分布式缓存,存在节点上下线和缓存内容替换的问题, (1)那么节点之间是不是要同步它们之间所有的key和相互知道其它所有的节点呢?我想的是如果它们不同步这两个信息,那么在不同的分布式节点上它们的一致性哈希内容不同,就不知道去哪个节点找key。(2)如果上述同步成立,在实际中它们是采用什么同步策略或工具呢?望指点,谢谢。

whybeyoung commented 2 years ago

博主您好,我看完整个项目后在这里有一点疑问,望解答,谢谢。 在实际的分布式部署中,每个节点部署一个分布式缓存,存在节点上下线和缓存内容替换的问题, (1)那么节点之间是不是要同步它们之间所有的key和相互知道其它所有的节点呢?我想的是如果它们不同步这两个信息,那么在不同的分布式节点上它们的一致性哈希内容不同,就不知道去哪个节点找key。(2)如果上述同步成立,在实际中它们是采用什么同步策略或工具呢?望指点,谢谢。

1、只淘汰,不更新缓存值 2、https://segmentfault.com/a/1190000021199728看下图解的一致性hash做了什么

Kaimar666 commented 2 years ago

这是一封自动回复邮件。已经收到您的来信。

iLiuqi commented 2 years ago

个人觉得,测试多节点缓存逻辑效果时,main 中 fronted api server 和 cache server 的启动应该独立分开,原用例中,启动的第三个服务用2个端口既承担了 api 又 承担了 cache,容易混乱,分开的话更便于理解。

LLoyou00 commented 2 years ago

@iLiuqi 个人觉得,测试多节点缓存逻辑效果时,main 中 fronted api server 和 cache server 的启动应该独立分开,原用例中,启动的第三个服务用2个端口既承担了 api 又 承担了 cache,容易混乱,分开的话更便于理解。

同意。。。

Kaimar666 commented 2 years ago

这是一封自动回复邮件。已经收到您的来信。

BetterZhuang commented 1 year ago

是不是初始化的时候只有maincache中有数据,分布式节点上并没有?

mindingyao commented 1 year ago

@BetterZhuang 是不是初始化的时候只有maincache中有数据,分布式节点上并没有?

maincache中也没有吧。测试用例里面启动了三个分布式节点服务,其中一个既作为API又作为分布式节点(这个太晕了,看了好几遍)。每个节点都对应一个Group,一个Group又包含一个本地缓存maincache。初始所有节点的本地缓存maincache里面是没有数据的,每次从远程节点得到key后(可以是数据库或者其他数据源,作者是用本地map模拟的数据源),得到的key会被缓存在对应远程节点的maincache里面,下次有相同的请求key就可以直接获取。重点是搞清楚每次启动一个节点,就会新建一个对应的group,里面包含一系列东西,包括节点的本地缓存。理解有误不对欢迎指出。

zhucebuliaole commented 1 year ago

@mindingyao

@BetterZhuang 是不是初始化的时候只有maincache中有数据,分布式节点上并没有?

maincache中也没有吧。测试用例里面启动了三个分布式节点服务,其中一个既作为API又作为分布式节点(这个太晕了,看了好几遍)。每个节点都对应一个Group,一个Group又包含一个本地缓存maincache。初始所有节点的本地缓存maincache里面是没有数据的,每次从远程节点得到key后(可以是数据库或者其他数据源,作者是用本地map模拟的数据源),得到的key会被缓存在对应远程节点的maincache里面,下次有相同的请求key就可以直接获取。重点是搞清楚每次启动一个节点,就会新建一个对应的group,里面包含一系列东西,包括节点的本地缓存。理解有误不对欢迎指出。

我认为从day2的解释来看,并不是一个group对应一个节点,而是一个Group对应一类资源。从day5的测试用例中也可以看到,所有的节点共享了一个gee(也就是group),但是我认为很奇怪的是,如果group与http解耦合了的话,意味着每一类资源最终还是仅有一个group,不同的节点仅仅是用于负载均衡,也就是不同的key通过不同的节点访问同一个group文件。 我认为你说的“重点是搞清楚每次启动一个节点,就会新建一个对应的group”似乎不太对啊。每次启动的节点仅仅会启动一个新的http服务。您看看我说的是否有问题?

mindingyao commented 1 year ago

@zhucebuliaole

@mindingyao

@BetterZhuang 是不是初始化的时候只有maincache中有数据,分布式节点上并没有?

maincache中也没有吧。测试用例里面启动了三个分布式节点服务,其中一个既作为API又作为分布式节点(这个太晕了,看了好几遍)。每个节点都对应一个Group,一个Group又包含一个本地缓存maincache。初始所有节点的本地缓存maincache里面是没有数据的,每次从远程节点得到key后(可以是数据库或者其他数据源,作者是用本地map模拟的数据源),得到的key会被缓存在对应远程节点的maincache里面,下次有相同的请求key就可以直接获取。重点是搞清楚每次启动一个节点,就会新建一个对应的group,里面包含一系列东西,包括节点的本地缓存。理解有误不对欢迎指出。

我认为从day2的解释来看,并不是一个group对应一个节点,而是一个Group对应一类资源。从day5的测试用例中也可以看到,所有的节点共享了一个gee(也就是group),但是我认为很奇怪的是,如果group与http解耦合了的话,意味着每一类资源最终还是仅有一个group,不同的节点仅仅是用于负载均衡,也就是不同的key通过不同的节点访问同一个group文件。 我认为你说的“重点是搞清楚每次启动一个节点,就会新建一个对应的group”似乎不太对啊。每次启动的节点仅仅会启动一个新的http服务。您看看我说的是否有问题?

“我认为从day2的解释来看,并不是一个group对应一个节点,而是一个Group对应一类资源” 你这个说法准确一点,我只是简单陈述一下我理解的重点,即启动一个节点创建一个Group。 “从day5的测试用例中也可以看到,所有的节点共享了一个gee(也就是group)” day5测试用例里面不是执行了三次共启动了三个节点吗?每次都会调用createGroup函数新建一类Group资源,里面包含一个本地缓存cache。 “不同的节点仅仅是用于负载均衡,也就是不同的key通过不同的节点访问同一个group文件” 你觉得奇怪的地方确实是不合理的,共享一类资源没有意义是吧。分布式缓存的设计初衷就是根据不同key将请求分散到不同节点处理。 “每次启动的节点仅仅会启动一个新的http服务” 你再看仔细看一下启动的shell脚本呢?

zhucebuliaole commented 1 year ago

@mindingyao

@zhucebuliaole

@mindingyao

@BetterZhuang 是不是初始化的时候只有maincache中有数据,分布式节点上并没有?

maincache中也没有吧。测试用例里面启动了三个分布式节点服务,其中一个既作为API又作为分布式节点(这个太晕了,看了好几遍)。每个节点都对应一个Group,一个Group又包含一个本地缓存maincache。初始所有节点的本地缓存maincache里面是没有数据的,每次从远程节点得到key后(可以是数据库或者其他数据源,作者是用本地map模拟的数据源),得到的key会被缓存在对应远程节点的maincache里面,下次有相同的请求key就可以直接获取。重点是搞清楚每次启动一个节点,就会新建一个对应的group,里面包含一系列东西,包括节点的本地缓存。理解有误不对欢迎指出。

我认为从day2的解释来看,并不是一个group对应一个节点,而是一个Group对应一类资源。从day5的测试用例中也可以看到,所有的节点共享了一个gee(也就是group),但是我认为很奇怪的是,如果group与http解耦合了的话,意味着每一类资源最终还是仅有一个group,不同的节点仅仅是用于负载均衡,也就是不同的key通过不同的节点访问同一个group文件。 我认为你说的“重点是搞清楚每次启动一个节点,就会新建一个对应的group”似乎不太对啊。每次启动的节点仅仅会启动一个新的http服务。您看看我说的是否有问题?

“我认为从day2的解释来看,并不是一个group对应一个节点,而是一个Group对应一类资源” 你这个说法准确一点,我只是简单陈述一下我理解的重点,即启动一个节点创建一个Group。 “从day5的测试用例中也可以看到,所有的节点共享了一个gee(也就是group)” day5测试用例里面不是执行了三次共启动了三个节点吗?每次都会调用createGroup函数新建一类Group资源,里面包含一个本地缓存cache。 “不同的节点仅仅是用于负载均衡,也就是不同的key通过不同的节点访问同一个group文件” 你觉得奇怪的地方确实是不合理的,共享一类资源没有意义是吧。分布式缓存的设计初衷就是根据不同key将请求分散到不同节点处理。 “每次启动的节点仅仅会启动一个新的http服务” 你再看仔细看一下启动的shell脚本呢?

是的,我重新看了一下脚本,我之前理解的不太对,应该就是每个分布式节点都建立了同名的group然后在本地建了一个LRU缓存。我能想到的解释大概是group可以解决不同类型资源里key有冲突的情况。 BTW,新年快乐:)

spade69 commented 1 year ago

@zhucebuliaole

@mindingyao

@zhucebuliaole

@mindingyao

@BetterZhuang 是不是初始化的时候只有maincache中有数据,分布式节点上并没有?

maincache中也没有吧。测试用例里面启动了三个分布式节点服务,其中一个既作为API又作为分布式节点(这个太晕了,看了好几遍)。每个节点都对应一个Group,一个Group又包含一个本地缓存maincache。初始所有节点的本地缓存maincache里面是没有数据的,每次从远程节点得到key后(可以是数据库或者其他数据源,作者是用本地map模拟的数据源),得到的key会被缓存在对应远程节点的maincache里面,下次有相同的请求key就可以直接获取。重点是搞清楚每次启动一个节点,就会新建一个对应的group,里面包含一系列东西,包括节点的本地缓存。理解有误不对欢迎指出。

我认为从day2的解释来看,并不是一个group对应一个节点,而是一个Group对应一类资源。从day5的测试用例中也可以看到,所有的节点共享了一个gee(也就是group),但是我认为很奇怪的是,如果group与http解耦合了的话,意味着每一类资源最终还是仅有一个group,不同的节点仅仅是用于负载均衡,也就是不同的key通过不同的节点访问同一个group文件。 我认为你说的“重点是搞清楚每次启动一个节点,就会新建一个对应的group”似乎不太对啊。每次启动的节点仅仅会启动一个新的http服务。您看看我说的是否有问题?

“我认为从day2的解释来看,并不是一个group对应一个节点,而是一个Group对应一类资源” 你这个说法准确一点,我只是简单陈述一下我理解的重点,即启动一个节点创建一个Group。 “从day5的测试用例中也可以看到,所有的节点共享了一个gee(也就是group)” day5测试用例里面不是执行了三次共启动了三个节点吗?每次都会调用createGroup函数新建一类Group资源,里面包含一个本地缓存cache。 “不同的节点仅仅是用于负载均衡,也就是不同的key通过不同的节点访问同一个group文件” 你觉得奇怪的地方确实是不合理的,共享一类资源没有意义是吧。分布式缓存的设计初衷就是根据不同key将请求分散到不同节点处理。 “每次启动的节点仅仅会启动一个新的http服务” 你再看仔细看一下启动的shell脚本呢?

是的,我重新看了一下脚本,我之前理解的不太对,应该就是每个分布式节点都建立了同名的group然后在本地建了一个LRU缓存。我能想到的解释大概是group可以解决不同类型资源里key有冲突的情况。 BTW,新年快乐:)

这里Group应该是一个namespace的概念,类似于某类缓存的资源或数据,我感觉group可以去对标分片的概念,而这里起了若干个cacheserver的分布式节点对应的应该是复制节点的概念。至于最外层的9999应该是充当client的角色

Kaimar666 commented 1 year ago

这是一封自动回复邮件。已经收到您的来信。

zhangxc666 commented 1 year ago

想请问下,如果要进行多个group的数据交互的话,该如何进行?必须要手动请求/_geecache/group/key吗?

zhangxc666 commented 1 year ago

还有一个问题不太明白,为什么8001,8002,8003的group的name都是同一个值,是一个name可以对应于多个maincache吗?那这样的话一个group又具体指什么?

GuYith commented 1 year ago

@zhangxc0921 还有一个问题不太明白,为什么8001,8002,8003的group的name都是同一个值,是一个name可以对应于多个maincache吗?那这样的话一个group又具体指什么?

先解释为什么group的name一样,这里可以再仔细读一下main.go中的代码,调用createGroup()时创建了一个group,代码中指定了所有group的name为"scores",所以都一样,我觉得可能和其他人理解的一样,一个group对应一类资源

作者在Day2中的原句:

Group 是 GeeCache 最核心的数据结构,负责与用户的交互,并且控制缓存值存储和获取的流程。

另外说一下个人的理解,你可以把group看成一个控制中心,maincache是本地缓存(缓存节点的实体,真正存放缓存的地方),getter是用户访问缓存的入口,peers的实际类型为HTTPPool,可以看成是一个缓存节点池(并没有真正的缓存节点),管理着对远程节点的访问,包装了访问缓存节点的一致性哈希算法。

run.sh脚本一共创建了3个group,并且启动了三个缓存节点服务(8001,8002,8003),启动了一个api服务(9999),其中api服务(9999)和缓存节点服务(8003)是通过同一个group进行管理的,在创建每个group的时候,远程节点的addr信息写入了每个group的HTTPPool中。

用户访问api的过程,首先访问该group中的maincache,若没有命中,再通过HTTPPool中的一致性哈希算法选择远程节点或本地节点,返回缓存值

KKKZOZ commented 11 months ago

@iLiuqi 个人觉得,测试多节点缓存逻辑效果时,main 中 fronted api server 和 cache server 的启动应该独立分开,原用例中,启动的第三个服务用2个端口既承担了 api 又 承担了 cache,容易混乱,分开的话更便于理解。

太对了哥,真的该这样,要不然真有点混乱

Kaimar666 commented 11 months ago

这是一封自动回复邮件。已经收到您的来信。

ZMbiubiubiu commented 2 months ago

我觉得这篇文章读者很绕,还有一部分原因是命名不太规范。比如peers,一会儿指代host、一会儿指代PeerPicker、一会儿指代consistentHash。从名称上没有做出区分,绕来绕去的

Kaimar666 commented 2 months ago

这是一封自动回复邮件。已经收到您的来信。