dubbo 注册模块

概述

本篇,着重于描述 dubbo-registry 模块。

多注册中心机制

dubbo 允许配置多个注册中心,服务引用、服务注册时,则会向多个注册中心拉取 provider 信息或者注册自身信息。

需要注意的是,如果填写了多个注册中心的地址,那么在服务有变更时,会收到多个注册中心的推送(nacos 是这么处理的)

代码入口:PushReceiver#run() nacos 会针对每个注册地址,创建一个单独的 PushReceiver。

以下为配置 demo


dubbo:

registries:

r1:

address: nacos://127.0.0.1:8848

username: nacos

password: nacos

r2:

address: nacos://127.0.0.1:8846

username: nacos

password: nacos

r3:

address: nacos://127.0.0.1:8847

username: nacos

password: nacos

backup

当注册中心是集群,或多注册中心时,不建议配置多个注册中心,建议将集群地址作为 backup 注册,这样就能避免当 provider 变更时,收到多个注册中心的回调。

配置如下: dubbo.registry.address=nacos://localhost:8848?backup=localshot:8846,localshot:8847

变更推送

当 provider 有变更时, 注册中心会推送 全量的 provider 至客户端,而非变更的 provider.

采用全量,而非增量,在笔者看来的原因是:当只推送增量时,有可能会推送失败,客户端会丢失此次增量变更,client 的信息会与 server 不一致。如果每次都推送全量,则保证了 client 的信息与 server 的信息一致。

nacos 代码入口:
PushReceiver#run()

image.png

zookeeper 代码入口:
ZookeeperRegistry#doSubscribe(URL, NotifyListener)

image.png

nacos

nacos 使用 udp 的方式将变更实时推送至客户端。

client 接收 server 消息推送代码入口: PushReceiver#run()

实际业务处理代码入口: RegistryDirectory#notify(List<URL>)

重试机制

代码入口: FailbackRegistry(URL)

在 client 注册、订阅失败后,client 默认至多重试 2 次,每次重试间隔 5s。

client 缓存机制

通用性 failback 缓存

默认情况下,client 侧在连接注册中心前,会优先从本地加载所需要的 provider 缓存。

该行为,可通过参数 file.cache 来控制。

该缓存作用: 当执行订阅失败后,会使用缓存的 provider,来做订阅操作。

代码入口: FailbackRegistry#subscribe(URL, NotifyListener)

image.png

file.cache

代码入口:AbstractRegistry(URL)

默认从 ${user.home}/.dubbo/dubbo-registry-${applicationName}-${注册中心IP-注册中心端口}.cache 加载缓存。

image.png

文件是以 application 为单位。文件中,存储的是每个 application 所需要的 provider 的缓存信息。

例如:

image.png

file.cache 写入

代码入口: AbstractRegistry#saveProperties(URL)

触发文件写入的几个时机

  1. consumer 在执行 refer 时,会从注册中心获取 provider 信息,之后则将信息写入文件
  2. provider 有变更时, 会重新将所有 provider 信息写入文件

需要注意的是:该缓存文件默认为异步写入。

nacos 中的缓存机制

需要注意:zookeeper 中,没有使用额外的缓存机制。

nacos 在启动后,会从本地路径 ${user.home}/nacos/naming/public 加载所有 provider 的缓存信息。

代码入口: DiskCache#read(String)

consumer 在做 refer 时,则会优先使用这部分缓存信息。因此,如果本地和 nacos-server 信息不一致,则启动很容易失败

nacos 的缓存文件是以 provider 为单位的。每个文件存储了,该 provider 所有的信息。

