[apache/dubbo]dubbo应用级别注册元数据建议增加从应用名到接口名的mapping以方便nacos-sync之类的工具同步dubbo元数据

2023-12-08 362 views
2

Describe the feature

目前dubbo3服务注册到注册中心之后,还会发布映射关系到注册中心的元数据中心,但是只发部了“接口名 -》 应用名”的单向映射。 nacos-sync这种同步服务实例到目标集群的工具,不仅要同步实例,而且要同步这种映射关系的元数据。 问题是当dubbo服务注册方式为应用级别实例、而且metadata-type为默认local时,nacos-sync不知道要从注册中心元数据中心拉取哪些元数据,因为nacos-sync只知道要同步的应用服务名,并不能知道元数据里哪些dataId对应这个应用服务名,即使通过http方式遍历获取所有group=mapping的映射记录,也不能确定这些mapping都是有效的,因为实例注销之后这些元数据还会长期保留(比如kill -9干掉了dubbo服务,注册中心会自动注销实例,但对应的元数据还留在了元数据中心)。 这个问题有个简单的解决办法:dubbo注册应用实例成功后再发布map到元数据中心时,多发布一个反向的映射关系,dataId为应用名,group为mapping,内容为本次发布的其他mapping的dataId集合,比如"com.xxx.InterfaceA, com.xxx.InterfaceB,com.xxx.InterfaceC" 这个mapping对dubbo自己没有用,但对注册实例同步工具是巨大的支持,补齐了中间缺少的一环: 获取服务实例列表 -》 获取应用Instance名(应用名) -》根据应用名查询元数据中心获取各个Interface名称 -》从元数据中心拉取各个Interface的元数据 -》同步元数据到目标集群 、 同步服务实例到目标集群

回答

5

metadata-type为remote的元数据可以采用下列方式同步: 从源集群注册中心拉取服务实例 -》 获取每个实例的dubbo.metadata.revision -》根据dataId=应用名、group=获取的revision查询源集群元数据中心获取元数据,同步到目标集群元数据中心(ConfigService) -》解析元数据json串,获取接口服务services的keySet集合,keySet中的每个key都是“接口名:dubbo”的形式,split拆出接口名 -》从源集群元数据中心获取dataId=接口名,group=mapping的内容,同步到目标集群元数据中心(ConfigService) -》 再将服务实例同步到目标集群(NameService)

8

首先考虑这个 sync 模型的合理性: 需求侧是合理的,按照一定的标签去隔离同步的数据。 实现侧可以简单一些,目前其实就两个方案: 1)(建议的)Dubbo SDK 侧注册的时候在 Metadata 添加数据,sync 的时候直接识别。实现方案可以按照 https://github.com/apache/dubbo/issues/13293 提的 ServiceInstanceCustomizer 实现。 2)sync 侧去判断 revision 对应的信息。但这里会存在几个问题:revision 默认 local,sync 侧很难获取正式数据;通过 definition 获取的是非实时数据准确度有问题

1

主要是想把映射关系从源集群同步到目标集群,目前集群缺少接口名到应用名的映射关系,只有同步的应用级别的服务实例,这些实例都无法使用。 当然手工在目标集群创建映射关系也是个办法,只是想探讨一下能不能通过sync自动同步过去,自动调用目标集群的configservice把这些元数据发布到目标集群

8

主要是需要将源集群的接口名到应用名映射关系同步到目标集群,只同步应用级别服务实例,目标集群的dubbo消费端无法确定要访问哪个应用服务实例。 比如源集群的服务实例如下(只有应用级别的): image

注册中中心相关的映射关系元数据(dataId为接口名,内容为应用名): image

nacos-sync同步任务配置的是实例名(比如上面图中的helloTestApp),从源集群的nacos注册中心拉取的实例信息里并没有接口信息,所以并不知道要同步哪些mapping到目标集群。 源集群的mapping数据里可能有废弃的元数据,比如已经不再用的旧接口com.test.old.OldApi 映射到了helloTestApp应用,通过遍历所有mapping逐个比对内容的方式不仅性能差,而且也会将这些废弃旧接口传播到目标集群。

0

