Dubbo learning record (18) - service call [4] - service consumer starts Netty client, Cluster extension point

The service consumer starts the Netty client

The place involved is service export. During the process of creating DubboInvoker, the Netty client is started; the
specific code:

public abstract class AbstractProtocol implements Protocol {
    
    
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    
    
        // 异步转同步Invoker , type是接口,url是服务地址
        // DubboInvoker是异步的,而AsyncToSyncInvoker会封装为同步的
        return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
    }

    protected abstract <T> Invoker<T> protocolBindingRefer(Class<T> type, URL url) throws RpcException;
}

Call the refer method of DubboProtocol. Refer is not defined in DubboProtocol. It inherits AbstractProtocol, which defines the refer method internally. Call the protocolBindingRefer method. This method is an abstract method, and the subclass DubboProtocol implements this method;

    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
    
    
        // 在DubboInvoker发送请求时会轮询clients去发送数据
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }

DubboProtocol#getClients

    private ExchangeClient[] getClients(URL url) {
    
    
        boolean useShareConnect = false;
        int connections = url.getParameter(CONNECTIONS_KEY, 0);

        List<ReferenceCountExchangeClient> shareClients = null;
        if (connections == 0) {
    
    
            useShareConnect = true;
than properties.
             */
            String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
            connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
                    DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
            shareClients = getSharedClient(url, connections);
        }
        //...省略部分代码
        return clients;
    }
  1. Get the connection parameter object, if not set, set the default value to 0;
  2. Get the shareConnections parameter, if not set, then shareConnectionsStr is empty;
  3. If shareConnectionsStr is empty, set the default value of connections to 1;
  4. Call the getSharedClient method to create a Netty client;
    /**
     * Get shared connection
     *
     * @param url
     * @param connectNum connectNum must be greater than or equal to 1
     */
    private List<ReferenceCountExchangeClient> getSharedClient(URL url, int connectNum) {
    
    
        // 这个方法返回的是可以共享的client,要么已经生成过了,要么需要重新生成
        // 对于已经生成过的client,都会存在referenceClientMap中,key为所调用的服务IP+PORT
        String key = url.getAddress();
        List<ReferenceCountExchangeClient> clients = referenceClientMap.get(key);
        // 根据当前引入的服务对应的ip+port,看看是否已经存在clients了,
        if (checkClientCanUse(clients)) {
    
    
            // 如果每个client都可用,那就对每个client的计数+1,表示这些client被引用了多少次
            batchClientRefIncr(clients);
            return clients;
        }
        locks.putIfAbsent(key, new Object());
        synchronized (locks.get(key)) {
    
    
            clients = referenceClientMap.get(key);
			//省略部分代码;
            if (CollectionUtils.isEmpty(clients)) {
    
    
                // 如果clients为空,则按指定的connectNum生成client
                clients = buildReferenceCountExchangeClientList(url, connectNum);
                referenceClientMap.put(key, clients);
            } else {
    
    
            	//省略部分代码;
            }
            return clients;
        }
    }

Call buildReferenceCountExchangeClientList to create a client;

DubboProtocol#buildReferenceCountExchangeClientList

    private List<ReferenceCountExchangeClient> buildReferenceCountExchangeClientList(URL url, int connectNum) {
    
    
        List<ReferenceCountExchangeClient> clients = new ArrayList<>();

        for (int i = 0; i < connectNum; i++) {
    
    
            clients.add(buildReferenceCountExchangeClient(url));
        }

        return clients;
    }

Loop connectNum, the default is 1, call buildReferenceCountExchangeClient to create a client;

DubboProtocol#buildReferenceCountExchangeClient

  1. Call initClient to create a client;
  2. Then wrap the client instance as a ReferenceCountExchangeClient instance;
    private ReferenceCountExchangeClient buildReferenceCountExchangeClient(URL url) {
    
    
        // 生成一个ExchangeClient
        ExchangeClient exchangeClient = initClient(url);

        // 包装成ReferenceCountExchangeClient
        return new ReferenceCountExchangeClient(exchangeClient);
    }

