六Dubbo核心技术归纳--3Dubbo中Invoker的作用及转换

六Dubbo核心技术归纳--3Dubbo中Invoker的作用及转换

6.3 dubbo的核心模型Invoker

Dubbo中invoker是provider和consumer实现RPC调用的关键,invoker的构建过程,是Dubbo服务的init初始化过程;invoker.invoke的调用是Dubbo服务的call调用过程。因此,这里分析invoker模型,主要是provider和consumer端的初始化过程。

6.3.1 Invoker接口及主要实现类

该部分内容主要见[7.3.2.1 Invoker的接口及主要实现类](#7.3.2.1 Invoker的接口及主要实现类)

public interface Invoker<T> extends Node {
     // 服务的interface
    Class<T> getInterface();
     //调用服务
    Result invoke(Invocation invocation) throws RpcException;
}

invoke方法就是通过动态代理的机制,来调用远程服务。

dubbo中常用的Invoker类,如下:

image-20220902102149739

  • AbstractProxyInvoker 本地执行类的Invoker,实际通过Java反射的方式执行原始对象的方法。为proxyFactory.getInvoker生成的被wrapper包装过的Invoker-------InvokerWrapper。
  • AbstractInvoker: 远程通信类的Invoker,实际通过通信协议发起远程调用请求,并接收响应。
  • AbstraceClusterInvoker 多个远程通信类的Invoker聚合成的集群Invoker,加入了集群容错和负载均衡策略。cluster集群下的Invoker实现,采用模板方法设计模式,abstract定义方法框架,具体方法实现由子类实现。

6.3.2 Dubbo中Invoker的转换(todo)

Invoker是Dubbo中实体类,rpc的server端服务提供和client端服务调用,都要由invoker实现。因此,其作为一个可执行体,在server端,用于调用provider的本地服务调用;在client端,其内包含远程通信的NettyClient,用于远程调用。

provider端和consumer端,在Config配置类中,分别持有ProxyFactory和Protocol实例,通过??????(写invoke怎么从config启动初始化)

image-20230313134716908

在Dubbo框架图中标志出来,provider端和consumer端涉及到Invoker的构建、转换等过程的模块和执行流程。分析过程中,对应源码和框架调用流程图去理解。

6.3.2.1 Provider端Invoker

(这里是provider端的init过程)

image-20230313134731039

provider端的invoker,由ProxyFactory.getInvoker()创建。而ProxyFactory默认实现类是JavassistProxyFactory。

@SPI("javassist")
public interface ProxyFactory {

    /**
     * create proxy.
     * 
     * @param invoker
     * @return proxy
     */
    @Adaptive({Constants.PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker) throws RpcException;

    /**provider端创建invoker
     * create invoker.
     * 
     * @param <T>
     * @param proxy
     * @param type
     * @param url
     * @return invoker
     */
    @Adaptive({Constants.PROXY_KEY})
    <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;

}

从入口方法分析:

public class JavassistProxyFactory extends AbstractProxyFactory {

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper类不能正确处理带$的类名
//TAG1
      //创建provider端服务类的wrapper(javassist创建)
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
      //创建匿名invoker对象
//TAG2
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class<?>[] parameterTypes, 
                                      Object[] arguments) throws Throwable {
              	//invoker.doInvoke方法,会调用wrapper.invokeMethod,最终会调用包装类内目标service方法
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

getInvoker(T proxy, Class type, URL url)参数:

proxy参数为服务端service的实现类serviceImpl;type为provider提供的服务接口。

getInvoker主要做两件事:

//TAG1 Wrapper.getWrapper(Class)——Class为提供服务的service实现类。
provider端,根据服务实现类,构建服务的proxy代理类invoker。而对目标服务的调用,是对wrapper对象的调用(wrapper是javassist技术创建的包装类)

创建目标service的包装类。仅可以通过Wrapper静态方法getWrapper(Class)创建。创建过程,首先对传入Class对象解析,分别对属性、方法等信息。这个包装类,持有service服务接口的真实实现类proxy,把公共逻辑创建字节码追加append,然后javassist创建class对象,最后反射创建wrapper实例。wrapper实例方法的调用,通过wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments),通过方法名和参数类型,选择调用的方法。

// TAG2 创建匿名类AbstractProxyInvoker
实现doInvoke方法。该方法,将对服务的调用,转发给wrapper.invokeMethod。

TAG1 getWrapper()

该部分内容详见[2.2 Dubbo中Wrapper](#2.2 Dubbo中Wrapper)

上述过程,通过对传入provider服务类,重新构建字节码,通过Dubbo自己的ClassGenerator,其初始化通过获取javassist的ClassPool工具类,然后在getWrapper中,对构建的字节码转换为Class对象,然后反射创建实例对象wrapper类。(wrapper类中,把proxy目标类中所有方法,放在invokeMethod中,调用时候,if判断methodName选择方法的调用执行)。

TAG2 创建匿名AbstractProxyInvoker类

image-20230313134759660

返回主流程,创建完wrapper后,provider端会返回匿名AbstractProxyInvoker类,实现doInvoker方法,其调用wrapper,实现对服务实现类的调用逻辑。(该处实际是AOP的思路)

总结provider的invoker获取过程:

image-20230313134818874

6.3.2.2 Consumer端Invoker(todo config启动)

在consumer端,Invoker用于执行远程调用(所以consumer端invoker带有通信能力,内部传入NettyClient)。

consumer端初始化创建Invoker。

image-20230313134835869

ReferenceConfig内启动初始化

public class ReferenceConfig<T> extends AbstractReferenceConfig {

    private static final long    serialVersionUID        = -5864351140409987595L;
		
  	//静态参数,初始化时创建protocol.class的自适应类DubboProtocol
    private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
		//FailoverCluster
    private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
    //JavassistProxyFactory
    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

Protocol实现类中,常用的有DubboProtocol和RegistryProtocol。

image-20221012013906226
1 DubboProtocol.refer

dubboProtocol.refer执行中参数

image-20221012124646372

serviceType为调用服务的接口,URL以dubbo开头,后续跟了dubbo服务地址,以及服务名称DemoService,该url描述了一个dubbo协议的服务。因此,refer此处逻辑,是调用远程host处dubbo协议的demoService的服务。

public class DubboProtocol extends AbstractProtocol {

   public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
        // 创建RPC功能的invoker
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    		//将创建好的invoker加入集合
        invokers.add(invoker);
        return invoker;
    }

上述创建RPC功能的invoker。参数中,getClients(url)获取客户端实例,为ExchangeClient。

DubboProtocol
  private ExchangeClient[] getClients(URL url){
        //是否共享连接
        boolean service_share_connect = false;
  			//从URL获取连接数的参数,如果为0,表示未配置
        int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
        //如果connections不配置,则共享连接,否则每服务每连接
        if (connections == 0){
          //未配置连接数,采用共享
            service_share_connect = true;
            connections = 1;
        }
        
        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            if (service_share_connect){
              	//获取共享客户端
                clients[i] = getSharedClient(url);
            } else {
              //init新客户端
                clients[i] = initClient(url);
            }
        }
        return clients;
    }

getClients(URL)方法,根据url中参数,判定是否需要共享连接,获取客户端。

此处,跟入getSharedClient获取共享客户端方法

//DubboProtocol
private ExchangeClient getSharedClient(URL url) {
    String key = url.getAddress();
    //检查缓存
    ReferenceCountExchangeClient client = referenceClientMap.get(key);
  //1 当本地缓存中有客户端,返回
    if (client != null) {
        if (!client.isClosed()) {
          	//如果dubboProtocol的exchangeClient缓存中有address对应的客户端,则返回客户端共享,并增加引用计数(“引用计数”功能的 ExchangeClient)
            client.incrementAndGetCount();
            return client;
        } else {
          //当client关闭,从缓存移除
            referenceClientMap.remove(key);
        }
    }
		
//2 当缓存没有client,新建
    locks.putIfAbsent(key, new Object());
  //对当前address加锁,
    synchronized (locks.get(key)) {
        if (referenceClientMap.containsKey(key)) {
            return referenceClientMap.get(key);
        }

        // 创建 ExchangeClient 客户端
        ExchangeClient exchangeClient = initClient(url);
        // 将 ExchangeClient 实例传给 ReferenceCountExchangeClient,这里使用了装饰模式
        client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
    //加锁,将key-client入缓存
        referenceClientMap.put(key, client);
        ghostClientMap.remove(key);
        locks.remove(key);
        return client;
    }
}

getSharedClient(URL url)方法获取共享客户端,首先会先从DubboProtocol本地缓存获取;如果没有,加锁创建新客户端initClient,并存入缓存。继续跟入创建新客户端代码:

   private ExchangeClient initClient(URL url) {

    // 获取客户端类型,默认为 netty
    String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

    // 添加编解码和心跳包参数到 url 中
    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

    // 检测客户端类型,是否是Dubbo所支持的(transport为底层通信,目前支持Mina、Netty、Grizzly)
    if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
        throw new RpcException("Unsupported client type: ...");
    }

    ExchangeClient client;
    try {
        // 获取 lazy 配置
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
            // 创建懒加载 ExchangeClient 实例
            client = new LazyConnectExchangeClient(url, requestHandler);
        } else {
            // 创建普通 ExchangeClient 实例
            client = Exchangers.connect(url, requestHandler);
        }
    } catch (RemotingException e) {
        throw new RpcException("Fail to create remoting client for service...");
    }
    return client;
   }

initClient(URL)中,通过向url中添加parm参数,添加心跳、编解码等处理器。然后通过Exchangers.connect(url, requestHandler)创建ExchangeClient。

跟入connect连接:

public class Exchangers {

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
    throw new IllegalArgumentException("url == null");
}
if (handler == null) {
    throw new IllegalArgumentException("handler == null");
}
  //加入CODEC_KEY:exchange的url上的参数对
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
  // SPI加载,获取 Exchange 实例,默认为 HeaderExchanger
return getExchanger(url).connect(url, handler);
}
  
 public static Exchanger getExchanger(URL url) {
    String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
    return getExchanger(type);
}

