[apache/dubbo]ZookeeperRegistryNotifier 无法实现多次通知合并的功能?

2024-05-24 877 views
4

不太理解 ZookeeperRegistryNotifier 的 notify() 方法里面,为什么要进行 sleep ?

image

我一开始以为是这里说的【延迟通知】https://cn.dubbo.apache.org/zh/blog/2022/06/23/%e6%b5%85%e6%9e%90-dubbo-3.0-%e4%b8%ad%e6%8e%a5%e5%8f%a3%e7%ba%a7%e5%9c%b0%e5%9d%80%e6%8e%a8%e9%80%81%e6%80%a7%e8%83%bd%e7%9a%84%e4%bc%98%e5%8c%96/#%E5%BB%B6%E8%BF%9F%E9%80%9A%E7%9F%A5

但是阅读代码之后,发现应该起不到这里说的【延迟通知】的效果。

看了 org.apache.dubbo.registry.RegistryNotifier#notify 的实现,org.apache.dubbo.registry.zookeeper.ZookeeperRegistry.ZookeeperRegistryNotifier#notify 的实现的确有问题,ZookeeperRegistryNotifier实现不了【阻塞期间,多次通知的合并】

回答

6

看了下代码,目前 zk 接口级的实现是有这个问题的,是需要基于 org.apache.dubbo.registry.RegistryNotifier 去进行实现,不应该阻塞 zk 的通知线程

4

看了下代码,感觉通过阻塞curator的监听线程 应该是可以满足延迟通知 聚合事件的功能。 curator启动的监听线程sleep的时候,应该不会再接收到监听节点的变更事件了,待到sleep结束后 会重新从zk-server拉取监听节点的数据 并触发一次变更事件,然后再执行一次Notify。 image image image

1

@Bo-Qiu 咱们可以做个简单的测试。

先在 org.apache.dubbo.registry.zookeeper.ZookeeperRegistry.ZookeeperRegistryNotifier#notify() 方法里面,添加下面这行日志,在每次 doNotify() 之前进行一次输出:

image

然后,把 dubbo-demo-xml-consumer 和 dubbo-demo-xml-provider 调整成【接口级】注册和订阅,启动。

在 Zookeeper 里面,/dubbo/org.apache.dubbo.demo.DemoService/providers 节点下只有一个节点,如下:

image

最后,我们执行快速执行下面两个命令,这两个命令,只是把上面 dubbo-demo-xml-provider 注册的 URL 里面的 IP,从 192.168.2.146 改成192.168.2.147和192.168.2.148节点,模拟两个 Provider 节点同时上线的情况: create /dubbo/org.apache.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F192.168.2.147%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26background%3Dfalse%26delay%3D5000%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26ipv6%3D2408%3A8207%3A7889%3Ac32%3Ada3d%3Accff%3A0%3Ab9d%26methods%3DsayHello%2CsayHelloAsync%26pid%3D29364%26service-name-mapping%3Dtrue%26side%3Dprovider%26timeout%3D3000%26timestamp%3D1673276136517

create /dubbo/org.apache.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F192.168.2.148%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26background%3Dfalse%26delay%3D5000%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26ipv6%3D2408%3A8207%3A7889%3Ac32%3Ada3d%3Accff%3A0%3Ab9d%26methods%3DsayHello%2CsayHelloAsync%26pid%3D29364%26service-name-mapping%3Dtrue%26side%3Dprovider%26timeout%3D3000%26timestamp%3D1673276136517

在 Consumer 的控制台,得到如下输出:

image

很明显,只是两次通知时间间隔了 5 秒(也就是下图里面创建 ZookeeperRegistryNotifier 的 delayTime 默认值),并没有将两个通知合并。

image
3

@Bo-Qiu 咱们可以做个简单的测试。

先在 org.apache.dubbo.registry.zookeeper.ZookeeperRegistry.ZookeeperRegistryNotifier#notify() 方法里面,添加下面这行日志,在每次 doNotify() 之前进行一次输出:

image

然后,把 dubbo-demo-xml-consumer 和 dubbo-demo-xml-provider 调整成【接口级】注册和订阅,启动。

