Dubbo 消费端引用梳理
服务消息端通过ReferenceConfig get() 方法拿到服务调用代理对象, 从而实现无感的远程调用。你可能有一堆问题
- 代理对象怎么生成的
- 默认通信协议是什么
- 它是如何感知到服务提供者的列表变化
- .......
先直观看下官网服务消费者消费一个服务的过程示意图
从一个入门例子讲起
ReferenceConfig.get() 方法我们获取到服务远程调用的代理对象。调用具体业务方法时代理对象会无感发起远程调用,并且将调用结果返回。这中间可能涉及集群容错、负载均衡、各种拦截器等等
ReferenceConfig<DemoService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setRegistry(new RegistryConfig("N/A"));
referenceConfig.setApplication(new ApplicationConfig("dubbo"));
referenceConfig.setUrl("dubbo://127.0.0.1:20880");
referenceConfig.setInterface(DemoService.class);
referenceConfig.setVersion("1.0");
DemoService demoService = referenceConfig.get();
String sayHello = demoService.sayHello("hello ");
消费端引用源码处理
首次调用get方法时会进行初始化,记录代理对象ref ,后续请求重复使用这个ref对象
初始化过程会判断泛化调用,参数组装等等。核心代码 根据请求参数创建代理对象
ref = createProxy(map);
创建代理对象代码梳理
createProxy 核心代码全貌(以下仅仅摘取核心代码)
private T createProxy(Map<String, String> map) {
if (shouldJvmRefer(map)) {
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
invoker = REF_PROTOCOL.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
if (urls.size() == 1) {
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (UrlUtils.isRegistry(url)) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// for multi-subscription scenario, use 'zone-aware' policy by default
String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);
// The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
} else { // not a registry url, must be direct invoke.
String cluster = CollectionUtils.isNotEmpty(invokers)
? (invokers.get(0).getUrl() != null ? invokers.get(0).getUrl().getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME) : Cluster.DEFAULT)
: Cluster.DEFAULT;
invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
}
}
}
// create service proxy
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}
同一jvm 引用
当服务提供方和调用方是同一jvm进程时,这时不需要发起请求直接调用即可。
if (shouldJvmRefer(map)) {
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
invoker = REF_PROTOCOL.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
}
private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
如果是同一jvm 引用,则new URL对象,其protocol LOCAL_PROTOCOL。
invoker = REF_PROTOCOL.refer(interfaceClass, url); 得到invoker执行对象。
REF_PROTOCOL 由于 dubbo SPI Adaptive机制,会自适应选择InjvmProtocol实现,此时我们的invoker 实际上为 InjvmInvoker ,InjvmInvoker 直接从从对应的Exporter中拿到对应服务提供者的Invoker,执行具体的业务逻辑,这里并不会发起远程调用。
远程调用
如果不是同一jvm 就要需要远程调用,服务提供者信息从注册中心中获取,dubbo 允许多注册中心,对于每个注册中心url 都会转换为一个Invoker。
单注册中心情况
直接通过invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); 拿到单个invoker 对象,REF_PROTOCOL 默认是DubboProtocol
多注册中心情况
多注册中心, 一个注册中心会得到一个invoker 对象,会得到invokers
Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers))
将多个invokers"伪装"成一个invoker ,同时包装了容错策略,负载均衡。
RegistryProtocol
回顾下上篇文章服务发布流程,Dubbo Wrap机制,上面protocol.refer 还会经过 RegistryProtocol 进行增强
directory.subscribe(toSubscribeUrl(subscribeUrl));订阅注册中心变化, RegistryDirectory感知注册中心变化 每一个提供者转换为一个invoker (同一服务,可以有多台服务器分布式部署,每个ip port都会对应一个invoker)。同样通过 cluster.join(directory); 进行容错策略,负载均衡包装。
invoker 已经各种Wrap 包装了,最底层的是DubboInvoker(与Protocol对应)
DubboInvoker
DubboInvoker中包含List invokers属性,这里invokers就是所有服务提供者的列表, 实际调用是 cluster包含负载均衡,选择一个进行具体机器的服务提供者进行调用。
以FailfastClusterInvoker 为例,select 方法根据负载均衡算法选择一个实际的服务提供者进行调用。
再看看DubboInvoker doInvoke 方法,ExchangeClient 是通信的关键。
ExchangeClient
再看看ExchangeClient是哪里来的,回到DubboProtocol
DubboInvoker<T>(serviceType, url, getClients(url), invokers)
再看看getClients(url) 方法,最终会调用initClient(URL url),
追踪进去根据url 建立Client 指定了 requestHandle (和上篇文章服务提供者一样),requestHandle 处理 对应请求处理 (包含TCP连接相关回调处理)reply、received、connected、disconnected等方法。
private ExchangeClient initClient(URL url) {
//省略一些无关代码
ExchangeClient client;
try {
// connection should be lazy
if (url.getParameter(LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
ref 对象
断点查看最终ref对象是什么,真实的Invoker确实被各种包装。从扩展性角度Dubbo 高度扩展性设计,对于源码学习会造成一些学习上障碍。
总结
本文梳理Dubbo消费端获取引用对象,无感发起远程调用流程,当然限于篇幅,本文只讨论主流程, 一些集群容错、负载均衡等等没有涉及。