深度解析dubbo信息交换层Exchanger

本文基于dubbo v2.6.x

1.信息交换层介绍

Exchange信息交换层介于Protocol层与Transport层之间,起着承上启下的作用,在Exchange层,封装请求响应模式,将Invocation与Result封装成Request与Response,将请求同步转成异步。以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer

2. Exchanger

我们先来看下交换层的接口Exchanger,它是个dubbo spi 扩展点,默认实现类是HeaderExchanger ,它提供了bind与connect 方法抽象,在服务暴露的时候会调用bind方法来绑定一个服务器,返回对应的ExchangeServer,在服务引用的时候通过connect方法创建客户端连接服务端,返回对应的ExchangeClient。
在这里插入图片描述
接下来我们来看下它的默认实现HeaderExchanger

3. HeaderExchanger

在这里插入图片描述
我们可以看到HeaderExchanger 在实现Exchanger 接口,重写bind与connect方法,在bind方法中创建了一个HeaderExchangeServer对象,然后在connect方法中创建了一个HeaderExchangeClient。

4. Exchangers

在解析ExchangeClient 与rExchangeServer 之前我们要先解析下这个Exchangers类,它是一个门面类,是Exchange层的统一入口,提供了一堆静态bind方法与connect方法。
在这里插入图片描述
在上层Protocol层也是通过该门面bind与cennect。
这里就解析几个常用的方法:
在这里插入图片描述
在这里插入图片描述
bind与connect 方法都是先检验参数,然后设置codec参数,缺省exchange,最后是调用getExchanger方法获取对应Exchanger来调用对应的bind或者connect方法
在这里插入图片描述
可以看出这里根据你配置的exchanger属性值来使用dubbo spi 获取Exchanger对应的实现类对象。

5. HeaderExchangeServer

在解析HeaderExchangeServer 之前我们先要看下它的继承关系
在这里插入图片描述
HeaderExchangeServer实现 ExchangerServer接口,该接口提供了两个获取ExchangeChannel的方法,这个channel可以简单理解为一个连接,可以通过channel发送与接受数据,ExchangerServer 又继承Server接口,这个Server提供了两个获取Channel的抽象,Server接口继承
Endpoint与Resetable 接口,Endpoint 可以理解为一个端,这个端可以发送接受消息。Resetable接口提供一个reset重置方法抽象。
解释了这么多上层接口的功能,那HeaderExchangeServer 主要是干了什么事情呢,除了上面那堆功能 ,还有“心跳”功能,它维护了与客户端的心跳。
我们先来看下HeaderExchangeServer 的成员。

 // 任务调度线程池
private final ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1,
        new NamedThreadFactory(
                "dubbo-remoting-server-heartbeat",
                true));
private final Server server;// server对象
// heartbeat timer
private ScheduledFuture<?> heartbeatTimer;
// heartbeat timeout (ms), default value is 0 , won't execute a heartbeat.
private int heartbeat;// 心跳
private int heartbeatTimeout;//心跳超时 
private AtomicBoolean closed = new AtomicBoolean(false);// 关闭的标识

接着再来看下构造方法实现:

 public HeaderExchangeServer(Server server) {
        if (server == null) {
            throw new IllegalArgumentException("server == null");
        }
        // 服务器
        this.server = server;
        //心跳
        this.heartbeat = server.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
        // 心跳超时
        this.heartbeatTimeout = server.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);
        if (heartbeatTimeout < heartbeat * 2) {
            throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
        }
        // 启动心跳
        startHeartbeatTimer();
 }

在构造方法中,首先检查server,从url中获取heartbeat 的参数值,缺省是0,其实这个参数在DubboProtocol中判断如果没有设置就设置缺省值为601000,也就是1分钟,接着就是获取heartbeat.timeout 心跳超时时间参数值,缺省是3heartbeat,这里也是3分钟,判断如果心跳超时时间小于 心跳间隔的2倍的就抛出异常,最后调用startHeartbeatTimer 方法启动心跳任务定时器。
接下来看下启动发送心跳任务startHeartbeatTimer方法

