前言:

之前我们组内部使用Thrift搭建了一个小型的RPC框架,具体的实现细节可以参考我之前的一篇技术文章:

相关代码的下载地址:https://github.com/zhangkai253/simpleRPC

在我们的RPC框架中,通过设置socket的timeout属性来控制超时时间,但是这个参数的设置无法保证对超时时间的准确控制,因为只要在超时时间范围内,该socket接收到了1个字节的数据,TimeoutException就不会被抛出。

在我们的日常业务中,竟然会出现设置了300ms超时时间,结果耗时3秒的情况。于是,我们开始寻找更好的解决方案。

方案一:

使用线程池设置超时时间,该方案简单直接,不过多进行叙述。

方案二:

修改底层的TTransport.readAll方法,在其中设置超时时间

Thrift中的TTransport负责进行数据的传输,其基本的类结构图如下所示:

rpc 长连接 每次发送消息可以设置超时时间吗 rpc time out_Thrift

我们本次的改进任务主要关心如下几个类:

rpc 长连接 每次发送消息可以设置超时时间吗 rpc time out_RPC_02

我们的RPC框架底层使用了TFastFramedTransport来执行传输任务,这个类是TFramedTransport类的改进版本,两个类在消息格式上面是一致的,都是通过读取4个字节的消息头来解决拆包和粘包的问题。

只是底层buffer的使用方式上面有所不同,在读取数据的时候TFramedTransport类使用的是TMemoryInputTransport,而TFastFramedTransport则使用的是AutoExpandingBufferReadTransport。

rpc 长连接 每次发送消息可以设置超时时间吗 rpc time out_数据_03

通过上面的流程,我们可以看到数据的传输主要是通过在Tsocket中重写TTransport的readAll方法,可以有效地控制超时时间。

方案三:

使用TAsyncClient来替代现在的client,利用TAsyncClientManager来进行超时时间管理,下面我们看看TAsyncClientManager是如何管理请求的。这个问题的答案,我们可以直接从相关的源码中获得,

public void run() {
      while (running) {
        try {
          try {
              // 当Thrift请求发送的时候,如果请求设置了超时时间,则会被放到
              // timeoutWatchSet中
            if (timeoutWatchSet.size() == 0) {
              // 如果没有设置了超时时间的请求,则无限期低等待下去
              selector.select();
            } else {
              // 从timeoutWatchSet中取出距离当前最近的过期时间
              long nextTimeout = timeoutWatchSet.first().getTimeoutTimestamp();
              long selectTime = nextTimeout - System.currentTimeMillis();
              if (selectTime > 0) {
                // 如果过期时间大于当前时间,则等待剩余时间
                selector.select(selectTime);
              } else {
                // 如果过期时间小于等于当前时间,则立即返回
                selector.selectNow();
              }
            }
          } catch (IOException e) {
            LOGGER.error("Caught IOException in TAsyncClientManager!", e);
          }
          transitionMethods();
          timeoutMethods();
          startPendingMethods();
        } catch (Exception exception) {
          LOGGER.error("Ignoring uncaught exception in SelectThread", exception);
        }
      }


      try {
        selector.close();
      } catch (IOException ex) {
        LOGGER.warn("Could not close selector. This may result in leaked resources!", ex);
      }
    }

上述三种方案都可以很好地实现RPC框架对超时时间的控制。大家可以根据自己的使用场景选择合适的解决方案。

更新补充:

最后给大家留一个小问题吧,在第3种方案中我们使用了异步的方案来实现对超时时间的控制,我们如何阻塞等待异步请求的结果呢?

如果想进一步沟通和讨论的小伙伴,可以加群聊或者微信进一步交流哈。