{
    "hosts":[
        {
            "ip":"192.168.31.100",
            "port":12435,
            "valid":true,
            "healthy":true,
            "marked":false,
            "instanceId":"192.168.31.100#12435#DEFAULT#DEFAULT_GROUP@@providers:org.csp.learn.dubbo.nacos.provider.api.service.HelloService::",
            "metadata":{
                "side":"provider",
                "methods":"hello",
                "release":"2.7.8",
                "deprecated":"false",
                "dubbo":"2.0.2",
                "weight":"122",
                "pid":"99343",
                "interface":"org.csp.learn.dubbo.nacos.provider.api.service.HelloService",
                "actives":"100",
                "generic":"false",
                "timeout":"4000",
                "path":"org.csp.learn.dubbo.nacos.provider.api.service.HelloService",
                "protocol":"dubbo",
                "delay":"5000",
                "metadata-type":"remote",
                "application":"provider-service",
                "dynamic":"true",
                "category":"providers",
                "anyhost":"true",
                "timestamp":"1687396394033"
            },
            "enabled":true,
            "weight":1,
            "clusterName":"DEFAULT",
            "serviceName":"DEFAULT_GROUP@@providers:org.csp.learn.dubbo.nacos.provider.api.service.HelloService::",
            "ephemeral":true
        },
        {
            "ip":"192.168.31.100",
            "port":12436,
            "valid":true,
            "healthy":true,
            "marked":false,
            "instanceId":"192.168.31.100#12436#DEFAULT#DEFAULT_GROUP@@providers:org.csp.learn.dubbo.nacos.provider.api.service.HelloService::",
            "metadata":{
                "side":"provider",
                "methods":"hello",
                "release":"2.7.8",
                "deprecated":"false",
                "dubbo":"2.0.2",
                "weight":"122",
                "pid":"3328",
                "interface":"org.csp.learn.dubbo.nacos.provider.api.service.HelloService",
                "actives":"100",
                "generic":"false",
                "timeout":"4000",
                "path":"org.csp.learn.dubbo.nacos.provider.api.service.HelloService",
                "protocol":"dubbo",
                "delay":"5000",
                "metadata-type":"remote",
                "application":"provider-service",
                "dynamic":"true",
                "category":"providers",
                "anyhost":"true",
                "timestamp":"1687398868099"
            },
            "enabled":true,
            "weight":1,
            "clusterName":"DEFAULT",
            "serviceName":"DEFAULT_GROUP@@providers:org.csp.learn.dubbo.nacos.provider.api.service.HelloService::",
            "ephemeral":true
        }
    ],
    "dom":"DEFAULT_GROUP@@providers:org.csp.learn.dubbo.nacos.provider.api.service.HelloService::",
    "name":"DEFAULT_GROUP@@providers:org.csp.learn.dubbo.nacos.provider.api.service.HelloService::",
    "cacheMillis":10000,
    "lastRefTime":1687398869399,
    "checksum":"8ec1bc3e45da8f0c4c0a128aea4bec84",
    "useSpecifiedURL":false,
    "clusters":"",
    "env":"",
    "metadata":{

    }
}

nacos 缓存写入

代码入口: DiskCache#write(ServiceInfo, String)
每次写入全量的 provider 信息至缓存。
触发写入时机: provider 有变更

nacos 缓存机制带来的弊端

要聊弊端,需要来看下 consumer 在 refer 时的流程。

  1. 应用启动时,默认读取的是本地缓存信息。
  2. consumer 引用 provider 时,会向注册中心拉取 provider 的信息
  3. 如果缓存中有该 provider 信息,则不会向注册中心拉取信息
  4. 接下来,consumer 将会检查 provider 是否有效。
  5. 如果 provider 无法连通,则应用程序启动失败

在笔者公司的开发环境中,存在过因为缓存机制而导致的服务启动失败。

我们的测试环境部署在 k8s 上。有时候我们会本地启动 debug。因为测试环境部署在 k8s 上,所以 IP 经常变动。当服务 ip 变更后,本地存储的信息还是旧的,启动就会失败。

因此,建议在测试环境下,关闭 nacos 的缓存机制。

代码设计

先看下 dubbo-registry 模块的代码设计类图。以下类图,基本包含了 dubbo-registry 包含的几个核心功能。

image.png

  1. RegistryFactory 提供创建注册中心 server
  2. RegistryService 提供订阅、注册功能
  3. FailbackRegistry 提供重试功能
  4. HashedWheelTimer 提供时间轮调度算法
  5. TimerTask 实现任务重新逻辑
  6. RegistryDirectory 提供注册变更回调处理

从类图上,我们不难看出。框架作者在写代码时,遵循了以下规则。

  1. 抽象逻辑,定义接口具备功能
  2. 抽象类实现接口,定义通用的逻辑,并暴露抽象的保护方法,由子类自定义实现
  3. 子类继承抽象类,实现更具体的逻辑。

在日常开发中,如果我们的业务具有多个不同实现,我们也可以参考以上代码结构。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYCs6Iwi' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片