主要是需要将源集群的接口名到应用名映射关系同步到目标集群,只同步应用级别服务实例,目标集群的dubbo消费端无法确定要访问哪个应用服务实例。

没太懂这个具体是什么场景

9

nacos-sync负责把A注册中心集群中登记的相关服务实例同步到B注册中心集群(通常是异地中心),使得B中心的相关消费者也能够访问A中心的相关服务。nacos-sync每个同步任务的配置信息包括实例来源注册中心url、同步目标注册中心url以及同步的服务实例名称。 实现过程如下所示,nacos-sync从NacosCLusterA集群pull相关的服务实例信息(拉取A集群的服务列表),然后将得到的服务实例信息push到NacosClusterB。

            +-------------+
     +----> |NacosClusterA|
     |      +-------------+               +-------------+
     |                                    |NacosClusterB|
Pull |                                    +--+----------+
Info |      +------------+                   ^
     |      |ZooKeeper   |                   |
     |      +--+---------+                   | Push Info
     |         ^ Pull Info                   |
     |         |                             |
     |        ++-----------------------------+--+
     <--------+  NacosSync1, NacosSync2,....    |
              +---+-------------------------+---+
                  |                         |
                  |                         |
                  |                         |
                  |       +---------+       |
                  +-----> |NacosSync| <-----+
                          |Database |
                          +---------+

https://github.com/nacos-group/nacos-sync#architecture-topology 对于应用级别注册的dubbo服务实例而言,从A注册中心拉取的服务实例信息中并不包含相关接口名到应用名的映射关系,nacos-sync还需要根据同步任务登记的应用服务名去A元数据中心拉取相关的映射关系,如果dubbo发布接口映射关系元数据到元数据中心时,再发布一条应用名到接口名的映射关系(相当于多发一条应用名下全部接口的目录信息),那么基于应用名就能从元数据中心直接获取该应用下的所有的接口名,然后获取各个接口到应用的映射关系元数据,再把这些元数据同步到B集群。

8

nacos-sync配置的同步任务界面(对于应用级别注册实例而言,配置的同步服务名就是应用名): image

4

nacos-sync本质是代理注册服务,替相关服务向其他注册中心发起注册或解除注册请求

8

还有一个问题是:为什么不能按照应用名直接同步,而需要查询到接口?是配置上的问题需要同步的数据源只有接口格式的还是其他原因?

4

调用nacosNamingService#getAllInstances(serviceName, groupName, new ArrayList<>(), true)从nacos源集群获取指定应用名的实例数据里没有接口到应用的映射关系,只是个半成品,同步到目标集群也不能被目标集群的消费者使用(缺少映射关系,dubbo consumer没办法确定相关rpc接口要去调用哪个应用): image 上面半成品缺少的映射关系元数据在nacos源集群的元数据中心,打开元数据中心,找到上面这个应用名的一个接口名HelloAservice(我知道这个接口名,但nacos-sync它并不知道啊): image 点击详情,查看这个接口配置的应用名就是nacos-sync配置的同步应用实例名称: image nacos-sync只知道应用名的前提下,需要遍历元数据中心记录的每个映射关系元数据,逐个确认要同步哪个,这样做的效率比较差(nacos只有按接口名查询映射关系的grpc接口以及查询全部映射关系的http接口,没有提供按映射内容的查询接口)。

9

另外同样的接口名可能有多个应用,比如: image 这种情况下,从应用名helloTestApp反查接口名的sql也不好写: image

7

调用nacosNamingService#getAllInstances(serviceName, groupName, new ArrayList<>(), true)从nacos源集群获取指定应用名的实例数据里没有接口到应用的映射关系,只是个半成品,同步到目标集群也不能被目标集群的消费者使用(缺少映射关系,dubbo consumer没办法确定相关rpc接口要去调用哪个应用): image 上面半成品缺少的映射关系元数据在nacos源集群的元数据中心,打开元数据中心,找到上面这个应用名的一个接口名HelloAservice(我知道这个接口名,但nacos-sync它并不知道啊): image 点击详情,查看这个接口配置的应用名就是nacos-sync配置的同步应用实例名称: image nacos-sync只知道应用名的前提下,需要遍历元数据中心记录的每个映射关系元数据,逐个确认要同步哪个,这样做的效率比较差(nacos只有按接口名查询映射关系的grpc接口以及查询全部映射关系的http接口,没有提供按映射内容的查询接口)。

