“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情”
经过前面几篇文章分析,Dubbo的服务提供方终于正常启动并可以处理请求了,今天就带着大家一起看下Dubbo的服务调用方的启动流程
一、消费Demo
public class ConsumerApplication {
public static void main(String[] args) {
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
reference.setInterface(DemoService.class);
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap
.application(new ApplicationConfig("dubbo-demo-api-consumer"))
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
.reference(reference)
.start();
String message = ReferenceConfigCache.getCache().get(reference).sayHello("dubbo");
System.out.println("Application_sayHello_" + message);
}
}
以API调用为例,可以看到,消费端通过ReferenceConfig的API来执行对应的服务调用。
二、时序图
老规矩,有图先看图,以上就是客户端调用方整体流程。首先通过ReferenceConfigCache类获取ReferenceConfig,内部对ReferenceConfig做了个缓存,优先从对应的缓存中获取,否则执行ReferenceConfig init方法进行初始化,同前文分析的其他流程一样,依然是调用的Protocol$Adaptive类的方法,经历过层层Wrapper到达RegistryProtocol类,执行其doRefer方法获取到对应的invoke。
三、RegistryProtocol-doRefer
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
... ...
// 1、建立路由规则
directory.buildRouterChain(subscribeUrl);
// 2、订阅服务提供方者地址
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
// 3、包装容错策略到invoker
Invoker invoker = cluster.join(directory);
return invoker;
}
doRefer的核心逻辑如上,首先创建对应的路由规则,毕竟服务端肯定不是单点服务,一般来说都是分布式部署,因此要确定,用哪种路由策略来选择对应的服务端机器;然后向ZK订阅对应的服务端地址,以便客户端能感知到服务端机器的上下线;最后生成invoker对象,并绑定对应的容错策略;
3.1.创建路由规则
查看directory.buildRouterChain实现如下:
public void buildRouterChain(URL url) {
this.setRouterChain(RouterChain.buildChain(url));
}
public static <T> RouterChain<T> buildChain(URL url) {
return new RouterChain<>(url);
}
跟踪RouterChain初始化方法可以看到,默认四种routers实现,分别是MockInvokersSelector、TagRouter、AppRouter、ServiceRouter
后面选择集群容错策略后,发起服务调用前,会调用routerChain.route,获取负载均衡后的invoek列表(本文先不展开讲了,分析负载均衡时候的时候在展开)
3.2 订阅服务提供方地址
3.2.1.时序图
3.2.2.步骤2
步骤2主要从zk获取服务提供者列表,获取到后会调用notify方法通知RegistryProtocol
本地Debug发现实际URL是有三个,查看对应的时间戳发现,第一条才是我本地启动的服务方。另外两条看时间戳应该是脏数据,通过看协议头也能发现,一个的dubbo,一个是empty。
3.2.2.步骤12
主要是将服务提供者地址URL转换成invoker对象,这里最终调用的是protocol.refer方法,阅读过前文的同学想必对这个对象已经很熟悉了,没错,实际调用的就是就是Protocol$Adaptive方法,依然是经历层层Wrapper,最终调用到DubboProtocol相关方法。
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
... ...
invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
... ...
}
首先调用DubboProtocol的父类refer方法,内部会调用抽象方法protocolBindingRefer,查看DubboProtocol实现,getClients方法用来创建消费端的NettyClient对象
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
private ExchangeClient[] getClients(URL url) {
// whether to share connection
boolean useShareConnect = false;
int connections = url.getParameter(CONNECTIONS_KEY, 0);
List<ReferenceCountExchangeClient> shareClients = null;
if (connections == 0) {
useShareConnect = true;
String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
shareClients = getSharedClient(url, connections);
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (useShareConnect) {
clients[i] = shareClients.get(i);
} else {
clients[i] = initClient(url);
}
}
return clients;
}
这里关注下useShareConnect变量,用来表示是否共享链接。默认是共享的,也就是说引用同一台机器上的多个服务时,可以复用一个Netty链接,同时初始化client的时候,可以选择是否懒加载创建链接,默认是客户端启动的时候就和服务端建立连接。
3.3 选择容错策略
当directory.subscribe方法执行成功后,此时就把服务提供者提供的RUL信息转成了Invoker列表,并保存到了RegistryDerectory里。下面就来看看cluster.join的实现,查看源码发现cluster对象本身也是个SPI注入点,默认实现为FailoverCluster,查看FailoverCluster实现如下
@Override
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<>(directory);
}
同时dubbo会对cluster使用wrapper类MockClusterWrapper进行增强,也就是说cluster.join用的cluster对象实际为MockClusterWrapper对象,MockClusterWrapper对象内部调用了FailoverCluster方法。
public class MockClusterWrapper implements Cluster {
private Cluster cluster;
public MockClusterWrapper(Cluster cluster) {
this.cluster = cluster;
}
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
}
MockClusterWrapper,顾名思义,用来实现mock功能。若是开启Mock则须要过滤出MockInvoker后执行服务降级,这里就不展开讲了。
回到容错机制,可以看到dubbo提供了很多集群容错的方式
- Failover Cluster
- 失败重试,可通过retries=x设置重试次数,通常用于幂等性的读操作。
- Failsafe Cluster
- 快速失败,出现异常时,立刻报错。通常用于非幂等的写操作。
- Failback Cluste
- 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
- Forking Cluster
- 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源
- Broadcast Cluste
- 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息
四、小结
本文主要分析了服务调用方相关实现,从ReferenceConfig一直分析到了Netty客户端启动,其中涉及到路由策略选择、invoke注册、集群容错策略等,有些知识点由于篇幅限制,暂未展开讲解,后面将会为大家一一呈现。