//启动心跳timer
private void startHeartbeatTimer() {
    stopHeartbeatTimer();
    if (heartbeat > 0) {
        heartbeatTimer = scheduled.scheduleWithFixedDelay(
                new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
                    @Override
                    public Collection<Channel> getChannels() {
                        return Collections.unmodifiableCollection(
                                HeaderExchangeServer.this.getChannels());
                    }
                }, heartbeat, heartbeatTimeout),
                heartbeat, heartbeat, TimeUnit.MILLISECONDS);
    }
}

在启动前先关闭一次,如果心跳间隔大于0 就启动,创建一个HeartBeatTask对象,往构造中塞了一个ChannelProvider 对象,用来获取Channel们,可以看到Channel 是HeaderExchangeServer 对象的getChannels方法提供的,同时还塞了心跳间隔时间与心跳超时时间。
我们来看下HeartBeatTask的run方法:

    @Override
    public void run() {
        try {
            long now = System.currentTimeMillis();
            for (Channel channel : channelProvider.getChannels()) {
                if (channel.isClosed()) {
                    continue;
                }
                try {
                    // 最后一次读
                    Long lastRead = (Long) channel.getAttribute(
                            HeaderExchangeHandler.KEY_READ_TIMESTAMP);
                    // 最后一次写
                    Long lastWrite = (Long) channel.getAttribute(
                            HeaderExchangeHandler.KEY_WRITE_TIMESTAMP);
                    // 最后一次读的时间不是null &&  最后一次读的时间距离现在不超过 心跳间隔时间
                    if ((lastRead != null && now - lastRead > heartbeat)
                            // 或者最后一次写的时间不是null && 最后一次写的时间距离现在不超过 心跳间隔时间
                            || (lastWrite != null && now - lastWrite > heartbeat)) {
                        Request req = new Request();
                        req.setVersion(Version.getProtocolVersion());
                        req.setTwoWay(true);
                        req.setEvent(Request.HEARTBEAT_EVENT);// 设置心跳事件
                        channel.send(req);//发送出去
                        if (logger.isDebugEnabled()) {
                            logger.debug("Send heartbeat to remote channel " + channel.getRemoteAddress()
                                    + ", cause: The channel has no data-transmission exceeds a heartbeat period: " + heartbeat + "ms");
                        }
                    }
                    // 最后读的时间,超过心跳超时时间,就会进行重新连接
                    if (lastRead != null && now - lastRead > heartbeatTimeout) {
                        logger.warn("Close channel " + channel
                                + ", because heartbeat read idle time out: " + heartbeatTimeout + "ms");
                        if (channel instanceof Client) {// 是客户端直接重新连接
                            try {
                                ((Client) channel).reconnect();
                            } catch (Exception e) {
                                //do nothing
                            }
                        } else {
                            channel.close(); // 服务端断开连接
                        }
                    }
                } catch (Throwable t) {
                    logger.warn("Exception when heartbeat to remote channel " + channel.getRemoteAddress(), t);
                }
            }
        } catch (Throwable t) {
            logger.warn("Unhandled exception when heartbeat, cause: " + t.getMessage(), t);
        }
    }

在发送心跳的run方法中,遍历该server的所有channel,然后判断 channel 上读取时间戳值与写出时间戳值(这两个值会在 连接,断开连接 ,发送消息,接收消息的时候被塞到对应channel里),主要是判断这两个时间戳有没有与当前时间戳差值有没有超过心跳超时,如果没有就封装一个心跳Request,setTwoWay 这个是 需要回复的意思,然后将这个心跳请求发送出去。如果读时间戳与现在时间差值大于了心跳超时,然后如果是客户端的话就进行重连,如果是服务端的话断开该channel的连接,这里可以这么理解,就是我发出去了心跳,然后迟迟没有收到回复(这个回复 可以是心跳的回复,也可以是调用,响应都行),然后超出了这个超时时间,这个channel是客户端就重连接,是服务端就断开,为啥服务端不重连接呢?客户端没有监听具体的端口,就算有服务端重连接就成客户端了,所以只能断开连接,客户端在发送消息的时候,发现连接断开了,就会自己重连接了。
我们再来看下其他方法实现:

