Dubbo的Consumer启动过程(通过URL生成Invoker)

在上一篇博客中,我们详解讲述了URL的生成过程,从Zookeeper订阅到Providers的url字符串,解析成URL对象,然后按照匹配规则选择合适的URL。

这篇博客主要讲解根据Provider的URL信息,连接Provider,创建Channel,生成Invoker对象,以为做RPC远程调用的客户端。

/**
 * AbstractRegistry. (SPI, Prototype, ThreadSafe)
 *
 * Registry 抽象类,实现了如下方法:
 *
 * 1、通用的注册、订阅、查询、通知等方法
 * 2、读取和持久化注册数据到文件,以 properties 格式存储
 */
public abstract class AbstractRegistry implements Registry {

     /**
     * Turn urls into invokers, and if url has been refer, will not re-reference.
     * 将每个provider映射为相应的Invoker, Invoker内包含consumer和provider的ExchangeClient,也就是连接和信息转换对象
     *
     * @param urls
     * @return invokers
     */
    private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
        ......
        if (enabled) {
             // 分发式的Invoker, 便于负载均衡 , protocol.refer(serviceType, url) refer Provider,生成Invoker
             // protocol  >> Protocol$Adaptive , 通过ExtesionLoader获取到的是 Wrapper包装,比如 ProtocolFilterWrapper
             invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
         }
    }

}

接着上段代码的:invoker = new InvokerDelegate(protocol.refer(serviceType, url), url, providerUrl);接续讲。

public class DubboProtocol extends AbstractProtocol {

    @Override
    public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
        // 初始化序列化优化器
        optimizeSerialization(url);
        // 获得远程通信客户端数组
        // 创建 DubboInvoker 对象
        // create rpc invoker.
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        // 添加到 `invokers`
        invokers.add(invoker);
        return invoker;
    }

    /**
     * 获得连接服务提供者的远程通信客户端数组
     *
     * @param url 服务提供者 URL
     * @return 远程通信客户端
     */
    private ExchangeClient[] getClients(URL url) {
        // 是否共享连接
        // whether to share connection
        boolean service_share_connect = false;
        int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
        // if not configured, connection is shared, otherwise, one connection for one service
        if (connections == 0) { // 未配置时,默认共享, 也就是一个Client和Server之间共用一个ExchangeClient
            service_share_connect = true;
            connections = 1;
        }

        // 创建连接服务提供者的 ExchangeClient 对象数组
        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            if (service_share_connect) { // 共享
                clients[i] = getSharedClient(url);
            } else { // 不共享
                clients[i] = initClient(url);
            }
        }
        return clients;
    }

    /**
     * Get shared connection
     *
     * 获得 ExchangeClient 对象。若集合中已经存在,则直接使用,无需创建。否则,创建 ExchangeClient 对象。
     * 也就是说一个Consumer连接一个Provider会共用一个ExchangeClient,
     */
    private ExchangeClient getSharedClient(URL url) {
        // 从集合中,查找 ReferenceCountExchangeClient 对象, key 是  ip:port
        String key = url.getAddress();
        ReferenceCountExchangeClient client = referenceClientMap.get(key);
        if (client != null) {
            // 若未关闭,增加指向该 Client 的数量,并返回它
            if (!client.isClosed()) {
                client.incrementAndGetCount();
                return client;
                // 若已关闭,移除
            } else {
                referenceClientMap.remove(key);
            }
        }
        // 同步,创建 ExchangeClient 对象。
        synchronized (key.intern()) {
            // 创建 ExchangeClient 对象
            ExchangeClient exchangeClient = initClient(url);
            // 将 `exchangeClient` 包装,创建 ReferenceCountExchangeClient 对象
            client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
            // 添加到集合
            referenceClientMap.put(key, client);
            // 添加到 `ghostClientMap`
            ghostClientMap.remove(key);
            return client;
        }
    }


}

对于一个Consumer和Provider之间,默认情况下只创建一个Channel,也就是说创建一个通道,他们之间的数据传输共用同一个Channel,这样能够有效减少服务器间的长连接数量,提高性能。

public class DubboProtocol extends AbstractProtocol {

    /**
     * Create new connection
     *
     * 创建 ExchangeClient 对象,"连接"服务器
     * ####################### //TODO 这一步最关键,连接URL指定的Server,创建Channell 封装成Client #######################
     *
     * @param url provider 的 url
     * @return
     */
    private ExchangeClient initClient(URL url) {
        // 校验 Client 的 Dubbo SPI 拓展是否存在
        // client type setting. 传输类型,默认值 "netty"
        String transType = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
        // BIO is not allowed since it has severe performance issue.
        if (transType != null && transType.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(transType)) {
            throw new RpcException("Unsupported client type: " + transType + "," +
                " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
        }

        // 设置编解码器为 Dubbo ,即 DubboCountCodec
        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);

        // 默认开启 heartbeat, 心跳间隔默认为 60*1000 ,也就是一分钟
        // enable heartbeat by default
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

        // 连接服务器,创建客户端
        ExchangeClient client;
        try {
            // 懒连接,创建 LazyConnectExchangeClient 对象
            // connection should be lazy
            if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
                client = new LazyConnectExchangeClient(url, requestHandler);
                // 直接连接,创建 HeaderExchangeClient 对象
            } 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;
    }

}


