dubbo 服务消费原理分析之服务目录

news2024/9/21 20:42:28

文章目录

  • 前言
  • 一、RegistryDirectory
    • 1、DynamicDirectory
    • 2、RegistryProtocol.doCreateInvoker
    • 2、RegistryProtocol.subscribe
    • 3、ListenerRegistryWrapper.subscribe
    • 4、FailbackRegistry.subscribe
    • 5、ZookeeperRegistry.doSubscribe
    • 6、RegistryDirectory.notify
    • 7、RegistryDirectory.refreshOverrideAndInvoker
    • 8、RegistryDirectory.toInvokers
  • 二、Protocol
    • 1、DubboProtocol.refer
    • 2、DubboProtocol.getClients
    • 3、NettyTransporter.connect
    • 4、NettyClient


前言

文章基于3.1.0版本进行分析

		<dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>3.1.0</version>
        </dependency>

一、RegistryDirectory

在这里插入图片描述

RegistryDirectory是Dubbo中的服务目录,服务目录中存储了一些和服务提供者有关的信息。通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。
通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用。

创建RegistryDirectory会出初始化父类DynamicDirectory ,初始化接口、分组、失败策略等,从DynamicDirectory 开始分析

1、DynamicDirectory

创建服务目录与初始化的一些信息,比如接口、分组、失败策略

	public DynamicDirectory(Class<T> serviceType, URL url) {
        super(url, true);

        ModuleModel moduleModel = url.getOrDefaultModuleModel();

        // 容错适配器,Cluster$Adaptive 默认的容错机制是失效转移 failover
		this.cluster = moduleModel.getExtensionLoader(Cluster.class).getAdaptiveExtension();
        // 路由工厂适配器RouterFactory$Adaptive
		this.routerFactory = moduleModel.getExtensionLoader(RouterFactory.class).getAdaptiveExtension();

        if (serviceType == null) {
            throw new IllegalArgumentException("service type is null.");
        }

        if (StringUtils.isEmpty(url.getServiceKey())) {
            throw new IllegalArgumentException("registry serviceKey is null.");
        }

        this.shouldRegister = !ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true);
        this.shouldSimplified = url.getParameter(SIMPLIFIED_KEY, false);

        // 接口类型 org.sjl.dubbo.AsyncProvider
		this.serviceType = serviceType;
		// 接口key org.sjl.dubbo.AsyncProvider
        this.serviceKey = super.getConsumerUrl().getServiceKey();

        this.directoryUrl = consumerUrl;
        String group = directoryUrl.getGroup("");
        this.multiGroup = group != null && (ANY_VALUE.equals(group) || group.contains(","));
		// 默认快速失败
        this.shouldFailFast = Boolean.parseBoolean(ConfigurationUtils.getProperty(moduleModel, Constants.SHOULD_FAIL_FAST_KEY, "true"));
    }

2、RegistryProtocol.doCreateInvoker

ClusterInvoker的创建过程

	protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
        // 初始化服务目录的注册中心和协议
		directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<>(directory.getConsumerUrl().getParameters());
		// urlToRegistry = consumer://192.168.0.101/org.sjl.dubbo.AsyncProvider?application=dubbo-springboot-start-consumer&background=false&dubbo=2.0.2
		// &interface=org.sjl.dubbo.AsyncProvider&methods=sayHiAsync,sayHello,sayHelloAsync&pid=15072&qos.enable=false&release=3.1.0
		// &side=consumer&sticky=false&timeout=30000&timestamp=1725462541548
		URL urlToRegistry = new ServiceConfigURL(
            parameters.get(PROTOCOL_KEY) == null ? CONSUMER : parameters.get(PROTOCOL_KEY),
            parameters.remove(REGISTER_IP_KEY),
            0,
            getPath(parameters, type),
            parameters
        );
        urlToRegistry = urlToRegistry.setScopeModel(directory.getConsumerUrl().getScopeModel());
        urlToRegistry = urlToRegistry.setServiceModel(directory.getConsumerUrl().getServiceModel());
        if (directory.isShouldRegister()) {
            directory.setRegisteredConsumerUrl(urlToRegistry);
			//  消费者配置到注册中心
            registry.register(directory.getRegisteredConsumerUrl());
        }
		// 路由调用链
        directory.buildRouterChain(urlToRegistry);
		// 服务发现并订阅的逻辑 核心逻辑
        directory.subscribe(toSubscribeUrl(urlToRegistry));

        return (ClusterInvoker<T>) cluster.join(directory, true);
    }