@Override
public void send(Object message, boolean sent) throws RemotingException {
   if (closed.get()) {
       throw new RemotingException(this.getLocalAddress(), null, "Failed to send message " + message + ", cause: The server " + getLocalAddress() + " is closed!");
   }
   server.send(message, sent);
}
 @Override
public void send(Object message) throws RemotingException {
    if (closed.get()) {
        throw new RemotingException(this.getLocalAddress(), null, "Failed to send message " + message + ", cause: The server " + getLocalAddress() + " is closed!");
    }
    server.send(message);
}

这两个send方法都是判断是否关闭,然后调用server的send方法进行消息发送。

@Override
public Collection<ExchangeChannel> getExchangeChannels() {
    Collection<ExchangeChannel> exchangeChannels = new ArrayList<ExchangeChannel>();
    Collection<Channel> channels = server.getChannels();
    if (channels != null && !channels.isEmpty()) {
        for (Channel channel : channels) {
            exchangeChannels.add(HeaderExchangeChannel.getOrAddChannel(channel));
        }
    }
    return exchangeChannels;
}

getExchangeChannels 方法会循环遍历channel,然后使用HeaderExchangeChannel 的方法进行包装channel,将channel包装成ExchangeChannel,下面的那个获取根据远程地址获取ExchangeChannel 也是先获取channel,然后再使用HeaderExchangeChannel进行包装。

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Collection<Channel> getChannels() {
    return (Collection) getExchangeChannels();
}
@Override
public Channel getChannel(InetSocketAddress remoteAddress) {
    return getExchangeChannel(remoteAddress);
}

getChannels与getChannel 分别调用的getExchangeChannels 和getExchangeChannel 方法,然后返回的是ExchangeChannel。
reset重置方法实现:

@Override
public void reset(URL url) {//重置心跳的配置,心跳参数发生变化,重启心跳定时器
    server.reset(url);
    try {
        if (url.hasParameter(Constants.HEARTBEAT_KEY)
                || url.hasParameter(Constants.HEARTBEAT_TIMEOUT_KEY)) {
            int h = url.getParameter(Constants.HEARTBEAT_KEY, heartbeat);
            int t = url.getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, h * 3);
            if (t < h * 2) {
                throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
            }
            if (h != heartbeat || t != heartbeatTimeout) {
                heartbeat = h;
                heartbeatTimeout = t;
                startHeartbeatTimer();
            }
        }
    } catch (Throwable t) {
        logger.error(t.getMessage(), t);
    }
}

可以reset方法就是重启的心跳定时器。
接下来看下close关闭方法。

@Override
public void close() {
    doClose();
    server.close();
}

这个无参关闭方法,先是调用doClose方法,然后再调用server的close方法,关闭server。

private void doClose() {// 关闭心跳定时器
    if (!closed.compareAndSet(false, true)) {
        return;
    }
    stopHeartbeatTimer();
    try {
        scheduled.shutdown();
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    }
}

doClose中,先是设置关闭状态,然后调用stopHeartbeatTimer方法关闭定时器,最后是关闭定时调用线程池。

private void stopHeartbeatTimer() {
    try {
        ScheduledFuture<?> timer = heartbeatTimer;
        if (timer != null && !timer.isCancelled()) {
            timer.cancel(true);
        }
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    } finally {
        heartbeatTimer = null;
    }
}

在关闭心跳任务定时器stopHeartbeatTimer方法中,如果没有关闭,就调用定时器的cancel方法进行关闭。
再来看下它的优雅关闭(在一定时间内关闭即可)

@Override
public void close(final int timeout) {
    startClose();
    if (timeout > 0) {
        final long max = (long) timeout;
        final long start = System.currentTimeMillis();
        if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
            sendChannelReadOnlyEvent();
        }
        while (HeaderExchangeServer.this.isRunning()
                && System.currentTimeMillis() - start < max) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                logger.warn(e.getMessage(), e);
            }
        }
    }
    doClose();
    server.close(timeout);
}

这里就是在时间范围内向channel发送只读消息。先是判断channel.readonly.send的属性值,缺省是true,然后调用sendChannelReadOnlyEvent 方法向所有channel发送只读消息,这个时间过了之后,才会调用doClose方法,与server的close(time)方法。
最后再来看下sendChannelReadOnlyEvent方法的实现:

