Dubbo source code analysis 7: Dubbo service introduction process

Insert picture description here

Introduction

Service introduction is nothing more than generating a proxy object on the client side. This proxy object helps us assemble call parameters, initiate network calls, and receive requests. When we call a method of an interface, it is like calling a local method.

Put a sketch map exported by the Dubbo service so that it won’t be dizzy in the analysis later.
Insert picture description here

Analyze service introduction according to the routine of service export. When introducing the service, you need to configure the following content, this configuration will be parsed into a ReferenceBean object

<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>
public class ReferenceBean<T> extends ReferenceConfig<T> implements 
FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
    
    

ReferenceBean implements the FactoryBean interface. When you want to get this Bean, you will call the FactoryBean#getObject method to return the Bean
ReferenceBea. When the service is injected into other classes, the introduction process will be started, and it will be introduced only when it is used. This introduction method is lazy Style .

ReferenceBean implements the InitializingBean interface, that is, Bean will call back the afterPropertiesSet method of this interface during the initialization process, and decide whether to manually call the FactoryBean#getObject method according to the configuration.

<dubbo:reference id="demoService" check="false" init="true" interface="org.apache.dubbo.demo.DemoService"/>

init=true, when the spring container starts, the service has been introduced. This method is hungry .

public void afterPropertiesSet() throws Exception {
    
    
    // 进行属性赋值
    
    // 当配置了饿汉式时,调用FactoryBean#getObject
    if (shouldInit()) {
    
    
        getObject();
    }
}

Lazy Chinese and Hungry Chinese are just different when the service is introduced. The process of introduction is to call the FactoryBean#getObject method. So we can follow the FactoryBean#getObject method

Initialization process

// ReferenceBean
// 重写了FactoryBean接口的getObject方法
public Object getObject() {
    
    
    return get();
}
// ReferenceConfig
// 生成代理类
public synchronized T get() {
    
    
    checkAndUpdateSubConfigs();

    if (destroyed) {
    
    
        throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
    }
    if (ref == null) {
    
    
        init();
    }
    return ref;
}
// ReferenceConfig
private void init() {
    
    
	// 构建参数,代码省略
    // 创建代理对象
    ref = createProxy(map);
}

The init method mainly constructs a parameter map. I will not analyze it in detail, just look at the result. Then call createProxy to create a proxy object.
Insert picture description here
When creating a proxy object, depending on the configuration method, create a proxy in the following three ways

  1. Local reference
  2. Use direct connection to reference services
  3. Reference service based on registry

When a service is referenced based on a registry, it is divided into single registry introduction and multiple registry introduction. Simply analyze the way a single registry is introduced

Single registry

When calling refprotocol#refer method, protocol=registry, so the corresponding implementation class is RegistryProtocol, enter the RegistryProtocol#refer method

Insert picture description here
The first step of this method is to set the protocol to the value of the registry in the parameters, and then delete the value of the registry in the parameters, compare the upper and lower two figures to find out, and
Insert picture description here
then call the doRefer method

// 引入
// RegistryProtocol
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    
    
    // 创建服务目录
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    // 放入生成的Protocol$Adaptive
    directory.setProtocol(protocol);
    // all attributes of REFER_KEY
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    // 构建订阅的url
    URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
    if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
    
    
        directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
        // consumer也将自己注册到注册中心
        registry.register(directory.getRegisteredConsumerUrl());
    }
    // 初始化路由规则
    directory.buildRouterChain(subscribeUrl);
    // 订阅这几个节点的变化
    // category providers configurators routers
    // RegistryProtocol会收到这几个节点的信息,触发生成DubboInvoker,即用于远程调用的Invoker
    directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
            PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
    // 从服务目录选出来一个Invoker
    // MockClusterWrapper -> FailoverCluster
    // MockClusterInvoker -> FailoverClusterInvoker
    Invoker invoker = cluster.join(directory);
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}

This method creates a RegistryDirectory object. You can first think that RegistryDirectory generates a series of Invokers based on the address of the service provider. These Invoker lists are dynamic and refresh the Invoker according to the status of the registry.

Then the consumer registers itself to the registry and subscribes to the changes of these nodes of category providers configurators routers.
When the consumer receives the information from these nodes, it will initialize the routing rules and convert the provider url to Invoker, etc.