2、RegistryProtocol.subscribe

    public void subscribe(URL url) {
        setSubscribeUrl(url);
        registry.subscribe(url, this);
    }

3、ListenerRegistryWrapper.subscribe

进行包装,对订阅完成后的后置处理

	public void subscribe(URL url, NotifyListener listener) {
        try {
            if (registry != null) {
                registry.subscribe(url, listener);
            }
        } finally {
            if (CollectionUtils.isNotEmpty(listeners)) {
                RuntimeException exception = null;
                for (RegistryServiceListener registryListener : listeners) {
                    if (registryListener != null) {
                        try {
                            registryListener.onSubscribe(url, registry);
                        } catch (RuntimeException t) {
                            logger.error(t.getMessage(), t);
                            exception = t;
                        }
                    }
                }
                if (exception != null) {
                    throw exception;
                }
            }
        }
    }

4、FailbackRegistry.subscribe

failbackRegistry这个类,从名字就可以看出,它的主要作用就是实现具有故障恢复功能的服务订阅机制,简单来说就是如果在订阅服务注册中心时出现异常,会触发重试机制。

	public void subscribe(URL url, NotifyListener listener) {
        super.subscribe(url, listener);
		// 移除失效的监听
        removeFailedSubscribed(url, listener);
        try {
            // Sending a subscription request to the server side
			// 发送订阅到服务端
            doSubscribe(url, listener);
        } catch (Exception e) {
            Throwable t = e;

            // *** 异常处理

            // Record a failed registration request to a failed list, retry regularly
            addFailedSubscribed(url, listener);
        }
    }

5、ZookeeperRegistry.doSubscribe

所有Service层发起订阅,类似于监控中心发起的订阅
指定的Service层发起的订阅,服务消费者的订阅

	@Override
    public void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            checkDestroyed();
			// 针对通用服务接口
            if (ANY_VALUE.equals(url.getServiceInterface())) {
                // ***
            }
			// 针对指定服务,正常走到这里
			else {
                CountDownLatch latch = new CountDownLatch(1);

                try {
                    List<URL> urls = new ArrayList<>();

                    // ["/dubbo/org.sjl.dubbo.GreetingsProvider/providers", ".../configurators", ".../routers"]
					// 需要订阅以上三个节点下数据变化
					for (String path : toCategoriesPath(url)) {
						// 构建一个listeners集合,其中key是NotifyListener,
						// value表示针对这个RegistryDirectory注册的子节点监听。
                        ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
                        ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, k, latch));

                        if (zkListener instanceof RegistryChildListenerImpl) {
                            ((RegistryChildListenerImpl) zkListener).setLatch(latch);
                        }

                        // 创建非临时节点,不存在时候会创建 
						// /dubbo/org.sjl.dubbo.GreetingsProvider/providers
                        // /dubbo/org.sjl.dubbo.GreetingsProvider/configurators
						// /dubbo/org.sjl.dubbo.GreetingsProvider/routers
						zkClient.create(path, false);
						// 服务目录创建完毕之后创建一个监听器用来监听子目录,同时要返回一个path目录的子子节点,
						// 比如providers下面的提供者节点列表,如果有多个可以返回多个
                        List<String> children = zkClient.addChildListener(path, zkListener);
                        if (children != null) {
                            urls.addAll(toUrlsWithEmpty(url, path, children));
                        }
                    }

                    // 调用notify方法触发回调通知
					notify(url, listener, urls);
                } finally {
                    // tells the listener to run only after the sync notification of main thread finishes.
                    latch.countDown();
                }
            }
        } catch (Throwable e) {
            throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

children里面值实例,包括ip、port、接口等参数

dubbo://192.168.0.101:20880/org.sjl.dubbo.GreetingsProvider?anyhost=true
&application=dubbo-springboot-start-provider
&background=false
&deprecated=false
&dubbo=2.0.2
&dynamic=true
&generic=false
&interface=org.sjl.dubbo.GreetingsProvider
&methods=sayHello,sayHi
&pid=30316&release=3.1.0
&service-name-mapping=true
&side=provider&timeout=30000
&timestamp=1725293330893

notify回调通知,链路结构为

FailbackRegistry -> AbstractRegistry -> RegistryDirectory

6、RegistryDirectory.notify

服务地址发生变化和更新时,调用Directory.notify来更新,意味着更新后的信息,会同步到Directory

	public synchronized void notify(List<URL> urls) {
        if (isDestroyed()) {
            return;
        }
		// 过滤不需要的数据
        Map<String, List<URL>> categoryUrls = urls.stream()
            .filter(Objects::nonNull)
            .filter(this::isValidCategory)
            .filter(this::isNotCompatibleFor26x)
            .collect(Collectors.groupingBy(this::judgeCategory));

        // 假设当前进来的通知是 providers节点
		// 判断configurator是否为空,这个节点下的配置,是在dubbo-admin控制台上修改配置时,会先创建一个配置节点到这个路径下,
		// 注册中心收到这个变化时会通知服务消费者,服务消费者会根据新的配置重新构建Invoker
		List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
        this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);

        // 判断路由规则配置是否为空,如果不为空,同样将路由规则添加到url中。
		List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
        toRouters(routerURLs).ifPresent(this::addRouters);

        // providers
		// 得到服务提供者的地址列表
        List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());

        // 3.x added for extend URL address
        ExtensionLoader<AddressListener> addressListenerExtensionLoader = getUrl().getOrDefaultModuleModel().getExtensionLoader(AddressListener.class);
        List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
        if (supportedListeners != null && !supportedListeners.isEmpty()) {
            for (AddressListener addressListener : supportedListeners) {
                providerURLs = addressListener.notify(providerURLs, getConsumerUrl(), this);
            }
        }
		// 刷新并覆盖注册中心里面的配置、url、Invoker,
        refreshOverrideAndInvoker(providerURLs);
    }

