《Dubbo进阶二》——RPC协议之网络传输原理

28 篇文章 1 订阅
23 篇文章 0 订阅

一 RPC协议的网络传输

一个RPC协议又通信模块、报文编码和序列化三个模块组成,其中通信模块实现了RPC的网络传输。网络传输的稳定性和性能直接影响RPC服务的稳定性和性能。
在这里插入图片描述
网络传输框架组成
在这里插入图片描述

  • io模型
    (1)BIO:同步阻塞
    (2)NIO:同步非阻塞(Netty)
    (3)AIO:异步非阻塞
  • 连接方式
    (1)长连接(Netty)
    (2)短链接
  • 线程分类(每个线程都有配有线程池)
    (1)IO线程
    (2)服务端业务线程
    (3)客户端调度线程
    (4)客户端结果exchange线程
    (5)心跳保活线程
    (6)重连线程
  • 线程池模型
    (1)fix:固定线程池,此线程池启动时即创建固定大小的线程数,不做任何伸缩
    (2)cached:缓存线程池,此线程池可伸缩,线程空闲一分钟后回收,新请求重新创建线程
    (3)Limited:有限线程池,此线程池一直增长,直到上限,增长后不收缩。

二 dubbo传输模块的实现

(PS:本文只探讨dubbo协议的默认传输服务netty)
在创建连接之前,在客户端必须先创建出Client、服务端必须先创建出Server用于连接,而创建Client和Server是由DubboProtocol.java调度的
在这里插入图片描述
服务端:
export()方法进行服务暴露,又调用了openServer()找到对应的Server,如果Server还没创建会调用createServer()创建Server
调用链会传递url参数(该参数就是在配置文件配置的ip端口服务名称等),通过url产生唯一的Server,代表一个服务。

	public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter(invoker, key, this.exporterMap);
        this.exporterMap.put(key, exporter);
        Boolean isStubSupportEvent = url.getParameter("dubbo.stub.event", false);
        Boolean isCallbackservice = url.getParameter("is_callback_service", false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter("dubbo.stub.event.methods");
            if (stubServiceMethods != null && stubServiceMethods.length() != 0) {
                this.stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            } else if (this.logger.isWarnEnabled()) {
                this.logger.warn(new IllegalStateException("consumer [" + url.getParameter("interface") + "], has set stubproxy support event ,but no stub methods founded."));
            }
        }

        this.openServer(url);
        return exporter;
    }

    private void openServer(URL url) {
        String key = url.getAddress();
        boolean isServer = url.getParameter("isserver", true);
        if (isServer) {
            ExchangeServer server = (ExchangeServer)this.serverMap.get(key);
            if (server == null) {
                this.serverMap.put(key, this.createServer(url));
            } else {
                server.reset(url);
            }
        }

    }

    private ExchangeServer createServer(URL url) {
        url = url.addParameterIfAbsent("channel.readonly.sent", Boolean.TRUE.toString());
        url = url.addParameterIfAbsent("heartbeat", String.valueOf(60000));
        String str = url.getParameter("server", "netty");
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        } else {
            url = url.addParameter("codec", "dubbo");

            ExchangeServer server;
            try {
                server = Exchangers.bind(url, this.requestHandler);
            } catch (RemotingException var5) {
                throw new RpcException("Fail to start server(url: " + url + ") " + var5.getMessage(), var5);
            }

            str = url.getParameter("client");
            if (str != null && str.length() > 0) {
                Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
                if (!supportedTypes.contains(str)) {
                    throw new RpcException("Unsupported client type: " + str);
                }
            }

            return server;
        }
    }