这里还是没懂的是为什么同步数据的来源是 接口

8

nacos-sync还可以将同步任务的服务名设置为ALL,表示将nacos源集群的所有实例同步到目标集群,此时它会调用nacos提供的获取所有实例的接口方法,当所有dubbo3服务采用应用级别注册时,这个方法返回的是注册到nacos集群的全部应用名集合。

8

如果修改nacos,增加按content字段查询config_info表的接口,这个接口不好写。 比如content="a,b,c"有三个应用分别是a、b、c,那么查询应用名为b的记录要怎么写,写content like ',%b%,'不行,万一content="b,c"就查不到了,写content like '%b%'也不行,万一content="xbx,c"就查错了。 保证功能正确,只能去遍历groupid=mapping的全部记录,将每条记录的content按逗号分拆成数组,再逐个查数组里的每个元素。记录少的时候这么搞还可以凑合用,记录多的时候还这么搞,效率太差。

4

dubbo3服务采用应用级别注册到nacos2时,实例信息会存放在注册中心的临时实例内存表(dubbo都是临时实例),dubbo3服务的各个接口名与应用名之间的映射关系会存到元数据中心的config_info表(dataId字段存接口名,groupid字段存固定值mapping,content字段存应用名,如果映射到多个应用,各个应用名之间逗号分割)。 nacos-sync同步数据到nacos目标集群,本质是将上述数据从源集群的上述两个表取出服务实例、映射关系等数据放入目标集群,现在nacos提供的从config_info取数据的接口方法只有dataid和groupid条件,没有content参数,只能按dubbo3的接口名和mapping去查询content。

  1. 如果把应用名和所属的各个接口存到config_info表,dataid字段存应用名,groupid字段存固定值appmapping(和mapping区别开来的目的是防止dubbo应用名命名时和某个接口名重名,那样就会取错记录了),content字段存该应用的各个接口;
  2. nacos-sync通过nacos提供的config_info查询接口,把dataid条件设为要同步的应用名、groupid设为appmapping就能获取config_info对应记录的content字段里的所有接口名;
  3. nacos-sync将源集群查询将查到的接口名作为dataid、groupid设为mapping、content设为应用名,循环调用nacos元数据发布接口,依次将这些接口与应用名的映射关系全部存入目标集群的config_info表;
  4. nacos-sync按应用名调用nacos服务实例信息查询接口,获取nacos源集群的服务实例信息,然后调用注册接口方法将获取的服务实例信息登记到nacos目标集群;
  5. 如果获取的服务实例信息里的metadata-type为remote(元数据存储在元数据中心),nacos-sync还需要将应用名作为dataid,服务实例信息里的revision作为groupid条件,调用元数据查询接口从nacos源集群获取dubbo服务实例元数据(查询config_info表),调用元数据发布接口再把服务实例的元数据发布到nacos目标集群,存入目标集群元数据中心的config_info表;
  6. 目标集群的dubbo消费者将rpc接口名作为dataid条件,groupid设为mapping,查询nacos目标集群元数据中心,查询config_info表里对应接口名的应用名(content字段);再将查到的应用名作为服务实例名,调用服务实例查询方法,从nacos目标集群的临时实例内存表拉取对应的dubbo服务实例信息;
  7. 目标集群的dubbo消费者获取服务实例信息后,如果服务实例信息里的metadata-type为local(存在服务端本地),目标集群的dubbo消费者建立长连接到服务实例信息里声明的ip和port之后,从服务端直接拉取dubbo服务实例元数据(比如dubbo.tag);
  8. 如果服务实例信息里的metadata-type为remote,目标集群dubbo消费者将dataid设为应用名,groupid设为服务实例信息里的revision值,调用目标集群元数据中心查询接口,查询config_info对应记录里的dubbo服务实例元数据(content字段)。

这里是不是有个误区,我的问题是为什么不能直接让 nacos-sync 同步指定的几个应用(dataId)

比如 com.example.Demo1 -> app1 com.example.Demo2 -> app1 com.example.Demo3 -> app2