7、RegistryDirectory.refreshOverrideAndInvoker

路由调用refreshInvoker方法

当注册中心的服务地址发生变化时,会触发更新。而更新之后并不是直接把url地址存储到内存,而是把
url转化为invoker进行存储,这个invoker是作为通信的调用器来构建的领域对象,所以如果地址发生变
化,那么需要把老的invoker销毁,然后用心的invoker替代。

    private synchronized void refreshOverrideAndInvoker(List<URL> urls) {
        // mock zookeeper://xxx?mock=return null
        refreshInvoker(urls);
    }
	
	private void refreshInvoker(List<URL> invokerUrls) {
        Assert.notNull(invokerUrls, "invokerUrls should not be null");
		// 如果只有一个服务提供者,并且如果是空协议,那么这个时候直接返回进制访问,并且销毁所有的invokers
        if (invokerUrls.size() == 1
            && invokerUrls.get(0) != null
            && EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            this.forbidden = true; // Forbid to access
            routerChain.setInvokers(BitList.emptyList());
            destroyAllInvokers(); // Close all invokers
        } else {
			this.forbidden = false; // Allow to access

            //  第一次初始化的时候,invokerUrls为空。
			if (invokerUrls == Collections.<URL>emptyList()) {
                invokerUrls = new ArrayList<>();
            }
            // use local reference to avoid NPE as this.cachedInvokerUrls will be set null by destroyAllInvokers().
            // 获取invoker缓存
			Set<URL> localCachedInvokerUrls = this.cachedInvokerUrls;
            if (invokerUrls.isEmpty() && localCachedInvokerUrls != null) {

                // 1-4 Empty address.
                logger.warn("1-4", "configuration ", "",
                    "Service" + serviceKey + " received empty address list with no EMPTY protocol set, trigger empty protection.");

                invokerUrls.addAll(localCachedInvokerUrls);

            } else {
                localCachedInvokerUrls = new HashSet<>();
                localCachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
                this.cachedInvokerUrls = localCachedInvokerUrls;
            }
            if (invokerUrls.isEmpty()) {
                return;
            }

            // use local reference to avoid NPE as this.urlInvokerMap will be set null concurrently at destroyAllInvokers().
            Map<URL, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
            // can't use local reference as oldUrlInvokerMap's mappings might be removed directly at toInvokers().
            Map<URL, Invoker<T>> oldUrlInvokerMap = null;
            if (localUrlInvokerMap != null) {
                // the initial capacity should be set greater than the maximum number of entries divided by the load factor to avoid resizing.
                oldUrlInvokerMap = new LinkedHashMap<>(Math.round(1 + localUrlInvokerMap.size() / DEFAULT_HASHMAP_LOAD_FACTOR));
                localUrlInvokerMap.forEach(oldUrlInvokerMap::put);
            }
			// 将URL转换为Invoker  这里会做一些协议的指定过滤操作
            Map<URL, Invoker<T>> newUrlInvokerMap = toInvokers(oldUrlInvokerMap, invokerUrls);// Translate url list to Invoker map

            /*
             * If the calculation is wrong, it is not processed.
             *
             * 1. The protocol configured by the client is inconsistent with the protocol of the server.
             *    eg: consumer protocol = dubbo, provider only has other protocol services(rest).
             * 2. The registration center is not robust and pushes illegal specification data.
             *
             */
            if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {

                // 3-1 - Failed to convert the URL address into Invokers.

                logger.error(
                    "3-1", "inconsistency between the client protocol and the protocol of the server",
                    "", "urls to invokers error",
                    new IllegalStateException(
                        "urls to invokers error. invokerUrls.size :" +
                            invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));

                return;
            }

            List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
            this.setInvokers(multiGroup ? new BitList<>(toMergeInvokerList(newInvokers)) : new BitList<>(newInvokers));
            // pre-route and build cache
            routerChain.setInvokers(this.getInvokers());
            this.urlInvokerMap = newUrlInvokerMap;

            try {
				// 销毁不使用或者已经被删除的 Invoker。例如某个提供者的代码删除了
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
            } catch (Exception e) {
                logger.warn("destroyUnusedInvokers error. ", e);
            }

            // notify invokers refreshed
            this.invokersChanged();
        }
    }

将invokerURL列表转换为调用器映射。转换规则如下:

  • 如果URL已转换为invoker,它将不再被重新引用并直接从缓存中获取,请注意,URL中的任何参数更改都将被重新引用。
  • 如果传入调用器列表不为空,则表示它是最新的调用器列表。
  • 如果传入invokerUrl的列表为空,则意味着该规则只是一个覆盖规则或路由规则,需要重新对比以决定是否重新引用。

8、RegistryDirectory.toInvokers

协议过滤,配置合并都会在这里面去做。url转换为调用程序,如果url已被引用,则不会重新引用。将放入newUrlInvokeMap的项将从OldUrlInvoceMap中删除。

    private Map<URL, Invoker<T>> toInvokers(Map<URL, Invoker<T>> oldUrlInvokerMap, List<URL> urls) {
        Map<URL, Invoker<T>> newUrlInvokerMap = new ConcurrentHashMap<>(urls == null ? 1 : (int) (urls.size() / 0.75f + 1));
        if (urls == null || urls.isEmpty()) {
            return newUrlInvokerMap;
        }
		// 获取指定的协议
        String queryProtocols = this.queryMap.get(PROTOCOL_KEY);
		// 遍历所有提供者列表进行转换
        for (URL providerUrl : urls) {
            // If protocol is configured at the reference side, only the matching protocol is selected
            if (queryProtocols != null && queryProtocols.length() > 0) {
                boolean accept = false;
                String[] acceptProtocols = queryProtocols.split(",");
                for (String acceptProtocol : acceptProtocols) {
                    if (providerUrl.getProtocol().equals(acceptProtocol)) {
                        accept = true;
                        break;
                    }
                }
                if (!accept) {
                    continue;
                }
            }

            // 空协议 直接跳过
			if (EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
                continue;
            }
			//  查询扩展是否存在,这个要注意如果是自定义扩展或者像webservice这样的扩展一般需要额外引入扩展包才可以
            if (!getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {

                // 4-1 - Unsupported protocol

                logger.error("4-1", "typo in URL", "", "Unsupported protocol.",
                    new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() +
                    " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() +
                    " to consumer " + NetUtils.getLocalHost() + ", supported protocol: " +
                    getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).getSupportedExtensions()));

                continue;
            }
			// 合并url参数。顺序是:覆盖协议配置(比如禁用服务有时候就这样做)> -D(JVM参数) > 消费者 > 提供者
            URL url = mergeUrl(providerUrl);

            // Cache key is url that does not merge with consumer side parameters,
            // regardless of how the consumer combines parameters,
            // if the server url changes, then refer again
            Invoker<T> invoker = oldUrlInvokerMap == null ? null : oldUrlInvokerMap.remove(url);
            if (invoker == null) { // Not in the cache, refer again
                try {
                    boolean enabled = true;
                    if (url.hasParameter(DISABLED_KEY)) {
                        enabled = !url.getParameter(DISABLED_KEY, false);
                    } else {
                        enabled = url.getParameter(ENABLED_KEY, true);
                    }
                    if (enabled) {
						// 消费者引用服务提供者
                        invoker = protocol.refer(serviceType, url);
                    }
                } catch (Throwable t) {

                    // Thrown by AbstractProtocol.optimizeSerialization()
                    if (t instanceof RpcException && t.getMessage().contains("serialization optimizer")) {
                        // 4-2 - serialization optimizer class initialization failed.
                        logger.error("4-2", "typo in optimizer class", "",
                            "Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);

                    } else {
                        // 4-3 - Failed to refer invoker by other reason.
                        logger.error("4-3", "", "",
                            "Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
                    }
                }
                if (invoker != null) { // Put new invoker in cache
                    newUrlInvokerMap.put(url, invoker);
                }
            } else {
                newUrlInvokerMap.put(url, invoker);
            }
        }
        return newUrlInvokerMap;
    }

到这里我们可以看到核心功能已经展示出来了

  • 指定注册中心的三个路径的子节点变更事件
  • 触发时间变更之后,把变更的节点信息转化为Invoker
    这时候得到的Invoker是最新的服务提供者地址,远程访问得到我们实际的结果

二、Protocol

根据协议创建通信连接
调用链
QosProtocolWrapper -> ProtocolFilterWrapper -> ProtocolListenerWrapper -> DubboProtocol

1、DubboProtocol.refer

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        checkDestroyed();
        return protocolBindingRefer(type, url);
    }

    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        checkDestroyed();
        optimizeSerialization(url);

        // create rpc invoker.
		// 创建通信client,封装成DubboInvoker
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }

2、DubboProtocol.getClients

创建通信客户端

  • 判断是否为共享连接,默认是共享同一个连接进行通信
  • 是否配置了多个连接通道 connections,默认只有一个共享连接
	private ExchangeClient[] getClients(URL url) {
        int connections = url.getParameter(CONNECTIONS_KEY, 0);
        // whether to share connection
        // if not configured, connection is shared, otherwise, one connection for one service
        // 如果没有配置的情况下,默认是采用共享连接,否则,就是针对一个服务提供一个连接。
		// 共享长连接,多路复用
		if (connections == 0) {
            /*
             * The xml configuration should have a higher priority than properties.
             */
            String shareConnectionsStr = StringUtils.isBlank(url.getParameter(SHARE_CONNECTIONS_KEY, (String) null))
                ? ConfigurationUtils.getProperty(url.getOrDefaultApplicationModel(), SHARE_CONNECTIONS_KEY, DEFAULT_SHARE_CONNECTIONS)
                : url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
            connections = Integer.parseInt(shareConnectionsStr);

            // 返回共享连接
			List<ReferenceCountExchangeClient> shareClients = getSharedClient(url, connections);
            ExchangeClient[] clients = new ExchangeClient[connections];
            Arrays.setAll(clients, shareClients::get);
            return clients;
        }
		// 如果不是使用共享连接,则初始化一个新的客户端连接进行返回
        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            clients[i] = initClient(url);
        }
        return clients;
    }

最后创建的共享客户端是netty客户端
org.apache.dubbo.remoting.transport.netty4.NettyTransporter,外层由HeaderExchanger包装

3、NettyTransporter.connect

    @Override
    public Client connect(URL url, ChannelHandler handler) throws RemotingException {
        return new NettyClient(url, handler);
    }