public class Exchangers {

    /**
     * @param url
     * @param handler {@link DubboProtocol#requestHandler}
     * @return {@link HeaderExchangeClient}, 也就是生成Consumer客户端 Client
     * @throws RemotingException
     */
    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");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        /**
         *  {@link HeaderExchanger#connect(URL, ExchangeHandler)}
         */
        return getExchanger(url).connect(url, handler);
    }

}

创建ExchangeClient的过程太过繁杂,我仅仅列出一些关键的步骤,加上代码的注释和说明:

public class NettyClient extends AbstractClient {

    /**
     * 实例化ChannelHandler,此类间接实现了ChannelHandler的所有方法,用于信息的处理
     *
     * @param url
     * @param handler
     * @throws RemotingException
     */
    public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
        super(url, wrapChannelHandler(url, handler));
    }

    protected void doOpen() {
        // 设置日志工厂
        NettyHelper.setNettyLoggerFactory();

        // 实例化 ServerBootstrap
        bootstrap = new ClientBootstrap(channelFactory);
        // 设置可选项
        // config
        // @see org.jboss.netty.channel.socket.SocketChannelConfig
        bootstrap.setOption("keepAlive", true);
        bootstrap.setOption("tcpNoDelay", true);
        bootstrap.setOption("connectTimeoutMillis", getTimeout());

        /**创建 NettyHandler 对象,其作为Netty的一个ChannelHandler,参与Channel内数据的处理
         * NettyHandler extends {@link org.jboss.netty.channel.SimpleChannelHandler} , 间接实现了 {@link ChannelUpstreamHandler},{@link ChannelDownstreamHandler}
         * ##################### 其将Netty的Channel里获取到的数据导入Dubbo体系中,至关重要 #####################
         */
        final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);

        // 设置责任链路
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            @Override
            public ChannelPipeline getPipeline() {
                // 创建 NettyCodecAdapter 对象
                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast("decoder", adapter.getDecoder());  // 解码 ChannelHandler
                pipeline.addLast("encoder", adapter.getEncoder());  // 编码 ChannelHandler
                //  ##################### 处理器  , 将NettyHandler(url,this)作为Netty的一个ChannelHandler, 用于处理数据的交互  #####################
                pipeline.addLast("handler", nettyHandler);
                return pipeline;
            }
        });
    }


}

实例化ExchangeClient时会得到一个NettyClient 对象,同时在开启Channel时,生成一个NettyHandler 对象:


final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);

@Sharable
public class NettyHandler extends SimpleChannelHandler {

    /**
     * URL
     */
    private final URL url;

    /**
     * 属性类型为:NettyClient, 当前对象的很多行为转发到 {@link NettyClient}
     * NettyClient将部分行为转发给其持有的{@link NettyClient#getChannelHandler()}
     */
    private final ChannelHandler handler;

    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        // 创建 NettyChannel 对象
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            // 添加到 `channels` 中
            if (channel != null) {
                channels.put(NetUtils.toAddressString((InetSocketAddress)ctx.getChannel().getRemoteAddress()), channel);
            }
            // 提交给 `NettyClient(superClass AbstractPeer)` 处理器。
            handler.connected(channel);
        } finally {
            // 移除 NettyChannel 对象,若已断开
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

    @Override
    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            channels.remove(NetUtils.toAddressString((InetSocketAddress)ctx.getChannel().getRemoteAddress()));
            handler.disconnected(channel);
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            handler.received(channel, e.getMessage());
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

    @Override
    public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        super.writeRequested(ctx, e);
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            handler.sent(channel, e.getMessage());
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
        try {
            handler.caught(channel, e.getCause());
        } finally {
            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
        }
    }

}

NettyHandler 实现了SimpleChannelHandler 接口,这个接口是Netty框架的一个通道处理器,实现其特定的方法,然后将此ChannelHandler嵌入Netty的ChannelPipeline 中,就能够在Netty发生ChannelEvent(连接,断开,写入,读取等)时,执行相应的逻辑。

NettyHandler将Netty的时间处理转交给了其内部持有的ChannelHandler 对象,这个对象其内部最终会将处理逻辑转交给DubboProtocol的匿名属性requestHandler :

public class DubboProtocol extends AbstractProtocol {

    /**
     * 信息转化处理器, 从Netty的接受和发送消息最终都会调用此处
     */
    private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

