Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于Nacos Distro协议 服务数据sync和verify 没有时间/版本信息的疑问 #4355

Closed
MajorHe1 opened this issue Nov 27, 2020 · 6 comments

Comments

@MajorHe1
Copy link
Contributor

@MajorHe1 MajorHe1 commented Nov 27, 2020

Issue Description

Type: bug report or feature request

Describe what happened (or what feature you want)

我在阅读nacos distro协议实现的源码的时候,产生了一个疑问:

无论是DistroController里的onSyncDatum()方法还是syncChecksum()方法,它们在接收到其他nacos-server节点出传过来的数据的时候,都没有经过经过类似于时间戳/版本等信息的比较,就直接put进自身的内存当中去了。
onSyncDatum()传过来的数据:
image
syncChecksum()传过来的数据:
image
请问这样不会导致旧数据覆盖新数据的问题吗?

假如现在集群下有A、B、C三台nacos-server节点,设想这样两个场景:

场景一:
· 客户端短时间内向节点A发出了两个registerInstance请求,两个请求携带的元数据不一样
· 节点A收到第一个请求,通过distro向B、C广播数据包v1
· 节点A收到第二个请求,通过distro向B、C广播数据包v2
· 由于网络抖动或其他不可知因素,B或者C先收到第二个请求包 v2,再收到第一个请求包v1
· 会不会出现旧数据v1覆盖新数据v2的问题?而且此时节点B或者C的数据跟A不一致了

场景二:
· 接上面的场景一,假如这个时候节点的DistroVerifyTask开始工作,节点B或者C会把旧数据v1的checksum信息发送给A
· 如果节点A不加判断、比较,会不会出现A去B或者C中查询到旧数据v1,然后把自身的新数据v2覆盖掉的情况?

Describe what you expected to happen

总而言之,就是我发现distro协议无论是在广播通知的时候还是在verify对账的时候,收到数据包的nacos-server节点无法判断收到的数据和自身内存中的数据何者是“最近更新的正确的数据”,从而有可能发生旧数据覆盖新数据的情况。

请问是设计如此?还是我的理解有问题,阅读代码有疏漏的地方?

谢谢。

@KomachiSion
Copy link
Collaborator

@KomachiSion KomachiSion commented Nov 27, 2020

看一下DistroMapper和DistroFilter, 同一个tag(service)的请求只会在一个node上执行,所以不会出现您说的场景。

Loading

@KomachiSion
Copy link
Collaborator

@KomachiSion KomachiSion commented Nov 30, 2020

我再解释一下, 假设真的出现场景1

· 客户端短时间内向节点A发出了两个registerInstance请求,两个请求携带的元数据不一样
· 节点A收到第一个请求,通过distro向B、C广播数据包v1
· 节点A收到第二个请求,通过distro向B、C广播数据包v2
· 由于网络抖动或其他不可知因素,B或者C先收到第二个请求包 v2,再收到第一个请求包v1

那么A节点就是responsible节点,他自身的数据更新是按照v1,v2的接收顺序处理的。
由于v1,v2会在短时间内进行合并,大概率只会触发v2变更。

如果正好卡在v1执行,v2不执行的窗口节点,那么v1执行和v2执行会有一个delay时间,默认应该是1~2s。且根据DistroExecuteTaskExecuteEngine,同一个服务会在同一个线程中串行执行,v1必然在v2前被广播完成。

再假设您说的各种未知问题,导致了v2比v1更早在BC节点生效,那么根据最后DistroVerifyTask和DistroMapper的权威节点判定,BC节点还是会到A节点获取最新的v2数据。保证最终一致性。

Loading

@MajorHe1
Copy link
Contributor Author

@MajorHe1 MajorHe1 commented Dec 1, 2020

我再解释一下, 假设真的出现场景1

· 客户端短时间内向节点A发出了两个registerInstance请求,两个请求携带的元数据不一样
· 节点A收到第一个请求,通过distro向B、C广播数据包v1
· 节点A收到第二个请求,通过distro向B、C广播数据包v2
· 由于网络抖动或其他不可知因素,B或者C先收到第二个请求包 v2,再收到第一个请求包v1

那么A节点就是responsible节点,他自身的数据更新是按照v1,v2的接收顺序处理的。
由于v1,v2会在短时间内进行合并,大概率只会触发v2变更。

如果正好卡在v1执行,v2不执行的窗口节点,那么v1执行和v2执行会有一个delay时间,默认应该是1~2s。且根据DistroExecuteTaskExecuteEngine,同一个服务会在同一个线程中串行执行,v1必然在v2前被广播完成。

再假设您说的各种未知问题,导致了v2比v1更早在BC节点生效,那么根据最后DistroVerifyTask和DistroMapper的权威节点判定,BC节点还是会到A节点获取最新的v2数据。保证最终一致性。

非常感谢您的回复,根据您的建议去看了DistroMapper和DistroFilter,解决了很多疑问。
我这边还有两个问题想要请教一下您:

问题一:
在DistroMapper里面计算权威节点的时候,是简单对服务名计算Hash,然后对nacos-server节点数目取余。
这在nacos-server节点进行扩容、缩容、隔离、断网演练等场景下不会出问题吗?
举个例子:
· 服务service的权威节点是A,service上报了一个新数据包 Vnew 给到节点A
· 此时发生了nacos-server节点数目的变化(扩缩容等等),重新计算service的权威节点是B
· 节点B中关于service的状态是 Vold,这个时候节点A通过distro广播把 Vnew给到节点B,B节点会拒绝更新
· 在service重新上报心跳信息给到节点B之前,节点B有可能通过verify机制把集群中的service状态全部改成 Vold
在这种场景下,会有几秒钟的数据状态错误对不对?nacos是容忍这种错误的吗?

问题二:
在DistroSyncChangeTask执行的时候,将本节点responsible的服务信息同步给其他节点。
如果同步失败,会通过handleFailedTask()将任务重新添加到队列里面。
我经过测试,发现如果下线或者隔离一台nacos-server节点,会导致同步信息至下线节点的task一直失败,一直重试。
似乎nacos并没有失败多少次则清除该任务的机制。
这岂不是意味着如果不重启,nacos-server的内存中一直有无意义的task永远处于失败-重试的循环当中?

有劳解惑,谢谢

Loading

@KomachiSion
Copy link
Collaborator

@KomachiSion KomachiSion commented Dec 2, 2020

在DistroMapper里面计算权威节点的时候,是简单对服务名计算Hash,然后对nacos-server节点数目取余。
这在nacos-server节点进行扩容、缩容、隔离、断网演练等场景下不会出问题吗?
举个例子:
· 服务service的权威节点是A,service上报了一个新数据包 Vnew 给到节点A
· 此时发生了nacos-server节点数目的变化(扩缩容等等),重新计算service的权威节点是B
· 节点B中关于service的状态是 Vold,这个时候节点A通过distro广播把 Vnew给到节点B,B节点会拒绝更新
· 在service重新上报心跳信息给到节点B之前,节点B有可能通过verify机制把集群中的service状态全部改成 Vold
在这种场景下,会有几秒钟的数据状态错误对不对?nacos是容忍这种错误的吗?

会的,这种问题的确在极端场景下存在,默认心跳上报的间隔是5s,变为不健康默认15s,摘除30s, 在这个期间有3次机会续约成功,而发生问题一场景的转发通常为1次。极端场景是节点不停起停,且时间又每次都能卡在心跳发送间隔。但是这种场景太难遇到了。

问题二:
在DistroSyncChangeTask执行的时候,将本节点responsible的服务信息同步给其他节点。
如果同步失败,会通过handleFailedTask()将任务重新添加到队列里面。
我经过测试,发现如果下线或者隔离一台nacos-server节点,会导致同步信息至下线节点的task一直失败,一直重试。
似乎nacos并没有失败多少次则清除该任务的机制。
这岂不是意味着如果不重启,nacos-server的内存中一直有无意义的task永远处于失败-重试的循环当中?

首先失败的同步任务是不能被移除的,因为要确保最终数据一致,所以同步必须是成功的,失败后会在delay池中不停的合并,执行时获取最新的数据。
关于您说的如果完全摘除了某一个节点,任务会一直在重试,的确可能是重构时的问题,但是解决方法应该是通过判定目标端是否还在member中,只要member列表不变,就不应该停止重试。

Loading

@MajorHe1
Copy link
Contributor Author

@MajorHe1 MajorHe1 commented Dec 3, 2020

首先失败的同步任务是不能被移除的,因为要确保最终数据一致,所以同步必须是成功的,失败后会在delay池中不停的合并,执行时获取最新的数据。
关于您说的如果完全摘除了某一个节点,任务会一直在重试,的确可能是重构时的问题,但是解决方法应该是通过判定目标端是否还在member中,只要member列表不变,就不应该停止重试。

按照您的说法,我再去看了一下源码,并做了测试,发现重试的任务并没有判断目标端是否还在member中。
也就是说,如果发生了缩容的场景,集群的cluster.conf文件改变了,目标端已经不在member中了,但是nacos的内存当中还是会有已经无意义的task一直在失败-重试。
请问后续的nacos版本是否会考虑修复这个问题?
以及后续的nacos版本是否会考虑引入一致性Hash算法来替代普通Hash,解决扩缩容场景下,服务重新计算权威节点导致大范围抖动的问题?

Loading

@KomachiSion
Copy link
Collaborator

@KomachiSion KomachiSion commented Dec 4, 2020

请问后续的nacos版本是否会考虑修复这个问题?
以及后续的nacos版本是否会考虑引入一致性Hash算法来替代普通Hash,解决扩缩容场景下,服务重新计算权威节点导致大范围抖动的问题?

请问后续的nacos版本是否会考虑修复这个问题?

会的,这个问题肯定会修复

以及后续的nacos版本是否会考虑引入一致性Hash算法来替代普通Hash,解决扩缩容场景下,服务重新计算权威节点导致大范围抖动的问题?

可以考虑做这个优化,但是短期内没有这个考虑。

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants