English | 中文
在发起 RPC 请求的时候,框架会根据超时时间限制等待回包的时间。如果超过设定时间,客户端调用会立刻返回超时失败。
超时时间被细分为3个配置,以更细粒度的方式提供超时控制:
链路超时
:上游调用方通过协议字段把自己允许的超时时间传给当前服务,意思是说我只给你这么多的超时时间,请在该超时时间内务必给我返回数据,超过这个时间返回都是没有意义的,如下图的 A 调用 B 的链路超时
。消息超时
:当前服务配置的从收到请求消息到返回响应数据的最长消息处理时间,这是当前服务控制自身不浪费资源的手段,如下图的 B 内部的消息超时
。调用超时
:当前服务调用下游服务设置的每一个 RPC 请求的超时时间,如下图的 B 调用 C 的超时时间
。通常一个服务会连续调用多次 rpc,如下图 B 调完 C,继续串行调用 D 和 E,这个调用超时控制的是每个 RPC 的独立超时时间。
发起 RPC 调用请求时,框架会计算此次 RPC 调用实际超时时间。实际超时时间是通过以上三个超时配置实时计算的最小值,计算过程如下:
- 首先计算得到链路超时和消息超时的最小值,比如:链路超时 2s,消息超时 1s,则
当前消息允许的最长处理时间
为 1s。 - 发起 RPC 调用时,再次计算
当前消息允许的最长处理时间
和单个调用超时
的最小值,比如图中的 B->C 设置的调用超时
为 5s,则实际上 B 调用 C 的实际超时仍然是 1s;再比如 B->C 单个超时时间为 500ms,这种情况 B 调用 C 的实际超时即为 500ms,此时 500ms 这个值也会通过协议字段传给 C,在服务端 C 的视角来看就是他的链路超时
。链路超时时间会在整个 RPC 调用链上一直传递下去,并逐渐减少,直至为 0,这样也就永远不会出现死循环调用的问题。 - 因为每一次 RPC 调用都会实际消耗一部分时间,所以
当前消息允许的最长处理时间
需要实时计算剩余时间,比如上面 B 调用 C 真实耗时 200ms,当前消息允许的最长处理时间
就只剩下 800ms 了。此时发起 B->D 调用时,需要计算当前消息允许的最长处理时间
和 B->D调用超时
的最小值。比如图中的 B->D 设置的调用超时
时间为 1s,则实际生效的超时时间仍然为 800ms。
tRPC-Go 的超时控制是基于 Context
实现的。
Context
是请求上下文的意思,是所有 RPC 接口的第一个参数,可以设置超时,取消。所以为了使超时控制生效,所有的 RPC 调用都必须一路携带请求入口的 Context
。必须注意:超时只有通过 Context
才能控制。
Context
只能控制每次调用的超时时间,不能控制协程的结束,如果业务代码里面不考虑 Context
进行阻塞(如 time.Sleep
)则超时控制无法生效,协程也就会永远阻塞无法退出。
在 Server 收到请求时,会计算当前消息允许的最长处理时间
,通过 context.WithTimeout
将 Context
设置超时,并在业务处理函数结束时 cancel 掉当前 Context
。所以当你自己通过 go 创建协程执行异步逻辑时,一定不能再使用请求入口的 Context
,必须使用新的 Context
,如trpc.BackgroundContext()
。
tRPC-Go 的超时控制全部通过配置文件指定即可。 注意:以下设置的均是当前服务自身的超时配置,即前文图中的服务B。
超时时间默认会从最源头服务一直通过协议字段透传下去,用户可以自己配置开关开启或关闭是否继承。 链路超时是由上游 Client 调用方决定的,trpc Client 默认都会将当前的 RPC 实际超时时间设置到链路超时里。
server:
service:
- name: trpc.app.server.service
disable_request_timeout: true # 默认 false,默认超时时间会继承上游设置的超时时间;配置 true 则禁用,表示忽略上游服务调用当前服务时协议传递过来的超时时间
如果希望完全禁用超时,可配置此值。
每个服务启动时都可配置该服务所有请求的消息处理超时时间。
如果业务代码里面不考虑 Context
进行阻塞(如 time.Sleep
)则超时控制无法生效,处理协程不会立马结束。
server:
service:
- name: trpc.app.server.service
timeout: 1000 # 单位 ms,每个接收到的请求最多允许 1000ms 的执行时间,所以要注意权衡当前请求内的所有串行 RPC 调用的超时时间分配,默认为 0,不设置超时
每次 RPC 调用都可以配置当前请求的最大超时时间,如果代码里面有设置 WithTimeout
选项,代码配置有更高的优先级,调用超时
以代码为准,但是代码不够灵活,建议直接通过配置文件指定调用超时时间。
client:
service:
- name: trpc.app.server.service # 下游服务名称
timeout: 500 # 单位 ms,每个发起的请求最多允许 500ms 的超时时间,默认为 0,不设置超时,即无限等待