Dubbo和Nacos都已经学过一些了,这期来学习一下它俩的集成。
注:Dubbo有接口级和应用级的注册方式,下面是以应用级为讲解。 Dubbo接口级和应用级注册的区别
一、服务注册
1-1、启动时服务注册
服务启动正常注册后在Nacos控制台就可以看到类似下面的信息

Dubbo自定义了一个 DubboDeployApplicationListener implements ApplicationListener 在这个里面会去调用服务注册的方法 doRegister
java
@Override
public void doRegister(ServiceInstance serviceInstance) {
execute(namingService, service -> {
Instance instance = toInstance(serviceInstance);
service.registerInstance(instance.getServiceName(), group, instance);
});
}
具体调用的地方如下

完整的调用链路
- org.apache.dubbo.config.spring.context.DubboDeployApplicationListener#onApplicationEvent
- ... 直接断点就可以看了,不全部写出来了
- org.apache.dubbo.registry.nacos.NacosServiceDiscovery#doRegister
1-2、心跳检测

当程序执行完 132行之后,在Nacos上发现服务已经注册上去了,但紧接着在133行进行了断点,且不放行,过一会会发现Nacos上的服务没了。如果放开断点,让程序继续执行,再去Nacos上发现服务又有了,这说明客户端和Nacos服务端是有心跳检测的
心跳检测是由客户端发起的,客户端会启动一个定时任务,定时的去请求服务端
定时任务的入口:com.alibaba.nacos.common.remote.client.RpcClient#start
真正的心跳请求方法如下,当把 currentConnection打上断点,过一会Nacos上的服务就没了,放行断点一会,Nacos上的服务又有了
com.alibaba.nacos.common.remote.client.RpcClient#healthCheck
java
private boolean healthCheck() {
HealthCheckRequest healthCheckRequest = new HealthCheckRequest();
if (this.currentConnection == null) {
return false;
}
try {
Response response = this.currentConnection.request(healthCheckRequest, 3000L);
// not only check server is ok, also check connection is register.
return response != null && response.isSuccess();
} catch (NacosException e) {
// ignore
}
return false;
}
二、服务发现
Dubbo服务消费者启动时,会从Nacos获取可用的服务提供者列表。每一个可用的服务都会生成一个 invoker,在发起服务调用的时候会选择一个 invoker进行调用
调用者选择的入口如下,可参考 Dubbo消费者一次请求的过程
org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke
java
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
// ...
for (int i = 0; i < len; i++) {
// 选择一个 invoker进行调用
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
try {
Result result = invokeWithContext(invoker, invocation);
// ...
return result;
}
// ...
}
// ...
}
2-1、invoker的创建
1、通过接口定义获取服务的提供者的url

org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener#getAddresses
java
protected List<URL> getAddresses(ProtocolServiceKey protocolServiceKey, URL consumerURL) {
List<ProtocolServiceKeyWithUrls> protocolServiceKeyWithUrlsList =
serviceUrls.get(protocolServiceKey.getInterfaceName());
List<URL> urls = new ArrayList<>();
if (protocolServiceKeyWithUrlsList != null) {
for (ProtocolServiceKeyWithUrls protocolServiceKeyWithUrls : protocolServiceKeyWithUrlsList) {
if (ProtocolServiceKey.Matcher.isMatch(
protocolServiceKey, protocolServiceKeyWithUrls.getProtocolServiceKey())) {
urls.addAll(protocolServiceKeyWithUrls.getUrls());
}
}
}
if (serviceUrls.containsKey(CommonConstants.ANY_VALUE)) {
for (ProtocolServiceKeyWithUrls protocolServiceKeyWithUrls : serviceUrls.get(CommonConstants.ANY_VALUE)) {
urls.addAll(protocolServiceKeyWithUrls.getUrls());
}
}
return urls;
}
2、基于生产者的url创建 invoker
org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory#notify
org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory#refreshInvoker
org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory#toInvokers
3、invoker 赋值
org.apache.dubbo.rpc.cluster.directory.AbstractDirectory#setInvokers
org.apache.dubbo.rpc.cluster.directory.AbstractDirectory#refreshInvokerInternal
java
private synchronized void refreshInvokerInternal() {
BitList<Invoker<T>> copiedInvokers = invokers.clone();
refreshInvokers(copiedInvokers, invokersToReconnect);
refreshInvokers(copiedInvokers, disabledInvokers);
validInvokers = copiedInvokers;
}
2-2、项目启动时 invoker初始化流程
最终invoker的创建都是 2-1、invoker的创建,这里只需要知道什么时候去调用的 getAddresses

2-2、服务端上下线 invoker动态更新
最终invoker的创建都是 2-1、invoker的创建,这里只需要知道什么时候去调用的 getAddresses
注:服务上下线,都是由Nacos服务端通知到这里的

2-3、invoker的选择与故障转移
再来看一下 org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke,它选择完 invoker并不是就结束了,而是在 for循环里面去处理的,这是因为很有可能之前选择的 invoker是故障的,这时候它会继续去选择其它的 invoker
org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke
java
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
String methodName = RpcUtils.getMethodName(invocation);
int len = calculateInvokeTimes(methodName);
// retry loop.
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
// ...
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
invoked.add(invoker);
RpcContext.getServiceContext().setInvokers((List) invoked);
boolean success = false;
try {
Result result = invokeWithContext(invoker, invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn(...);
}
success = true;
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
if (!success) {
providers.add(invoker.getUrl().getAddress());
}
}
}
throw new RpcException(...);
}
select 方法入参里面就包含了之前选择过的 invoker,这样就可以排除之前故障的 invoker了
org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#select
java
/**
* 使用负载平衡策略选择调用者。
* a) 首先,使用负载均衡选择一个调用者。如果此调用程序在先前选择的列表中,或者,如果此调用程序不可用,则继续步骤 b(重新选择),否则返回第一个选定的调用程序
* b) 重选,重选的生效规则:已选>可用。该规则保证所选择的调用者有最小的机会成为先前选择的列表中的一员,并且还保证该调用者可用。
* 参数:
* loadbalance负载均衡策略
* invocation ------调用
* invokers ------调用者候选人
* selected -- 是否排除选定的调用者
* 返回:最终执行调用的调用者。
* 投掷:RpcException异常
*/
protected Invoker<T> select( LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
// ...
}
2-4、serviceUrls 的赋值
获取url的时候都是通过 serviceUrls 获取的,而生成 invoker是基于url,可想而知这个url是关键。当生产者注册和注销的时候,消费者肯定都是要感知的,所以定然有一个通知的机制
设置 serviceUrls,的入口为
org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener#doOnEvent
通知的链路

三、断开重连
如果Nacos挂掉了,在这之前Dubbo消费者有一个,提供者有2个
| 序号 | 场景描述 | 结果 |
|---|---|---|
| 1 | Dubbo消费者和提供者都不变 | 两个提供者正常访问 |
| 2 | Dubbo提供者挂掉了一个 | 一个提供者正常访问 |
| 3 | Dubbo提供者全部挂掉 | 无法访问 |
| 4 | 重启Dubbo提供者 | 无法启动成功,因为Nacos连接不上 |
| 5 | Nacos恢复,重启Dubbo提供者 | 正常访问 |