DubboProtocol#initClient

  1. Obtain parameters such as heartbeat, encoding, and client name;
  2. Call Exchangers.connect(url, requestHandler) to create a client;
    private ExchangeClient initClient(URL url) {
    
    

        // client type setting.
        // 拿设置的client,默认为netty
        String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT));
        // 编码方式
        url = url.addParameter(CODEC_KEY, DubboCodec.NAME);
        // enable heartbeat by default
        // 心跳, 默认60 * 1000,60秒一个心跳
        url = url.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT));
		
        ExchangeClient client;
        try {
    
    
            // connection should be lazy
            if (url.getParameter(LAZY_CONNECT_KEY, false)) {
    
    
            //设置懒加载方式, 则创建的LazyConnectExchangeClient实例
                client = new LazyConnectExchangeClient(url, requestHandler);
            } else {
    
    
                // 先建立连接,在调用方法时再基于这个连接去发送数据
                client = Exchangers.connect(url, requestHandler);  // connect
            }

        } catch (RemotingException e) {
    
    
        }

        return client;
    }

Exchangers.connect(url, requestHandler)

  1. Call the getExchanger(url) method to get an Exchanger instance, the default type is HeaderExchanger;
  2. Call the HeaderExchanger#connect method to connect to the server;
    public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    
    

        url = u	rl.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        // 得到一个HeaderExchanger去connect
        return getExchanger(url).connect(url, handler);
    }

HeaderExchanger#connect

An ExchangeClient client instance is returned;
a Client instance is returned by calling the Transporters.connect method, and a HeaderExchangeClient instance is created as a parameter;

public class HeaderExchanger implements Exchanger {
    
    

    public static final String NAME = "header";

    @Override
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    
    
        // 利用NettyTransporter去connect
        // 为什么在connect和bind时都是DecodeHandler,解码,解的是把InputStream解析成AppResponse对象
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

    @Override
    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    
    
        // 对handler包装了两层,表示当处理一个请求时,每层Handler负责不同的处理逻辑
        // 为什么在connect和bind时都是DecodeHandler,解码,解的是把InputStream解析成RpcInvocation对象
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }

}

Transporters#connect

  1. Call the getTransporter method, use the SPI mechanism by default, and get the default Transporter value type as NettyTransporter;
  2. Call the NettyTransporter#connect method to create a NettyClient;
    public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
    
    

        // NettyTransporter
        return getTransporter().connect(url, handler);
    }

NettyTransporter#connect

Create a NettyClient instance and return;

public class NettyTransporter implements Transporter {
    
    

    public static final String NAME = "netty";

    @Override
    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
    
    
        return new NettyServer(url, listener);
    }

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

}

NettyClient

The construction parameters of the parent class are called;

public class NettyClient extends AbstractClient {
    
    
    public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
    
    
    	super(url, wrapChannelHandler(url, handler));
    }
}

AbstractClient

  1. Call the constructor of the parent class
  2. Get the number of sending connections send.connect;
  3. Call the doOpen() method
  4. Create a consumer thread pool;
public abstract class AbstractClient extends AbstractEndpoint implements Client {
    
    

    protected static final String CLIENT_THREAD_POOL_NAME = "DubboClientHandler";
    private final Lock connectLock = new ReentrantLock();
    private final boolean needReconnect;
    protected volatile ExecutorService executor;

    public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
    
    
        super(url, handler);

        needReconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);

        try {
    
    
            doOpen();
        } catch (Throwable t) {
    
    
          //省略异常处理;
        } catch (Throwable t) {
    
    
        }

        // 得到消费端的线程池
        executor = (ExecutorService) ExtensionLoader.getExtensionLoader(DataStore.class)
                .getDefaultExtension().get(CONSUMER_SIDE, Integer.toString(url.getPort()));

        ExtensionLoader.getExtensionLoader(DataStore.class)
                .getDefaultExtension().remove(CONSUMER_SIDE, Integer.toString(url.getPort()));
    }