在 Zookeeper 里面,/dubbo/org.apache.dubbo.demo.DemoService/providers 节点下只有一个节点,如下:

image

最后,我们执行快速执行下面两个命令,这两个命令,只是把上面 dubbo-demo-xml-provider 注册的 URL 里面的 IP,从 192.168.2.146 改成192.168.2.147和192.168.2.148节点,模拟两个 Provider 节点同时上线的情况: create /dubbo/org.apache.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F192.168.2.147%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26background%3Dfalse%26delay%3D5000%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26ipv6%3D2408%3A8207%3A7889%3Ac32%3Ada3d%3Accff%3A0%3Ab9d%26methods%3DsayHello%2CsayHelloAsync%26pid%3D29364%26service-name-mapping%3Dtrue%26side%3Dprovider%26timeout%3D3000%26timestamp%3D1673276136517

create /dubbo/org.apache.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F192.168.2.148%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26background%3Dfalse%26delay%3D5000%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26ipv6%3D2408%3A8207%3A7889%3Ac32%3Ada3d%3Accff%3A0%3Ab9d%26methods%3DsayHello%2CsayHelloAsync%26pid%3D29364%26service-name-mapping%3Dtrue%26side%3Dprovider%26timeout%3D3000%26timestamp%3D1673276136517

在 Consumer 的控制台,得到如下输出:

image

很明显,只是两次通知时间间隔了 5 秒(也就是下图里面创建 ZookeeperRegistryNotifier 的 delayTime 默认值),并没有将两个通知合并。

image

嗯嗯,是的,这个理解错了,确实是有问题的。 我想到一种解决方法,大概是这样的: 可以通过延迟订阅来实现。 不再使用curator的cache监听节点,改为直接使用zk client的Watch,当收到事件后,订阅将失效,在delay的时间结束后再去订阅这个节点。 delay时间内的所有变更事件 将不再会从zk-server推送到,等到delay结束后再重新去订阅这个节点,并拉取数据 同客户端本地的做对比,若不一致,再推送一次。

2

通过延迟订阅的话,可以规避掉delay窗口内,zk-server推送到客户端的大量事件。 相对来说比直接在org.apache.dubbo.registry.RegistryNotifier实现合并 要合理一些。

7

老哥,你把这个assign给我哇,我近期有时间了修复一下

0

@Bo-Qiu 我看现在已经是用 Watcher 了,没有用 Cache 了吧。我看org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient.CuratorWatcherImpl 的 486 行,用的是client.getChildren().usingWatcher(CuratorWatcherImpl.this).forPath(path) 的方式。

看你这边的方案,是把这个注册Watcher 的逻辑放到 5 秒(默认值)延迟之后。

另外,想讨论延迟通知这个特性本身,会不会造成一些问题。例如,有 1-10、11-20、21-30 三组 Provider 实例,分批进行更新,进行一次上线更新之后,变成 31-40、41-50、51-60 三个 Provider 实例,延迟订阅的方式 会不会 出现部分 Provider 连接失败的情况,导致线上用户的请求失败。毕竟,延迟订阅的话,相当于和 Zookeeper 断开了 5 秒的时长。

应用级的 org.apache.dubbo.registry.RegistryNotifier 实现,我还没看,不知道会不会也有这个问题。

1

另外,想讨论延迟通知这个特性本身,会不会造成一些问题。例如,有 1-10、11-20、21-30 三组 Provider 实例,分批进行更新,进行一次上线更新之后,变成 31-40、41-50、51-60 三个 Provider 实例,延迟订阅的方式 会不会 出现部分 Provider 连接失败的情况,导致线上用户的请求失败。毕竟,延迟订阅的话,相当于和 Zookeeper 断开了 5 秒的时长。

是的,确实有这个问题,延迟通知这个功能是给超大规模集群设计的,避免推送风暴导致的增量内存突增。之前延迟推送也设计了前 10 次推送不等待的逻辑,我在想是不是我们需要加上一个地址数量的判断逻辑,如果数量少于某个值就直接推送。(此外对于并发推送的情况,我们需要加上对应的版本号,以此来防止覆盖)