客户端:
refer()方法找到服务,调用getClients()找Client,getClients()会根据配置文件的配置决定是调用getSharedClient()得到共享Client还是调用initClient()创建新的Client。(在配置文件中的dubbo:reference标签中,若配置了connections则走的独立连接即initClient(),没有配置connections则走共享连接,即getSharedClient()

    public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
        DubboInvoker<T> invoker = new DubboInvoker(serviceType, url, this.getClients(url), this.invokers);
        this.invokers.add(invoker);
        return invoker;
    }

    private ExchangeClient[] getClients(URL url) {
        boolean service_share_connect = false;
        int connections = url.getParameter("connections", 0);
        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] = this.getSharedClient(url);
            } else {
                clients[i] = this.initClient(url);
            }
        }

        return clients;
    }

    private ExchangeClient getSharedClient(URL url) {
        String key = url.getAddress();
        ReferenceCountExchangeClient client = (ReferenceCountExchangeClient)this.referenceClientMap.get(key);
        if (client != null) {
            if (!client.isClosed()) {
                client.incrementAndGetCount();
                return client;
            }

            this.referenceClientMap.remove(key);
        }

        synchronized(key.intern()) {
            ExchangeClient exchangeClient = this.initClient(url);
            client = new ReferenceCountExchangeClient(exchangeClient, this.ghostClientMap);
            this.referenceClientMap.put(key, client);
            this.ghostClientMap.remove(key);
            return client;
        }
    }

    private ExchangeClient initClient(URL url) {
        String str = url.getParameter("client", url.getParameter("server", "netty"));
        String version = url.getParameter("dubbo");
        boolean var10000;
        if (version != null && version.startsWith("1.0.")) {
            var10000 = true;
        } else {
            var10000 = false;
        }

        url = url.addParameter("codec", "dubbo");
        url = url.addParameterIfAbsent("heartbeat", String.valueOf(60000));
        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(), " "));
        } else {
            try {
                Object client;
                if (url.getParameter("lazy", false)) {
                    client = new LazyConnectExchangeClient(url, this.requestHandler);
                } else {
                    client = Exchangers.connect(url, this.requestHandler);
                }

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

dubbo传输类图
在这里插入图片描述
当服务端需要创建Server、客户端需要创建Client时,涉及到创建连接的最底层接口com\alibaba\dubbo\remoting\Transporter.java

@SPI("netty")
public interface Transporter {
    @Adaptive({"server", "transporter"})
    Server bind(URL var1, ChannelHandler var2) throws RemotingException;

    @Adaptive({"client", "transporter"})
    Client connect(URL var1, ChannelHandler var2) throws RemotingException;
}

Server bind()方法是对Server和ip端口进行绑定
Client connect()方法是把所需服务和Clinent对应起来
实际由netty实现类执行

public class NettyTransporter implements Transporter {
    public static final String NAME = "netty";

    public NettyTransporter() {
    }

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

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

连接数

服务端产生多少个Server连接以服务个数和协议个数的乘积确定;
而客户端产生多少个Client连接有点复杂,影响参数有客户端reference标签有无配置connections和服务端配置了多少个协议。

  • 客户端没有配置connections
    使用共享连接,以ip+端口确定一个连接

    • 服务端单个协议
      例如服务端配置了dubbo协议(一个protocol标签),有两个服务(两个service),则此时服务端产生两个Server连接服务(1*2),客户端配置了两个引用(两个reference)且没有配置connections,则只有一个Client连接,因为不管服务端有多少个服务,都能以一个IP+端口确定,客户端尽管有两个引用(N个也一样)但是使用共享连接是同一个连接,所以产生一个Client连接。
    • 服务端多个协议
      例如服务端配置了dubbo协议(第一个protocol标签),有配置了rmi第二个protocol标签),协议有两个服务(两个service),则此时服务端产生四个Server连接服务(2*2),客户端配置了两个引用(两个reference)且没有配置connections,则只有两个Client连接,因为此时服务端有两个协议产生两个不同IP+端口,客户端两个连接,分别对应不同协议的不同IP端口,所以产生两个Client连接。
  • 客户端配置connections=“2”

    • 服务端单个协议
      例如服务端配置了dubbo协议(一个protocol标签),有两个服务(两个service),则此时服务端产生两个Server连接服务(1*2),客户端配置了两个引用(两个reference)且一个配置了connections=“2”,另一个没有配置connections,则没有配置connects的引用使用共享连接产生一个Client连接,配置connections=“2”使用独立连接,且连接数为2,则产生两个Client连接,所以一共产生三个Client连接。
    • 服务端多个协议
      例如服务端配置了dubbo协议(第一个protocol标签),有配置了rmi第二个protocol标签),协议有两个服务(两个service),则此时服务端产生四个Server连接服务(2*2),客户端配置了两个引用(两个reference)且一个配置了connections=“2”,另一个没有配置connections,则没有配置connects的引用使用共享连接产生两个Client连接(因为服务端双协议),配置connections=“2”使用独立连接,且连接数为2,则产生四个Client连接(因为服务端双协议),所以一共产生六个Client连接。

简而言之,每增加一个协议,连接数翻倍。

长连接生命周期

初始连接:
引用服务|增加提供者–>获取连接–>是否获取共享连接–>创建连接客户端–>开启心跳检测状态检查定时任务–>开启连接状态检测
DubboProtocol.getClients()

建立Client连接之后,会创建出心跳检测和两个线程对改连接进行维护

心跳发送:
在创建一个连接客户端同时也会创建一个心跳客户端。
在com\alibaba\dubbo\remoting\exchange\support\header\HeaderExchangeClient.java调度。

private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("dubbo-remoting-client-heartbeat", true));

这里ScheduledThreadPoolExecutor是缓存线程池,核心线程数为两个,这两个不会回收,超出的用完马上回收,无上限(其实有,但数字超大,不可能达到)。