public static Exchanger getExchanger(String type) {
    return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}
public class HeaderExchanger implements Exchanger {
    
  	//connect创建客户端
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    // 这里包含了多个调用,分别如下:
    // 1. 创建 HeaderExchangeHandler 对象
    // 2. 创建 DecodeHandler 对象
    // 3. 通过 Transporters 构建 Client 实例
    // 4. 创建 HeaderExchangeClient 对象
    return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);}
public class Transporters {
    
    //通过 Transporters 构建 Client 实例
    public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    ChannelHandler handler;
    if (handlers == null || handlers.length == 0) {
      //handler为channel.pipeline上事件处理器,定义了一系列inbound和outbound事件触发方法
        handler = new ChannelHandlerAdapter();
    } else if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        // 如果 handler 数量大于1,则创建一个 ChannelHandler 分发器
        handler = new ChannelHandlerDispatcher(handlers);
    }
    // 获取 Transporter 自适应拓展类,并调用 connect 方法生成 Client 实例
    return getTransporter().connect(url, handler);
    }
    
    public static Transporter getTransporter() {
        return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
    }
public class NettyTransporter implements Transporter {
    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
    // 创建 NettyClient 对象
    return new NettyClient(url, listener);
    }

DubboProtocol.refer构建invoker流程如下:

image-20230313134920598

总结:

1 consumer端,DubboProtocol.refer创建的是DubboInvoker的实例,在init初始化构建后,底层有NettyClient的客户端,具备远程通信功能;

2 ExchangeClient没有通信能力,需要对底层NettyClient层层封装;

3 DubboProtocol中,refer方法创建一个DubboInvoker实例,每次创建实例加入invokers的集合中。

2 RegistryProtocol.refer

当集群中一个服务有多个实现,且注册在集群搭建的注册中心时,对应有多个invoker,因此需要将多个同一服务的invoker逻辑合并位一个。暴露出来的就是个集群模式的invoker。

这部分provider端invoker初始化流程图,如下:

image-20230313134939639

registryProtocol类内属性

public class RegistryProtocol implements Protocol {

    private Cluster cluster;
  	//用来设置new RegistryDirectory.setProtocol属性
    private Protocol protocol;
  	//registry工厂类,用来获取registry实例(提供服务注册、订阅服务)
    private RegistryFactory registryFactory;
  	//refer方法中,用proxyFactory.getInvoker(registry,type,url)创建invoker实例
	  private ProxyFactory proxyFactory;
}

从refer跟入源码:

执行refer的参数,

image-20221012124137547

URL以zookeeper开头,后面是localhost:2181,服务名称为RegistryService,该URL描述一个zookeeper注册中心。因此,此处refer方法,是调用地址为localhost:2181的zookeeper注册中心上的type服务。

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 取出url上registry的键值对,设置为url的protocol协议头,并移除url中registry键值对
  	//设置url的协议头为zookeeper
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY,Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
 //TAG1 获取注册中心Registry实例
    Registry registry = registryFactory.getRegistry(url);
  	//如果调用服务类型type是RegistryService
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    //将url参数转为 Map
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    //获取 group 配置
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                || "*".equals(group)) {
            // 通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻辑
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    
    // 调用 doRefer,引用注册中心为zookeeperRegistry上的服务
    return doRefer(cluster, registry, type, url);
}