AbstractClient#doOpen()

  1. Create a Bootstrap instance to represent the client;
  2. Set the parameters of the client, such as the type of long connection, and the client worker thread pool nioEventLoopGroup
  3. Set the processor handler;
    @Override
    protected void doOpen() throws Throwable {
    
    
        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);

        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.max(3000, 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);
                String socksProxyHost = ConfigUtils.getProperty(SOCKS_PROXY_HOST);
                if(socksProxyHost != null) {
    
    
                    int socksProxyPort = Integer.parseInt(ConfigUtils.getProperty(SOCKS_PROXY_PORT, DEFAULT_SOCKS_PROXY_PORT));
                    Socks5ProxyHandler socks5ProxyHandler = new Socks5ProxyHandler(new InetSocketAddress(socksProxyHost, socksProxyPort));
                    ch.pipeline().addFirst(socks5ProxyHandler);
                }
            }
        });
    }

At this point, the client NettyClient is created. Then package it as a HeaderExchangeServer instance, and then package it as a ReferenceCountExchangeClient instance;
and the binding of the handler is the same as the handler of the service provider, there is no difference;

Cluster extension point

  • When there are multiple service providers, organize multiple service providers into a cluster and pretend to be one provider.
  • To put it simply: Cluster corresponds to the function of cluster fault tolerance;
@SPI(FailoverCluster.NAME)
public interface Cluster {
    
    

    /**
     * Merge the directory invokers to a virtual invoker.
     *
     * @param <T>
     * @param directory
     * @return cluster invoker
     * @throws RpcException
     */
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;

}

The default extension point implementation class is FailoverCluster;

Cluster extension point implementation class

  • org.apache.dubbo.rpc.cluster.support.FailoverCluster
    automatic switchover, when failure occurs, retry other servers. Usually used for read operations, but retries can introduce longer delays. The number of retries can be set by retries="2" (excluding the first time). The default is 2 times;
public class FailoverCluster implements Cluster {
    
    

    public final static String NAME = "failover";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new FailoverClusterInvoker<T>(directory);
    }

}

  • org.apache.dubbo.rpc.cluster.support.FailfastCluster
    fails quickly, only one call is made, and an error is reported immediately upon failure. Usually used for non-idempotent write operations, such as adding new records.
public class FailfastCluster implements Cluster {
    
    

    public final static String NAME = "failfast";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new FailfastClusterInvoker<T>(directory);
    }

}

  • org.apache.dubbo.rpc.cluster.support.FailsafeCluster
    failsafe, when an exception occurs, ignore it directly. Typically used for operations such as writing to audit logs.
public class FailsafeCluster implements Cluster {
    
    

    public final static String NAME = "failsafe";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new FailsafeClusterInvoker<T>(directory);
    }

}

  • org.apache.dubbo.rpc.cluster.support.FailbackCluster
    automatically recovers from failures, records failed requests in the background, and resends them periodically. Typically used for message notification operations.
public class FailbackCluster implements Cluster {
    
    

    public final static String NAME = "failback";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new FailbackClusterInvoker<T>(directory);
    }

}

  • org.apache.dubbo.rpc.cluster.support.ForkingCluster
    calls multiple servers in parallel, and returns as long as one succeeds. It is usually used for read operations with high real-time requirements, but more service resources need to be wasted. The maximum parallel number can be set by forks="2".
public class ForkingCluster implements Cluster {
    
    

    public final static String NAME = "forking";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new ForkingClusterInvoker<T>(directory);
    }

}

  • org.apache.dubbo.rpc.cluster.support.AvailableCluster

  • Broadcast calls all providers one by one, and if any one reports an error, it will report an error. Usually used to notify all providers to update local resource information such as cache or logs.

  • Now in the broadcast call, you can configure the failure ratio of node calls through broadcast.fail.percent. When this ratio is reached, BroadcastClusterInvoker will no longer call other nodes and directly throw an exception.

  • broadcast.fail.percent ranges from 0 to 100. By default, an exception is thrown when all calls fail.

  • broadcast.fail.percent only controls whether to continue to call other nodes after failure, and does not change the result (if any node reports an error, it will report an error).

  • The broadcast.fail.percent parameter takes effect in dubbo2.7.10 and above.

  • Broadcast Cluster 配置 broadcast.fail.percent。

  • broadcast.fail.percent=20 means that when 20% of the nodes fail to call, an exception will be thrown, and no other nodes will be called.

public class BroadcastCluster implements Cluster {
    
    

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new BroadcastClusterInvoker<T>(directory);
    }

}

Guess you like

Origin blog.csdn.net/yaoyaochengxian/article/details/124551833