那同步的时候直接配置同步 app1,而不是配置同步 com.example.Demo1

9

只同步app1的实例信息到nacos目标集群没有用,目标集群的dubbo消费者会找不到服务提供者,报no provider,还需要将接口映射关系在内的元数据同步到目标集群的元数据中心。

9

nacos源集群的config_info的dataId目前只有接口名,没有应用名,我提的PR就是把应用名加到dataId列。

3

一个dubbo服务应用级别注册的信息包括两部分,一部分在nacos注册中心作为实例信息,“主键”是应用名,另一部分在nacos元数据中心,"主键"是接口名,内容是映射的应用名集合

5

当服务metadata-type=remote时,还会多一部分元数据在nacos元数据中心config_info表,"主键"是应用名(dataId列) + 应用实例的revision版本号(groupId列),content存实例元数据

7

现在nacos-sync配置的同步任务名称就是应用名称,比如app1,但nacos-sync并不知道app1有哪些接口,最直接的来源是能够从元数据中心获取应用名到接口的映射关系,否则dubbo服务在默认metadata-type=local的情况下,nacos-sync没办法自动在nacos目标集群元数据中心发布接口名到应用名的映射关系。

5

dubbo服务采用应用级别注册时,nacos-sync从注册中心获取实例方法入参只能填写应用名app1,填com.example.Demo1这种接口名是获取不到实例信息的,注册中心显示的实例只有应用名,没有接口名。

4

但是现在dubbo存放元数据中心的映射关系,"主键"只有接口名,问题是nacos-sync每个同步任务配置的servieName参数,当dubbo服务为应用级别注册时,只能配一个应用名,此时如果元数据中心提供从应用名查询全部接口名的功能,那么nacos-sync就能直接在目标集群建立对应的接口到应用名的映射关系了。 当然,修改nacos-sync加个表,手工去表里登记每个应用包含的接口名称也是个办法,不是不可以,只是人工管理起来可能存在风险,一部分元数据是dubbo注册时自动生成的,另一部分元数据手工维护,出现不一致时可能会导致生产出问题。

0

只同步app1的实例信息到nacos目标集群没有用,目标集群的dubbo消费者会找不到服务提供者,报no provider,还需要将接口映射关系在内的元数据同步到目标集群的元数据中心。

这个理解了,相当于同步的时候需要同步两份数据

6
  1. 现在通过接口级服务发现应该是绕过这些问题的,在有同步的场景下可以考虑直接使用接口级的
  2. 如果确实需要同步,目前在 sync 侧将接口名、应用名都全部手动配置应该也是可以走通的,但是相对复杂
  3. 如果需要 SDK 侧主动上报数据,建议通过 ConfigPostProcessor 在内部实现,因为这个需求在开源侧并不是一个大规模通用的场景
8

收到,暂时打算用metadata-type=remote的方式绕过这个问题了:

  1. 先通过应用名从注册中心查到应用实例的revision
  2. 再去元数据中心查应用名+revision对应的实例元数据,解析元数据里的接口名
  3. 通过前两步构建起各个接口名到应用名的mapping,会同实例数据一起同步到目标集群。

首先我们各个应用都是蓝绿部署模式,所以各应用实例的revision都是2个,revision数量有限,把它们全存到元数据中心应该是可行的; 其次,相比接口级别,采用应用级别注册方式的优点很明显,源集群注册实例少了,注册中心之间需要同步的实例数量也会少,注册中心和nacos-sync的压力都会变小。

0

debug跟踪了代码,consumer订阅服务调用ServiceDiscoveryRegistry#doSubscribe时,先调用ServiceNameMapping#getMappingByUrl读取AbstractReferenceConfig的providedBy属性,如果这个属性有值,就直接将其作为接口名和应用名的映射关系,看了一下代码注释,发现它的用途就是指定接口所属的应用级别服务名:

    /**
     * declares which app or service this interface belongs to
     */
    protected String providedBy;

consumer只需要配置这个属性,比如:

@DubboRerference(providedBy = "helloTestApp")
private HelloAservice helloService;

就不再需元数据中心保存各个接口名与应用名的映射关系了。 这样nacos-sync也能在注册中心集群之间同步metadata-type=local方式的应用服务实例了。