        @Override
        public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                Invocation inv = (Invocation)message;
                // 获得请求对应的 Invoker 对象
                Invoker<?> invoker = getInvoker(channel, inv);
                // 如果是callback 需要处理高版本调用低版本的问题
                // need to consider backward-compatibility if it's a callback
                if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
                    String methodsStr = invoker.getUrl().getParameters().get("methods");
                    boolean hasMethod = false;
                    if (methodsStr == null || !methodsStr.contains(",")) {
                        hasMethod = inv.getMethodName().equals(methodsStr);
                    } else {
                        String[] methods = methodsStr.split(",");
                        for (String method : methods) {
                            if (inv.getMethodName().equals(method)) {
                                hasMethod = true;
                                break;
                            }
                        }
                    }
                    if (!hasMethod) {
                        logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
                            + " not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl())
                            + " ,invocation is :" + inv);
                        return null;
                    }
                }
                // 设置调用方的地址
                RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
                // 执行调用
                return invoker.invoke(inv);
            }
            throw new RemotingException(channel, message.getClass().getName() + ": " + message
                + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
        }

        @Override
        public void received(Channel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                this.reply((ExchangeChannel)channel, message);
            } else {
                super.received(channel, message);
            }
        }

        @Override
        public void connected(Channel channel) {
            this.invoke(channel, Constants.ON_CONNECT_KEY);
        }

        @Override
        public void disconnected(Channel channel) throws RemotingException {
            if (logger.isInfoEnabled()) {
                logger.info("disconected from " + channel.getRemoteAddress() + ",url:" + channel.getUrl());
            }
            this.invoke(channel, Constants.ON_DISCONNECT_KEY);
        }

        /**
         * 调用方法
         *
         * @param channel 通道
         * @param methodKey 方法名
         */
        private void invoke(Channel channel, String methodKey) {
            // 创建 Invocation 对象
            Invocation invocation = createInvocation(channel, channel.getUrl(), methodKey);
            // 调用 received 方法,执行对应的方法
            if (invocation != null) {
                try {
                    this.received(channel, invocation);
                } catch (Throwable t) {
                    logger.warn("Failed to invoke event method " + invocation.getMethodName() + "(), cause: " + t.getMessage(), t);
                }
            }
        }

        private Invocation createInvocation(Channel channel, URL url, String methodKey) {
            String method = url.getParameter(methodKey);
            if (method == null || method.length() == 0) {
                return null;
            }
            RpcInvocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
            invocation.setAttachment(Constants.PATH_KEY, url.getPath());
            invocation.setAttachment(Constants.GROUP_KEY, url.getParameter(Constants.GROUP_KEY));
            invocation.setAttachment(Constants.INTERFACE_KEY, url.getParameter(Constants.INTERFACE_KEY));
            invocation.setAttachment(Constants.VERSION_KEY, url.getParameter(Constants.VERSION_KEY));
            if (url.getParameter(Constants.STUB_EVENT_KEY, false)) {
                invocation.setAttachment(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString());
            }
            return invocation;
        }
    };

}

Dubbo对DubboProtocol 里的匿名属性requestHandler 做了多次封装,使用装饰模式在每一个装饰器上都添加了额外的功能,装饰链如下:

ChannelHandler封装层级: MultiMessageHandler >> HeartbeatHandler >> AllChannelHandler >> DecodeHandler >> HeaderExchangeHandler >> requestHandler

最终生成的Invoker对象就是DubboInvoker,当然如果有多个Invoker,那么外层会封装成一个集群式的Invoker : FailoverClusterInvoker,内部持有多个Invoker组合成的目录:Directory实现对象,以便做负载均衡和集群容错。集群容错有多个策略可选择,默认是FailoverClusterInvoker。

以上就是Dubbo通过URL生成Invoker的过程,当然中间省去了很多过程,不过关键的几个步骤基本都将了,如果真的对其源码和流程很感兴趣,还是需要亲自翻翻源码的,仅靠一篇博客肯定是不够的,不过这篇博客应该对如何理解Dubbo的初始化过程有一定的帮助。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!关于Node.js的多进程启动机制和使用Dubbo时多次注册Dubbo Consumer的问题,我会尽力给您解答。 在Node.js中,多进程启动可以通过cluster模块来实现。通过cluster模块,可以将一个Node.js应用程序启动多个子进程,每个子进程独立运行,共同处理请求。这种方式可以提高应用程序的并发处理能力和稳定性。 然而,与Dubbo集成时可能会出现多次注册Dubbo Consumer的情况。Dubbo是一种分布式服务框架,它的目标是提供高性能和透明化的RPC远程服务调用能力。在使用Dubbo时,Dubbo Consumer(消费者)需要向Dubbo Provider(提供者)注册自己的信息,以便进行远程服务调用。 如果在多进程启动的Node.js应用程序中,每个子进程都独立地注册Dubbo Consumer的信息,就会导致多次注册。这可能会产生一些问题,例如重复消费相同的服务、资源浪费、调用异常等。 为了避免这种情况,您可以考虑以下解决方案之一: 1. 在主进程中注册Dubbo Consumer:在多进程启动的Node.js应用程序中,只在主进程中注册Dubbo Consumer的信息,并将其共享给所有子进程。这样可以避免重复注册。 2. 使用进程间通信(IPC):可以通过进程间通信的方式,在主进程中维护Dubbo Consumer的信息,并将其传递给子进程。子进程可以从主进程获取Consumer信息,而无需自己注册。 希望以上信息对您有所帮助!如果还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值