Dubbo跨机房调用

2022-04-06 14:28:55 浏览数 (1)

微信公众号:PersistentCoder

一、背景

在一些跨境业务中,特别是电商或者SAAS场景,用户群体是分离的,经营者在国内,而产品使用者在海外,或者外海用户分布在多个大区,而数据中心在其中一个大区,那么就会存在一些跨大区或者跨机房的服务调用场景,比如海外用户分布在亚洲大区和美洲大区,亚洲大区与美洲大区之间数据节点部署是MS架构,那么如果美洲大区的用户有写操作,就需要调用亚洲大区的服务。

那么就需要在双机房部署的时候,优先调用本机房服务,然后如果本机房没有服务或者不符合要求,那么会调用其他机房的服务。

二、实现方案

多注册中心

dubbo2.7.5版本引入了多注册中心集群负载均衡能力支持,对于多注册中心订阅的场景,选址时的多了一层注册中心集群间的负载均衡:

在Cluster Invoker这一级,支持以下选址策略:

  • 指定优先级
代码语言:javascript复制
<!-- 来自 preferred=“true” 注册中心的地址将被优先选择,只有该中心无可用地址时才 Fallback 到其他注册中心 -->
<dubbo:registry address="nacos://${nacos.address1}" preferred="true" />
  • 同zone优先
代码语言:javascript复制
<!-- 选址时会和流量中的 zone key 做匹配,流量会优先派发到相同 zone 的地址 -->
<dubbo:registry address="nacos://${nacos.address1}" zone="asia" />
  • 权重轮询
代码语言:javascript复制
<!-- 来自asia和america集群的地址,将以 8:2 的比例来分配流量 -->
<dubbo:registry id="asia" address="nacos://${nacos.address1}" weight=”80“ />
<dubbo:registry id="america" address="nacos://${nacos.address2}" weight=”20“ />
  • 默认,任意可用

配置调整

对于亚洲大区,读写都只需要调用本机房的服务,只需配置:

代码语言:javascript复制
<dubbo:registry address="nacos://${asia.address}" preferred="true" />

对于美洲大区,需要调用亚洲大区的写服务,因此需要配置美洲和亚洲两个注册中心,并将美洲的注册中心标记为默认:

代码语言:javascript复制
<dubbo:registry id="america" address="nacos://${america.address}" preferred="true" />
<dubbo:registry id="asia" address="nacos://${asia.address}" />

代码实现

代码语言:javascript复制
@Reference(registry="asia")
WriteAPI writeApi;

写服务注入强制指定亚洲大区,这样对于美洲大区调用写服务会调用到亚洲大区,对于亚洲大区调用写服务也会调用本大区服务。

三、原理分析

多注册中心集群负载均衡能力支持主要由ZoneAwareClusterInvoker来实现,核心代码如下:

代码语言:javascript复制
@Override
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    //首先优先选择标记为preferred的注册中心的ClusterInvoker
    for (Invoker<T> invoker : invokers) {
        ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
        if (clusterInvoker.isAvailable() && clusterInvoker.getRegistryUrl()
                .getParameter(REGISTRY_KEY   "."   PREFERRED_KEY, false)) {
            return clusterInvoker.invoke(invocation);
        }
    }
    //然后选择同zone的服务ClusterInvoker
    String zone = invocation.getAttachment(REGISTRY_ZONE);
    if (StringUtils.isNotEmpty(zone)) {
        for (Invoker<T> invoker : invokers) {
            ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
            if (clusterInvoker.isAvailable() && zone.equals(clusterInvoker.getRegistryUrl().getParameter(REGISTRY_KEY   "."   ZONE_KEY))) {
                return clusterInvoker.invoke(invocation);
            }
        }
        String force = invocation.getAttachment(REGISTRY_ZONE_FORCE);
        if (StringUtils.isNotEmpty(force) && "true".equalsIgnoreCase(force)) {
            throw new IllegalStateException("");
        }
    }

    //按照权重选择Invoker
    Invoker<T> balancedInvoker = select(loadbalance, invocation, invokers, null);
    if (balancedInvoker.isAvailable()) {
        return balancedInvoker.invoke(invocation);
    }

    //随机选择一个可用的
    for (Invoker<T> invoker : invokers) {
        ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
        if (clusterInvoker.isAvailable()) {
            return clusterInvoker.invoke(invocation);
        }
    }
    //随机选择一个
    return invokers.get(0).invoke(invocation);
}

从代码中我们可以看出几种策略的优先级,优先选择注册中心标记为preferred的Invoker调用,如果没有则选择同大区的服务调用,否则使用负载均衡根据权重选择Invoker,再者就随机选择一个可用的Invoker,最后如果前边都不满足则随便选择一个Invoker调用。

四、Bug

我们团队使用的dubbo框架版本是2.7.8,但是这个版本有一个比较低级的bug,就是前边代码中优先选择preferred的时候有一行代码获取preferred:

代码语言:javascript复制
clusterInvoker.getRegistryUrl().getParameter(REGISTRY_KEY   "."   PREFERRED_KEY, false)

这里用REGISTRY_KEY和PREFERRED_KEY进行了连接,getParameter的key是registry.preferred,而注册中心配置的key是preferred,所以配置会不生效,后续高版本对该bug进行了修复:

但是升级版本是一个高风险的操作,我们不能拿别人的错误惩罚自己。当然我们还有别的解决方案,既然他要的key是registry.preferred那么我就给你这个key。

把registry.preferred=true拼到注册中心url的后边就行了,实测可用。配置改成如下:

代码语言:javascript复制
<dubbo:registry id="america" address="nacos://${america.address}?registry.preferred=true" />
<dubbo:registry id="asia" address="nacos://${asia.address}" />

当然在高版本中直接使用preferred=true替换即可,在不确定当前版本是否有bug的情况下可以两种方式都写上。

五、总结

ZoneAwareClusterInvoker是其他ClusterInvoker的包装,ClusterInvoker又是Invoker的包装,那么ZoneAwareClusterInvoker的调用逻辑就是:

回过头来我们思考一个问题,就本篇文章分析的亚洲和美洲双大区注册中心场景中,美洲机房显式配置了两个注册中心,但是对于美洲集群,配置亚洲注册中心的目的只是订阅服务,没有双大区注册服务的诉求,然后dubbo的服务注册和订阅机制中并没有将注册和订阅做隔离,也就是说美洲的服务也会注册到亚洲注册中心,只不过不会有消费这而已,是不是在某种程度上造成了注册中心内存的浪费,以及美洲大区服务启动耗时增加。

本着浪费可耻,节约光荣的原则,那有没有一种机制或者有没有可能对于这种跨大区服务调用的场景,只有订阅服务诉求的情况下,做到服务订阅和服务注册隔离以及可个性化定制?

目前好像暂不支持,如果感兴趣可以自己研究下服务注册和订阅流程的源码,是否能够做到使用SPI或者其他方式做到隔离和定制化,以及实现之后的合理性和价值。

六、参考

ZoneAwareClusterInvoker

2.7.5新特性

多注册中心机制

[Dubbo-6034]

0 人点赞