private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
 //TAG2 创建 RegistryDirectory 实例
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    // 设置directory的注册中心和协议
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
  
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
  
  //TAG3 构建服务消费者的URL,,如consumer://
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);

    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
 //TAG4 registry.register(subscribeUrl)向注册中心注册
      		//首先,会向consumer消费者url中添加category=consumer的键值对参数
          // 然后,registry.register注册服务消费者,在 consumers 目录下创建新节点
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
//TAG5 directory.subscribe(subscribeURL)订阅节点信息
    // 首先,会向consumer消费者URL中添加键值对category=,来订阅 providers、configurators、routers 等节点数据;
  	//然后,调用directory.subscribe订阅节点信息
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));
  
//TAG6 cluster.join(derectory)
    // 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
    Invoker invoker = cluster.join(directory);
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}

RegistryProtocol.refer(Class type, URL url),代码内主要执行了以下逻辑:

RegistryProtocol.refer(Class<T> type, URL url){
 //TAG1 获取注册中心Registry实例(将consumer注册到注册中心)
    Registry registry = registryFactory.getRegistry(url);
  
 //TAG2 创建 RegistryDirectory 实例(用于订阅注册中心下providers、configurators、routers 等节点数据)
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    // 设置directory的注册中心和协议
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
  
//TAG3 构建服务消费者的URL,,如consumer://
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
  
//TAG4 registry.register(subscribeUrl)向注册中心注册
      		//首先,会向consumer消费者url中添加category=consumer的键值对参数
          // 然后,registry.register注册服务消费者,在 consumers 目录下创建新节点
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
//TAG5 directory.subscribe(subscribeURL)订阅节点信息
    // 首先,会向consumer消费者URL中添加键值对category=,来订阅 providers、configurators、routers 等节点数据;
  	//然后,调用directory.subscribe订阅节点信息
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));
  
//TAG6 cluster.join(derectory)
    // 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
    Invoker invoker = cluster.join(directory);

	return invoker;
}

