Registro de aprendizaje de Dubbo (18) - llamada de servicio [4] - el consumidor del servicio inicia el cliente Netty, punto de extensión del clúster

El consumidor del servicio inicia el cliente Netty

El lugar en cuestión es la exportación de servicios, durante el proceso de creación de DubboInvoker se inicia el cliente Netty, el
código específico:

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

Llame al método de referencia de DubboProtocol, que no define referir en DubboProtocol, que hereda AbstractProtocol, que define el método de referencia internamente, llama al método protocolBindingRefer, que es un método abstracto, y la subclase DubboProtocol implementa este método;

    @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. Obtenga el objeto de parámetro de conexión, si no está establecido, establezca el valor predeterminado en 0;
  2. Obtenga el parámetro shareConnections, si no está establecido, entonces shareConnectionsStr está vacío;
  3. Si shareConnectionsStr está vacío, establezca el valor predeterminado de las conexiones en 1;
  4. Llame al método getSharedClient para crear un cliente de Netty;
    /**
     * 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;
        }
    }

Llame a buildReferenceCountExchangeClientList para crear un cliente;

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

Bucle connectNum, el valor predeterminado es 1, llame a buildReferenceCountExchangeClient para crear un cliente;

DubboProtocol#buildReferenceCountExchangeClient

  1. Llame a initClient para crear un cliente;
  2. Luego envuelva la instancia del cliente como una instancia de ReferenceCountExchangeClient;
    private ReferenceCountExchangeClient buildReferenceCountExchangeClient(URL url) {
    
    
        // 生成一个ExchangeClient
        ExchangeClient exchangeClient = initClient(url);

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

DubboProtocol#initClient

  1. Obtenga parámetros como el latido del corazón, la codificación y el nombre del cliente;
  2. Llame a Exchangers.connect(url, requestHandler) para crear un cliente;
    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, controlador de solicitud)

  1. Llame al método getExchanger(url) para obtener una instancia de Exchanger, el tipo predeterminado es HeaderExchanger;
  2. Llame al método HeaderExchanger#connect para conectarse al servidor;
    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#conectar

Se devuelve una instancia de cliente ExchangeClient,
se devuelve una instancia de Cliente llamando al método Transporters.connect y se crea una instancia de HeaderExchangeClient como parámetro;

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

}

Transportistas#conectar

  1. Llame al método getTransporter, use el mecanismo SPI de forma predeterminada y obtenga el tipo de valor de Transporter predeterminado como NettyTransporter;
  2. Llame al método NettyTransporter#connect para crear un NettyClient;
    public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
    
    

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

NettyTransporter#conectar

Cree una instancia de NettyClient y regrese;

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

}

NettyCliente

Los parámetros de construcción de la clase padre se llaman;

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

ResumenCliente

  1. Llamar al constructor de la clase padre
  2. Obtener el número de conexiones de envío send.connect;
  3. Llame al método doOpen()
  4. Cree un grupo de subprocesos de consumidores;
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()));
    }

ResumenCliente#doOpen()

  1. Cree una instancia de Bootstrap para representar al cliente;
  2. Establezca los parámetros del cliente, como el tipo de conexión larga y el grupo de subprocesos de trabajo del cliente nioEventLoopGroup
  3. Establecer el controlador del procesador;
    @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);
                }
            }
        });
    }

En este punto, se crea el cliente NettyClient. Luego empaquetarlo como una instancia de HeaderExchangeServer y luego empaquetarlo como una instancia de ReferenceCountExchangeClient;
el enlace del controlador es el mismo que el controlador del proveedor de servicios, no hay diferencia;

Punto de extensión de clúster

  • Cuando haya múltiples proveedores de servicios, organice múltiples proveedores de servicios en un grupo y pretenda ser un solo proveedor.
  • En pocas palabras: el clúster corresponde a la función de tolerancia a fallas del clúster;
@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;

}

La clase de implementación del punto de extensión predeterminada es FailoverCluster;

Clase de implementación de punto de extensión de clúster

  • org.apache.dubbo.rpc.cluster.support.FailoverCluster
    cambio automático, cuando ocurre una falla, vuelva a intentarlo con otros servidores. Por lo general, se usa para operaciones de lectura, pero los reintentos pueden generar demoras más largas. El número de reintentos se puede establecer con retries="2" (excluyendo la primera vez). El valor predeterminado es 2 veces;
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
    falla rápidamente, solo se realiza una llamada y se informa un error inmediatamente después de la falla. Por lo general, se usa para operaciones de escritura no idempotentes, como agregar nuevos registros.
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
    a prueba de fallas, cuando ocurre una excepción, ignórela directamente. Normalmente se usa para operaciones como escribir en registros de auditoría.
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
    se recupera automáticamente de fallas, registra las solicitudes fallidas en segundo plano y las vuelve a enviar periódicamente. Normalmente se utiliza para operaciones de notificación de mensajes.
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
    llama a varios servidores en paralelo y regresa siempre que uno tenga éxito. Por lo general, se usa para operaciones de lectura con altos requisitos de tiempo real, pero se deben desperdiciar más recursos de servicio. El número paralelo máximo se puede establecer con 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 llama a todos los proveedores uno por uno, y si alguno informa un error, informará un error. Por lo general, se usa para notificar a todos los proveedores que actualicen la información de recursos locales, como caché o registros.

  • Ahora, en la llamada de transmisión, puede configurar la proporción de fallas de las llamadas de nodo a través de broadcast.fail.percent. Cuando se alcanza esta proporción, BroadcastClusterInvoker ya no llamará a otros nodos y generará una excepción directamente.

  • broadcast.fail.percent oscila entre 0 y 100. De forma predeterminada, se lanza una excepción cuando fallan todas las llamadas.

  • broadcast.fail.percent solo controla si continuar llamando a otros nodos después de la falla y no cambia el resultado (si algún nodo informa un error, informará un error).

  • El parámetro broadcast.fail.percent tiene efecto en dubbo2.7.10 y superior.

  • Clúster de difusión 配置 broadcast.fail.percent。

  • broadcast.fail.percent=20 significa que cuando el 20 % de los nodos no pueden llamar, se lanzará una excepción y no se llamará a ningún otro nodo.

public class BroadcastCluster implements Cluster {
    
    

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

}

Supongo que te gusta

Origin blog.csdn.net/yaoyaochengxian/article/details/124551833
Recomendado
Clasificación