When the protocol is dubbo, the entire execution link is as follows, and finally DubboInvoker will be generated. A detailed analysis of the process of DubboInvoker generation in the next section

Invoker invoker = cluster.join(directory);

Through the configuration of the RegistryDirectory and Cluster interface, select one of the multiple Invokers from the service provider to return. The cluster fault tolerance section will introduce Ha

// ReferenceConfig
proxyFactory.getProxy(invoker)

Then generate the proxy class of the target interface for the returned Invoker and return, and the export process is complete.

The specific implementation process of DubboInvoker

As mentioned earlier, when the service catalog subscribes to provider nodes, it will convert providerUrl to Invoker, which is an object that can initiate network calls.

The entire call link is as follows
Insert picture description here
. The process of converting from providerUrl to Invoker is described in detail in the section "Dubbo Source Code Analysis: Cluster Fault Tolerance"

// DubboProtocol#refer
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);

Directly new a DubboInvoker, the most important thing is to generate an ExchangeClient (client that can initiate network calls)

private ExchangeClient[] getClients(URL url) {
    
    
    // whether to share connection

    // 是否共享连接
    boolean useShareConnect = false;

    int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
    List<ReferenceCountExchangeClient> shareClients = null;
    // if not configured, connection is shared, otherwise, one connection for one service
    // 没有配置连接数,则默认使用一个
    if (connections == 0) {
    
    
        useShareConnect = true;

        /**
         * The xml configuration should have a higher priority than properties.
         */
        String shareConnectionsStr = url.getParameter(Constants.SHARE_CONNECTIONS_KEY, (String) null);
        // 这里默认为1
        connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(Constants.SHARE_CONNECTIONS_KEY,
                Constants.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;
}

This part is based on the configuration to decide whether to open a new connection or reuse the previous connection

Then look at the process of initializing the connection

private ExchangeClient initClient(URL url) {
    
    

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

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

    // BIO is not allowed since it has severe performance issue.
    // 检查是否有这个类型的客户端
    if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
    
    
        throw new RpcException("Unsupported client type: " + str + "," +
                " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
    }

    ExchangeClient client;
    try {
    
    
        // connection should be lazy
        // 懒加载,当真正发生请求的时候才进行连接
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
    
    
            client = new LazyConnectExchangeClient(url, requestHandler);

        } else {
    
    
            // 进行连接
            client = Exchangers.connect(url, requestHandler);
        }

    } catch (RemotingException e) {
    
    
        throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
    }

    return client;
}

It can be configured to connect directly when generating the Invoker, or connect to the server when the request is initiated.

The finally returned HeaderExchangeClient is encapsulated as NettyClient. This part of the code of the connection is very similar to the part of the service export. Draw a picture to summarize the process of service connection

Insert picture description here

// NettyClient
protected void doOpen() throws Throwable {
    
    
    // 执行业务逻辑的handler
    final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
    bootstrap = new Bootstrap();
    bootstrap.group(nioEventLoopGroup)
            .option(ChannelOption.SO_KEEPALIVE, true)
            .option(ChannelOption.TCP_NODELAY, true)
            .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
            //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
            .channel(NioSocketChannel.class);

    if (getConnectTimeout() < 3000) {
    
    
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
    } else {
    
    
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
    }

    bootstrap.handler(new ChannelInitializer() {
    
    

        @Override
        protected void initChannel(Channel ch) throws Exception {
    
    
            int heartbeatInterval = UrlUtils.getHeartbeat(getUrl());
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
            ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                    .addLast("decoder", adapter.getDecoder())
                    .addLast("encoder", adapter.getEncoder())
                    .addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
                    .addLast("handler", nettyClientHandler);
        }
    });
}

So in the next section, we have to analyze the execution process of this ChannelHandler

Welcome to follow

Insert picture description here

Reference blog

A dubbo series of articles
[1] https://juejin.im/user/465848661449693/posts
[2] https://zhuanlan.zhihu.com/p/87128564
Direct connection and registration center service reference process
[3] https:/ /developer.aliyun.com/article/723243
[4]https://aobing.blog.csdn.net/article/details/108461885

Guess you like

Origin blog.csdn.net/zzti_erlie/article/details/108015697