在RPC调用过程中,由于网络或服务端等不可靠,我们常常会收到Timeout异常。这是因为RPC框架为避免长时间等待导致客户端资源(线程)耗尽,都会提供设置超时时间的属性。
在Dubbo中,使用timeout 这个属性来给某个服务调用设置超时间(默认1s),如果服务在设置的超时时间内未返回结果,则会抛出超时异常:TimeoutException。
接下来我们来看看,dubbo的超时机制
Dubbo架构图与调用链
相信架构图和调用链大家看见过N次了,简单介绍一下每个分层的作用
架构图
- Config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
- Proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
- Registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
- Cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
- Monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
- Protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
- Exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
- Transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
- Serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
其中,较为核心的层有:
Config 配置层、Proxy 服务代理层、Registry 注册中心层、Cluster 路由层、Protocol 远程调用层
调用链
超时设置方式
Dubbo的属性支持全局配置,方法级别,接口级别的配置(以XML为例)。
方法级设置
<!-- 提供方 -->
<dubbo:service interface="x.DemoService" ref="demoServiceImpl">
<dubbo:method name="test" timeout="10000"/>
</dubbo:service>
<!-- 消费方 -->
<dubbo:reference interface="x.DemoService" id="demoService" version="1.0">
<dubbo:method name="test" timeout="10000"/>
</dubbo:reference>
接口级设置
<!-- 提供方 -->
<dubbo:service interface="x.DemoService" ref="demoServiceImpl" timeout="10000"/>
<!-- 消费方 -->
<dubbo:reference interface="x.DemoService" id="demoService" version="1.0" timeout="10000"/>
全局级设置
<!--提供方-->
<dubbo:provider timeout="10000"/>
<!--消费方-->
<dubbo:consumer timeout="10000"/>
优先级
原则如下,使用的优先于提供的,局部的优先于全局的。
故优先级:
自定义超时时间的优先级最高
服务消费者 的优先级要高于 服务提供者
方法配置 的优先级要高于 接口配置,接口配置 的优先级要高于 全局配置。
处理机制
消费端处理超时
首先我们先来看下消费端调用链路,一次请求经过路由选择,负载均衡,Filter处理,最终到DubboInvoker 处理,如下图所示:
我们就从Protocol层的Invoker方法分析:
我们此次分析的是dubbo协议的调用,对超时处理的逻辑就在DubboInvoker的doInvoke方法中,代码如下:
-
计算超时时间, 然后向服务端发起请求,并返回future作为结果
计算Timeout时,只需读取方法上的参数即可,因为在初始化时,已经把优先级的顺序执行完成了。可能有人有疑问:context.getObjectAttachment(TIMEOUT_KEY),这行代码并没有传methodName,那是因为RPCContext是RPC调用的上下文,允许我们自定义动态设置超时时间,所以自定义设置的超时时间比方法的优先级更高。
这里要注意RpcContext 在读服务调用过程中存在串组的问题,这种方案慎用,具体参考使用Dubbo的RpcContext居然那么多坑
-
client客户端发起请求时,内部会构造一个DefaultFuture, 然后创建一个超时检查任务TimeoutCheckTask,交由HashedWheelTimer来检查超时情况。
超时则调用HashedWheelTimeout类中的expire方法
-
检测超时之后,包装Response 通过判断请求是否发送,来确认是消费端超时还是服务端超时。
以上我们可以看出,判断是客户端超时还是服务端超时,是通过是否将消息发送出去为准的。实际上这并不能区分出到底是客户端还是服务端卡住了。当客户端自己慢的时候,它很可能认为是服务端超时了。
消费端超时情况分析与解决方案
可能的原因
- 服务端压力大,客户端过来的请求远远超过服务端处理的能力,导致许多请求无法在指定的时间返回结果,客户端自动返回一个超时的异常响应来结束此次调用。
- 客户端 Load 很高,负载压力很大的时候,会因为客户端请求发不出去、响应卡在 TCP Buffer 等问题,造成超时。因为客户端接收到服务端发来的数据或者请求服务端的数据,都会在系统层面排队,如果系统负载比较高,在内核态的时间占比就会加长,从而造成客户端获取到值时已经超时。
- 通常是业务处理太慢,可在服务提供方机器上执行:jstack [PID] > jstack.log 分析线程都卡在哪个方法调用上,这里就是慢的原因。如果不能调优性能,请调高 timeout 阈值。
- GC问题,检查服务端或客户端频繁GC的原因,可通过优化JVM参数,增加资源等方式解决GC问题。
- 网络问题;
排查工具
- 借助链路跟踪的分析服务(比如阿里的 ARMS)来分析下各个点的耗时情况。
服务端处理超时
server一般不处理超时。仅在Invocation中设置了TIMEOUT_ATTACHENT_KEY属性时,才会获取Timeout值,并把超时时间设置到RpcContext上下文中,以供后续TimeoutFilter使用。
long timeout = RpcUtils.getTimeout(invocation, -1);
if (timeout != -1) {
context.set(TIME_COUNTDOWN_KEY,TimeoutCountDown.newCountDown(timeout, TimeUnit.MILLISECONDS));
}
// TIMEOUT_ATTACHENT_KEY = "_TO"
public static long getTimeout(Invocation invocation, long defaultTimeout) {
long timeout = defaultTimeout;
Object genericTimeout = invocation.getObjectAttachment(TIMEOUT_ATTACHENT_KEY);
if (genericTimeout != null) {
timeout = convertToNumber(genericTimeout, defaultTimeout);
}
return timeout;
}
在TimeoutFilter中检测超时,会将结果清空,且仅打了行warn日志,这也在侧边体现了,其实服务端的超时并不是特别重要。
服务端超时有个问题,当真的设置了超时时间,仅仅只是把结果清空了,那超时后,客户端拿到的结果也只是个空结果,但客户端并不知道是真的超时返回的空,还是数据获取到为空,且清空结果这个动作本身也挺危险的。
还有就是,服务端就算不超时,但还有网络传输时间,客户端处理时间等操作,只是设置服务端超时时间,还是不能保证服务的稳定性,所以这里还是有些问题的。
服务端端超时情况分析与解决方案
可能的原因
- 服务端逻辑处理相对耗时。
- 服务端负载请求过高,无法响应。
- 当前的超时参数设置阈值与现实情况相差较大。
排查和解决步骤
- 根据接口名称查看是否存在耗时处理情况。
- 可监控服务器状态,及服务端调用的服务调用情况。
- 尝试将超时参数调大一些,或者不设置超时时间。
动态调整超时设置
在某些情况下, 你可能有调整调用超时设置情况,但是又不想改代码发布应用。这里可以通过以下几种方式实现:
11. Dubbo Admin 设置
Dubbo 提供动态调整服务超时时间的能力,在无需重启应用的情况下调整服务的超时时间,这对于临时解决一些服务上下游依赖不稳定而导致的调用失败问题非常有效。
这里可以参见官方文档 动态调整服务超时时间
12. 增加额外的线程池
简单一点可以在Filter 中使用线程池来处理请求,通过submit 方法,通过配置中心的不同级别的超时配置,使用futrue.get(timeout) 方法来实现超时控制,这里需要你在dubbo 服务级别设置的超时时间要比 配置中心的值要大。
总结
我们从源码的角度上介绍了Dubbo的超时处理机制,感兴趣可以自己追踪调用链路。对于Dubbo中的超时设置,可以在服务端、消费端,官方建议是设置在服务端,客户端做特殊处理即可,原因是服务端更清楚接口的性能情况。不过实际情况是,它仅将该参数传导到客户端,然后由客户端来控制。
标签:DUBBO,调用,dubbo,接口,设置,超时,浅析,服务端,客户端 From: https://blog.csdn.net/qq_40023049/article/details/143480500