private void sendChannelReadOnlyEvent() {
    //封装request请求
    Request request = new Request();
    request.setEvent(Request.READONLY_EVENT);
    request.setTwoWay(false);
    request.setVersion(Version.getProtocolVersion());
    // 循环发送给所有客户端
    Collection<Channel> channels = getChannels();
    for (Channel channel : channels) {
        try {
            if (channel.isConnected())
                channel.send(request, getUrl().getParameter(Constants.CHANNEL_READONLYEVENT_SENT_KEY, true));
        } catch (RemotingException e) {
            logger.warn("send cannot write message error.", e);
        }
    }
}

这里就是封装一个只读请求Request,设置setTwoWay 是false,也就是不需要对端回复,最后获取所有的channel,遍历如果没有关闭就调用send方法发送。

6.HeaderExchangeClient

在解析HeaderExchangeClient前我们也是看下它的继承关系图
在这里插入图片描述
可以看出来HeaderExchangeClient 实现ExchangeClient 接口,该接口没啥东西,就是概念上的抽象,ExchangeClient继承Client, ExchangeChannel,Client接口提供了重连,重置的抽象,ExchangeChannel 提供了request抽象,在往上看Channel提供了属性的操作,然后Endpoint就是发送,关闭的功能,Resetable 提供了reset重置的抽象。
HeaderExchangeClient 类提供了客户端心跳发送。

 // 任务调度线程池
private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("dubbo-remoting-client-heartbeat", true));
// client
private final Client client;
// channel连接
private final ExchangeChannel channel;
// heartbeat timer  心跳定时器
private ScheduledFuture<?> heartbeatTimer;
// heartbeat(ms), default value is 0 , won't execute a heartbeat.
private int heartbeat; // 是否心跳
private int heartbeatTimeout; // 心跳间隔时间

接着来看下HeaderExchangeClient的构造,第一个参数是client对象,第二个参数是是否启动心跳

public HeaderExchangeClient(Client client, boolean needHeartbeat) {
      // 判断client
      if (client == null) {
          throw new IllegalArgumentException("client == null");
      }
      this.client = client;
      // channel
      this.channel = new HeaderExchangeChannel(client);
      // dubbo版本
      String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY);
      //获取心跳  这个前面已经设置了1m ,可以直接取出来
      this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith("1.0.") ? Constants.DEFAULT_HEARTBEAT : 0);
      //心跳超时时间
      this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);

      if (heartbeatTimeout < heartbeat * 2) {// 避免间隔太短
          throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
      }

      if (needHeartbeat) {// 是否启动心跳
          startHeartbeatTimer();//启动心跳
      }
  }

在构造中先是检查client,然后将client封装成HeaderExchangeChannel ,接着就是处理心跳间隔,与心跳超时参数了,这里与HeaderExchangeServer一样,这里就不赘述了,如果需要启动心跳,就调用startHeartbeatTimer 方法启动心跳,这里也是一样的,也不赘述了,不一样的是它这channel就一个,就是那个客户端连接。

@Override
public void send(Object message) throws RemotingException {
    channel.send(message);
}
@Override
public void send(Object message, boolean sent) throws RemotingException {
    channel.send(message, sent);
}

这里send方法都是调用channel的send进行发送的。

/**
 * 发起请求
 * @param request 消息 invocation
 * @param timeout  超时时间
 * @return
 * @throws RemotingException
 */
@Override
public ResponseFuture request(Object request, int timeout) throws RemotingException {
    return channel.request(request, timeout);
}
@Override
public ResponseFuture request(Object request) throws RemotingException {
    return channel.request(request);
}

request 方法也是调用channel的request方法,然后返回ResponseFuture 。关于这个HeaderExchangeChannel我们后面的文章会有讲解。

@Override
public void close() {
   doClose();
   channel.close();
}
@Override
public void close(int timeout) {
   // Mark the client into the closure process
   startClose();
   doClose();
   channel.close(timeout);
}
@Override
public void startClose() {
   channel.startClose();
}
private void doClose() {
   stopHeartbeatTimer();
}

客户端的关闭没有像服务端那么复杂,调用doClose将心跳任务定时器关闭,然后调用channel的close方法将channel关闭。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

$码出未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值