Dubbo如何使用Nacos做注册中心的

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);
    });
}

具体调用的地方如下

完整的调用链路

  1. org.apache.dubbo.config.spring.context.DubboDeployApplicationListener#onApplicationEvent
  2. ... 直接断点就可以看了,不全部写出来了
  3. 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提供者 正常访问
相关推荐
Spring AI学习5 小时前
Spring AI深度解析(9/50):可观测性与监控体系实战
java·人工智能·spring
java1234_小锋6 小时前
Spring IoC的实现机制是什么?
java·后端·spring
xqqxqxxq6 小时前
背单词软件技术笔记(V2.0扩展版)
java·笔记·python
消失的旧时光-19437 小时前
深入理解 Java 线程池(二):ThreadPoolExecutor 执行流程 + 运行状态 + ctl 原理全解析
java·开发语言
哈哈老师啊7 小时前
Springboot学生综合测评系统hxtne(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·数据库·spring boot
4311媒体网7 小时前
帝国cms调用文章内容 二开基本操作
java·开发语言·php
zwxu_7 小时前
Nginx NIO对比Java NIO
java·nginx·nio
可观测性用观测云8 小时前
Pyroscope Java 接入最佳实践
java
气π9 小时前
【JavaWeb】——(若依 + AI)-基础学习笔记
java·spring boot·笔记·学习·java-ee·mybatis·ruoyi
阿里云云原生9 小时前
AgentScope Java 1.0:从模型到应用,AI Agent 全生命周期管理利器!
java·云原生