Thrift RPC改進—更加準確的超時管理

2022-09-08 15:01:41

前言:

之前我們組內部使用Thrift搭建了一個小型的RPC框架,具體的實現細節可以參考我之前的一篇技術文章:https://www.cnblogs.com/kaiblog/p/9507642.html

相關程式碼的下載地址:https://github.com/zhangkai253/simpleRPC

在我們的RPC框架中,通過設定socket的timeout屬性來控制超時時間,但是這個引數的設定無法保證對超時時間的準確控制,因為只要在超時時間範圍內,該socket接收到了1個位元組的資料,TimeoutException就不會被丟擲。

在我們的日常業務中,竟然會出現設定了300ms超時時間,結果耗時3秒的情況。於是,我們開始尋找更好的解決方案。

方案一:

使用執行緒池設定超時時間,該方案簡單直接,不過多進行敘述。

方案二:

修改底層的TTransport.readAll方法,在其中設定超時時間

Thrift中的TTransport負責進行資料的傳輸,其基本的類結構圖如下所示:

我們本次的改進任務主要關心如下幾個類:

我們的RPC框架底層使用了TFastFramedTransport來執行傳輸任務,這個類是TFramedTransport類的改進版本,兩個類在訊息格式上面是一致的,都是通過讀取4個位元組的訊息頭來解決拆包和粘包的問題。

只是底層buffer的使用方式上面有所不同,在讀取資料的時候TFramedTransport類使用的是TMemoryInputTransport,而TFastFramedTransport則使用的是AutoExpandingBufferReadTransport。

通過上面的流程,我們可以看到資料的傳輸主要是通過在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框架對超時時間的控制。大家可以根據自己的使用場景選擇合適的解決方案。

如果想進一步溝通和討論的小夥伴,可以加群聊或者微信進一步交流哈。