4、NettyClient

    public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
        // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
        // the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler
        super(url, wrapChannelHandler(url, handler));
    }
	public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
        // 初始化父类构造器,指定编码、超时时间等参数
		super(url, handler);
        // set default needReconnect true when channel is not connected
        needReconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, true);
		// 初始化线程池 默认为FixedThreadPool
        initExecutor(url);

        try {
			// 启动netty的核心代码初始化Bootstrap
            // 默认走的是NioSocketChannel
            // 初始化默认连接超时时间为3秒
            doOpen();
        } catch (Throwable t) {
            close();
            throw new RemotingException(url.toInetSocketAddress(), null,
                "Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
                    + " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
        }

        try {
            // connect.
            // 前面是初始化netty的客户端启动类Bootstrap 这里是执行连接的代码:bootstrap.connect(getConnectAddress());
            // 等待3秒连接失败则抛出异常
            connect();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());
            }
        } catch (RemotingException t) {
            // ** 异常处理
        } catch (Throwable t) {
            // ** 异常处理
        }
    }

RegistryProtocol.refer过程中有一个关键步骤,即在监听到服务提供者url时触发RegistryDirectory.notify()方法。
RegistryDirectory.notify()方法调用 refreshInvoker()方法将服务提供者urls转换为对应的远程invoker,最终调用到 DubboProtocol.refer()方法生成对应的 DubboInvoker。
DubboInvoker的构造方法中有一项入参 ExchangeClient[] clients,对应网络客户端 Client,通过调用 client.request()方法完成网络通信的请求发送和响应接收功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115623.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无人叉车里程计模块专题

1.无人叉车里程计模块传感器要求 2.里程计功能需求 3.无人叉车里程计模块测试用例 4.无人叉车里程计算法方案 5.源码

合理应用词云图,快速提升你的幻灯展示效果!|科研插图·24-09-08

小罗碎碎念 本期主题&#xff1a;词云图 这一期推文介绍一个非常具有实用价值的图——词云图&#xff0c;正好最近在准备开题答辩的PPT&#xff0c;顺手写一期推文&#xff0c;和大家分享一下。 R语言和Python的代码都准备了一份&#xff0c;其中Python的版本就是我用自己开始…

复制PPT模板

文章目录 环境需求步骤参考 环境 Windows 11 家庭中文版WPS Office&#xff1a;2024夏季更新 (17827) 注&#xff1a;查看WPS Office版本的方法&#xff1a; 点击左上角的“WPS Office”图标&#xff0c;然后点击右上角的三条横线&#xff0c;在弹出菜单中&#xff0c;选择“…

仕考网:公务员面试流程介绍

通知进面信息——资格审查——面试签到——抽签候考 面试形式&#xff1a; 面试分为结构化和无领导小组两种形式 1.在结构化面试中&#xff0c;当轮到某位考生时&#xff0c;引导员将在候考室宣布其编号&#xff0c;随后考生跟随引导人员前往考场入口。考生在开始考试时需回…

Origin画图——怎么才能使折线图更好看

1.这是化的原始图&#xff0c;看着也不错&#xff0c;但是总觉得缺少什么东西 2.下面就来对其进行改造&#xff0c;首先改的是坐标轴和标签。刻度的显示&#xff0c;网格线取消&#xff0c;坐标轴上轴右轴的刻度取消&#xff0c;字体的大小与style改成Arial。 3.对图层底色…

oc打包:权限弹窗无法正常弹出