directory.subscribe中参数URL为如下,根据添加的category,订阅对应的节点下的数据。

image-20221012155929896

当前invoker底层仍是NettyClient,注册中心是集群搭建模式,一个服务对应多个invoker,需要将多个invoker逻辑合并为一个。执行cluster.join(directory),返回一个集群模式管理的invoker。

跟入cluster.join(directory)

//进入到集群创建Invoker模式
@SPI(FailoverCluster.NAME)
public interface Cluster {
  
//合并其中Directory的Invoker为一个Invoker
    @Adaptive    
  <T> Invoker<T> join(Directory<T> directory) throws RpcException;
}

cluster是一个SPI拓展点,通过ExtensionLoader拓展点加载机制,默认实现为failoverCluster。但是,dubbo的[Wrapper机制](#2.2.2 Wrapper机制),得到的是MockClusterWrapper。

image-20221012165431166

配置文件中有wrapper类,且MockeClusterWrapper实现Cluster类,且存在有参构造函数,因此,会自动注入cluster实现类FailoveCluster,得到的cluster拓展点实例为wrapper类

public class FailoverCluster implements Cluster {

    public final static String NAME = "failover";
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<T>(directory);
    }
}

//进入到MockerClusterWrapper实现类中
public class MockClusterWrapper implements Cluster {

    private Cluster cluster;

    public MockClusterWrapper(Cluster cluster) {
      //wrapper机制,此处cluster注入FailoverCluster
        this.cluster = cluster;
    }

    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    		//先调用FailoverCluster.join,返回FailoverClusterInvoker
    		//然后,返回一个mock过的invoker
        return new MockClusterInvoker<T>(directory, this.cluster.join(directory));
    }
}

集群模式下的[mock机制](#2.3.1 Mock机制),在获取到的FailoverClusterInvoker包装为MockClusterInvoker后,使得invoker可以实现服务降级等一系列服务治理中的操作。调用invoker.invoke时,dubbo内部的负载均衡机制,会从多个invoker中选择一个,进行调用。

public class MockClusterInvoker<T> implements Invoker<T>{
	
	private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class);

	private final Directory<T> directory ;
	//mock的invoker内,持有一个invoker对象
	private final Invoker<T> invoker;

public Result invoke(Invocation invocation) throws RpcException {
    Result result = null;
    
    //获取URL中的mock参数值
    String value = directory.getUrl().getMethodParameter(invocation.getMethodName(),
                             Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); 
    if (value.length() == 0 || value.equalsIgnoreCase("false")){
        //没有mock时,直接调用invoker.invoke
        result = this.invoker.invoke(invocation);
    } else if (value.startsWith("force")) {
        if (logger.isWarnEnabled()) {
            logger.info("force-mock: " + invocation.getMethodName() + 
                        " force-mock enabled , url : " +  directory.getUrl());
        }
        //force:不发起远程调用,直接降级
        result = doMockInvoke(invocation, null);
    } else {
        //fail-mock:该情况下,远程调用正常,出现异常时,降级操作
        try {
            result = this.invoker.invoke(invocation);
        }catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            } else {
                if (logger.isWarnEnabled()) {
                    logger.info("fail-mock: " + invocation.getMethodName() + 
                            " fail-mock enabled , url : " +  directory.getUrl(), e);
                }
                //远程调用失败时,降级操作
                result = doMockInvoke(invocation, e);
            }
        }
    }
    return result;
}

当注册中心为集群模式,会在FailoverInvoker包装在MockClusterInvoker中,实现mock的降级逻辑实现。分别可根据URL传来的mock参数,针对是mock=no、force、fail-mock的的情况,分别进行远程调用或者时降级处理。

总结RegistryProtocol.refer获取invoker的初始化流程图,如下:

image-20221012173058259
posted @ 2023-03-13 14:52  LeasonXue  阅读(1150)  评论(0编辑  收藏  举报