public HeaderExchangeClient(Client client, boolean needHeartbeat) {
        if (client == null) {
            throw new IllegalArgumentException("client == null");
        } else {
            this.client = client;
            this.channel = new HeaderExchangeChannel(client);
            String dubbo = client.getUrl().getParameter("dubbo");
            this.heartbeat = client.getUrl().getParameter("heartbeat", dubbo != null && dubbo.startsWith("1.0.") ? '\uea60' : 0);
            this.heartbeatTimeout = client.getUrl().getParameter("heartbeat.timeout", this.heartbeat * 3);
            if (this.heartbeatTimeout < this.heartbeat * 2) {
                throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
            } else {
                if (needHeartbeat) {
                    this.startHeatbeatTimer();
                }

            }
        }
    }

HeaderExchangeClient()(doOpen()调用)这个方法中就可以通过 heartbeat 设置心跳连接时间
,默认基于60秒发送一次心跳来保持连接的存活。
最后调用startHeatbeatTimer()方法开启心跳检测

private void startHeatbeatTimer() {
        this.stopHeartbeatTimer();
        if (this.heartbeat > 0) {
            this.heatbeatTimer = scheduled.scheduleWithFixedDelay(new HeartBeatTask(new ChannelProvider() {
                public Collection<Channel> getChannels() {
                    return Collections.singletonList(HeaderExchangeClient.this);
                }
            }, this.heartbeat, this.heartbeatTimeout), (long)this.heartbeat, (long)this.heartbeat, TimeUnit.MILLISECONDS);
        }

    }

这个方法有调用了HeartBeatTask的run()方法进行检测,具体检测的是连接管道是否可用。
断线重连:
每创建一个客户端连接都会启动一个定时任务每两秒中检测一次当前连接状态,如果断线则自动重连。
com\alibaba\dubbo\remoting\transport\AbstractClient.class(NettyClient是其实现类)

private static final ScheduledThreadPoolExecutor reconnectExecutorService = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("DubboClientReconnectTimer", true));

reconnectExecutorService就是断线重连的缓存线程池。

private synchronized void initConnectStatusCheckCommand() {
        int reconnect = getReconnectParam(this.getUrl());
        if (reconnect > 0 && (this.reconnectExecutorFuture == null || this.reconnectExecutorFuture.isCancelled())) {
            Runnable connectStatusCheckCommand = new Runnable() {
                public void run() {
                    try {
                        if (!AbstractClient.this.isConnected()) {
                            AbstractClient.this.connect();
                        } else {
                            AbstractClient.this.lastConnectedTime = System.currentTimeMillis();
                        }
                    } catch (Throwable var3) {
                        String errorMsg = "client reconnect to " + AbstractClient.this.getUrl().getAddress() + " find error . url: " + AbstractClient.this.getUrl();
                        if (System.currentTimeMillis() - AbstractClient.this.lastConnectedTime > AbstractClient.this.shutdown_timeout && !AbstractClient.this.reconnect_error_log_flag.get()) {
                            AbstractClient.this.reconnect_error_log_flag.set(true);
                            AbstractClient.logger.error(errorMsg, var3);
                            return;
                        }

                        if (AbstractClient.this.reconnect_count.getAndIncrement() % AbstractClient.this.reconnect_warning_period == 0) {
                            AbstractClient.logger.warn(errorMsg, var3);
                        }
                    }

                }
            };
            this.reconnectExecutorFuture = reconnectExecutorService.scheduleWithFixedDelay(connectStatusCheckCommand, (long)reconnect, (long)reconnect, TimeUnit.MILLISECONDS);
        }

    }

if (!AbstractClient.this.isConnected())判断断连之后开始两秒一次重连AbstractClient.this.connect()
连接销毁:
基于注册中心通知,服务端断开后销毁
在com\alibaba\dubbo\remoting\transport\AbstractClient.class的destroyConnectStatusCheckCommand()(有close()方法调用)

private synchronized void destroyConnectStatusCheckCommand() {
        try {
            if (this.reconnectExecutorFuture != null && !this.reconnectExecutorFuture.isDone()) {
                this.reconnectExecutorFuture.cancel(true);
                reconnectExecutorService.purge();
            }
        } catch (Throwable var2) {
            logger.warn(var2.getMessage(), var2);
        }

    }

销毁并没有销毁线程池中的两个核心线程,而是将连接移除线程池。

三 通信线程协作流程

客户端线程

  • 客户端调度线程:发起远程进程调用的线程,在这个线程中完成对request的编码和序列化,然后交给IO线程,进入等待状态知道结果解析完返回
  • 客户端IO线程:由传输框架实现,发送request消息流,接受response消息流,解码反序列化response,提交response至结果转换线程
  • 客户端结果Exchange线程:转换response,设置结果到ResponseFuture,唤醒调度线程移交response结果
    在这里插入图片描述

服务端线程

  • 服务端IO线程:由传输框架实现,用于request消息流读取,解码反序列化request,然后调用业务线程;response编码与发送
  • 业务执行线程:服务端具体执行业务方法的线程,进行response编码序列化,回写结果至channel
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值