一、概要
vivo内部Java技术栈业务使用的是Apache Dubbo框架,基于开源社区2.7.x版本定制化开发。在海量微服务集群的业务实践中,我们发现Dubbo有一些性能瓶颈的问题会极大影响业务逻辑的执行效率,尤其是在集群规模数量较大时(提供方数量>100)时,路由及负载均衡方面有着较大的CPU消耗,从采集的火焰图分析高达30%。为此我们针对vivo内部常用路由策略及负载均衡进行相关优化,并取得了较好的效果。接下来主要跟大家分析一下相关问题产生的根源,以及我们采用怎样的方式来解决这些问题。当前vivo内部使用的Dubbo的主流版本是基于2.7.x进行相关定制化开发。
二、背景知识
2.1 Dubbo客户端调用流程
1.相关术语介绍
2.主要流程
客户端通过本地代理Proxy调用ClusterInvoker,ClusterInvoker从服务目录Directory获取服务列表后经过路由链获取新的服务列表、负载均衡从路由后的服务列表中根据不同的负载均衡策略选取一个远端Invoker后再发起远程RPC调用。
2.2 Dubbo路由机制
Dubbo的路由机制实际是基于简单的责任链模式实现,同时Router继承了Comparable接口,自定义的路由可以设置不同的优先级进而定制化责任链上Router的顺序。基于责任链模式可以支持多种路由策略串行执行如就近路由+标签路由,或条件路由+就近路由等,且路由的配置支持基于接口级的配置也支持基于应用级的配置。常见的路由方式主要有:就近路由,条件路由,标签路由等。具体的执行过程如下图所示:
1. 核心类
Dubbo路由的核心类主要有:RouterChain、RouterFactory 与 Router 。
(1)RouterChain
RouterChain是路由链的入口,其核心字段有
- invokers(List<invoker> 类型)
初始服务列表由服务目录Directory设置,当前RouterChain要过滤的Invoker集合 - builtinRouters(List类型)
当前RouterChain包含的自动激活的Router集合 - routers(List类型)
包括所有要使用的路由由builtinRouters加上通过addRouters()方法添加的Router对象
RouterChain核心逻辑
public class RouterChain<T> {
// 注册中心最后一次推送的服务列表
private List<Invoker<T>> invokers = Collections.emptyList();
// 所有路由,包括原生Dubbo基于注册中心的路由规则如“route://” urls .
private volatile List<Router> routers = Collections.emptyList();
// 初始化自动激活的路由
private List<Router> builtinRouters = Collections.emptyList();
private RouterChain(URL url) {
//通过ExtensionLoader加载可自动激活的RouterFactory
List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
.getActivateExtension(url, ROUTER_KEY);
// 由工厂类生成自动激活的路由策略
List<Router> routers = extensionFactories.stream()
.map(factory -> factory.getRouter(url))
.collect(Collectors.toList());
initWithRouters(routers);
}
// 添加额外路由
public void addRouters(List<Router> routers) {
List<Router> newRouters = new ArrayList<>();
newRouters.addAll(builtinRouters);
newRouters.addAll(routers);
Collections.sort(newRouters, comparator);
this.routers = newRouters;
}
public List<Invoker<T>> route(URL url, Invocation invocation) {
List<Invoker<T>> finalInvokers = invokers;
// 遍历全部的Router对象,执行路由规则
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
}