在遇到编写了权限无法弹出弹窗时,需要查看是不是调用时机不对,这里直接教万能改法。 将权限获取方法编写在applicationDidBecomeActive 进入前台的生命周期接口中,如下: if (@available(iOS 14, *)) {NSLog<

基于yolov5的水稻叶病害检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv5的水稻叶病害检测系统是一种利用深度学习技术实现的高效、准确的病害识别工具。该系统采用YOLOv5算法&#xff0c;该算法以其高效的检测速度和良好的识别准确性在目标检测领域广受好评。 该系统能够实时处理水稻叶片的图像数据&#xff0c;快速准确地…

SpringBoot登录退出|苍穹外卖登录退出分析

文章目录 概要整体流程注意事项一、拦截路径二、token三、注册防止用户重复提交 苍穹外卖登录退出分析注意解决JWT退出后依然有效的问题 概要 结合Spring Boot和Vue3实现安全的用户登录和退出功能&#xff0c;并使用拦截器、JWT和Redis缓存来提高系统的安全性和性能。 整体流…

NC 合并二叉树

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 已知两颗二叉…

【魔法 / NOI】

题目 思路 动态规划&#xff1a; 状态定义&#xff1a; f [ k ] [ i ] [ j ] 对应使用了不超过 k 次魔法&#xff0c;从 i 到 j 的路径集合 f[k][i][j] 对应使用了不超过k次魔法&#xff0c;从i到j的路径集合 f[k][i][j]对应使用了不超过k次魔法&#xff0c;从i到j的路径集合 状…

驱动(RK3588S)第八课时:平台设备总线

目录 目标一、平台设备总线的概念1、什么是平台设备总线2、平台设备总线 platform 的匹配3、设备树和平台设备总线的关系&#xff0c;以及匹配 二、平台设备总线的函数接口1、注册设备端的资源信息2、设备端提供的资源的信息3、注销申请的设备端的资源4、驱动端的函数&#xff…

如何做好网络安全

随着互联网技术的飞速发展&#xff0c;网站已成为企业对外展示、交流和服务的重要窗口。然而&#xff0c;随之而来的网站安全问题也日益凸显&#xff0c;给企业的业务发展和用户数据安全带来了巨大威胁。因此&#xff0c;高度重视网站安全已成为网络安全的首要任务。今天我们就…

【Effective Java】多构造器参数使用构建器 (快速上手)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;2.1 为什么引入构建器2.2 建造者模式2.2.1书中的例子2.2.2 例子加上有效性检查 三、问题描述四、解决方案&#xff1a;…

如何对列表、字符串进行分组

如何对列表、字符串进行分组 1、效果 2、代码 使用python自带库collections中的Counter函数即可实现 代码如下: # -*- coding: utf-8 -*-""" @contact: @file: test.py @time: 2024/9/8 11:18 @author: LDC """ from collections import Co…

Vivado时序报告之Report pulse width详解

目录 一、前言 二、Report pulse width 2.1 Report pulse width 2.2 配置界面 2.3 分析结果 一、前言 在进行时序分析时&#xff0c;除了slack的分析&#xff0c;还存在pulse width的检查&#xff0c;下面将对pulse width检查进行详细说明。在report timing summary报告中…

《粮食科技与经济》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《粮食科技与经济》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。 问&#xff1a;《粮食科技与经济》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a; 湖南省粮食和物资储备局 …

C#发送正文带图片带附件的邮件

1&#xff0c;开启服务&#xff0c;获取授权码。以QQ邮箱为例&#xff1a; 点击管理服务&#xff0c;进入账号与安全页面 2&#xff0c;相关设置参数&#xff0c;以QQ邮箱为例&#xff1a; 登录时&#xff0c;请在第三方客户端的密码输入框里面填入授权码进行验证。&#xff0…

日语输入法平假名和片假名切换

在学日语输入法的时候&#xff0c;我们在使用罗马音输入的时候&#xff0c;在进行平假名和片假名切换&#xff1a; 1、使用电脑在打字&#xff0c;日语输入法切换的时候使用 Shift Alt 如果日语输入法显示为 A 需要切换为 あ的话可以按Caps Lock键 。&#xff08;相当于中文…

【LeetCode】 Z 字形变换

1. 题目 2. 分析 充分地思考问题&#xff0c;然后得出抽象解&#xff0c;最后再写代码。 本题初看是有点儿麻烦的&#xff0c;因为有个N型的变换&#xff0c;但是如果把这个N型压缩一下&#xff0c;其实就是考虑每行放什么值的问题。那么问题就简化成当前的字符串的字符需要放…

C++中的一个标准输出流——cout

目录 开头1.什么是cout?2.C中的一个标准输出流——cout的实际应用打印“Hello, world!”打印大方块打印一个变量 下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。今天&#xff0c;我们要学一下关于C中的一个标准输出流——cout的一些知识。 1.什么是cout? cou…