微信公众号:PersistentCoder
一、背景
在一些跨境业务中,特别是电商或者SAAS场景,用户群体是分离的,经营者在国内,而产品使用者在海外,或者外海用户分布在多个大区,而数据中心在其中一个大区,那么就会存在一些跨大区或者跨机房的服务调用场景,比如海外用户分布在亚洲大区和美洲大区,亚洲大区与美洲大区之间数据节点部署是MS架构,那么如果美洲大区的用户有写操作,就需要调用亚洲大区的服务。
那么就需要在双机房部署的时候,优先调用本机房服务,然后如果本机房没有服务或者不符合要求,那么会调用其他机房的服务。
二、实现方案
多注册中心
dubbo2.7.5版本引入了多注册中心集群负载均衡能力支持,对于多注册中心订阅的场景,选址时的多了一层注册中心集群间的负载均衡:
在Cluster Invoker这一级,支持以下选址策略:
- 指定优先级
<!-- 来自 preferred=“true” 注册中心的地址将被优先选择,只有该中心无可用地址时才 Fallback 到其他注册中心 -->
<dubbo:registry address="nacos://${nacos.address1}" preferred="true" />
- 同zone优先
<!-- 选址时会和流量中的 zone key 做匹配,流量会优先派发到相同 zone 的地址 -->
<dubbo:registry address="nacos://${nacos.address1}" zone="asia" />
- 权重轮询
<!-- 来自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]