Thrift RPC改进—更加准确的超时管理

2022-10-15,,,,

前言:

之前我们组内部使用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框架对超时时间的控制。大家可以根据自己的使用场景选择合适的解决方案。

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

         

Thrift RPC改进—更加准确的超时管理的相关教程结束。

《Thrift RPC改进—更加准确的超时管理.doc》

下载本文的Word格式文档,以方便收藏与打印。