本文档描述gRPC channels 连接语义和对 RPCs 的相应影响。然后我们讨论API。
States of Connectivity
gRPC Channels 提供一种抽象,在其之上客户端可以与服务器通信。客户端方面的 channel 对象仅使用一个DNS名就能够被构造。Channels 封装了一系列功能,包括名称解析,建立tco连接(包含重连和backoff) 和 TLS 握手。Channels也能在已建立的连接上处理错误或者重连,或者在HTTP/2 GO_AWAY的情况中,重新解析名称或重连。
为了向 gRPC API 的用户隐藏所有这些活动的细节(i.e.,应用代码),且暴露关于channel状态这些有意义的信息,我们使用具有5种状态的状态机,定义如下:
CONNECTING:channel 正在尝试建立连接并期望在以下步骤中取得进展 name resolution,TCP connection establishment 或 TLS handshake。这可能被用作通道创建的初始化状态。
READY:channel 已经通过以下方法成功建立连接,TLS handshake(或其他等价方式)和协议层级(HTTP/2,etc)的握手,且随后所有的通讯尝试都已经成功(或者在没有任何已经失败的情况下挂起).
TRANSIENT_FAILURE:已经有一些短暂的失败(例如TCP3路握手超时或者一个socket错误)。在这一状态的Channels将最终转换为CONNETCTING状态并尝试再次建立连接。因为重试使用指数回退,连接失败的channel开始时将花费很少的时间挂起,但是随之不断重复的尝试,时间会逐渐增加。对于很多的non-fatal失败(e.g.,TCP连接尝试超时因为服务器不可用),channel可能会花费大量的时间在这个状态上。
IDLE:在这一状态,channel甚至不会尝试创建连接,因为缺少新的或挂起的RPCs。新的 RPCs 可能在这一状态被创建。任何在本channel上开始rpc的尝试都会使channel离开本状态,进入CONNECTING 状态。当在指定的IDLE_TIMEOUT内,channel上没有RPC活动,i.e,,在这期间没有新的或者挂起的(激活的)RPCs,channel将从READY或CONNECTING状态IDEL。另外,接收到GOAWAY的channel且没有激活或挂起的RPCs,也应当转到IDLE来避免试图断开连接的服务器连接过载。我们将使用默认的IDEL_TIMEOUT(300s, 5min)。
SHUTDOWN:本channel已经开始关闭了。任何新的RPCs将会立刻失败。挂起的RPCs可能会继续运行直到应用取消它们。Channels可能进入这个状态,因为应用显式的请求关闭,或者在尝试连接通讯时一个不可修复的错误发生了。进入这种状态的Channel永远不会离开这种状态。
下述表格列出从一种状态到另一种状态的合法转换和相应原因。空单元格表示不允许转换。
From/To | CONNECTING | READY | TRANSIENT_FAILURE | IDLE | SHUTDOWN |
---|---|---|---|---|---|
CONNECTING | 连接建立的增量过程 | 所有建立连接需要的步骤都成功了 | 建立连接需要的任何步骤中发生任意错误 | 在IDLE_TIMEOUT时间内,没有新的RPC活动 | 应用触发关闭 |
READY | 连接建立的增量过程 | 在已建立连接的通道上期望成功的通讯中发生任何错误 | 在IDLE_TIMEOUT时间内,没有新的RPC活动 | 应用触发关闭 | |
TRANSIENT_FAILURE | backoff实现所需要的等待时间过去 | 应用触发关闭 | |||
IDLE | 本channel上有任何新的RPC活动 | 应用触发关闭 | |||
SHUTDOWN |
Channel State API
所有的 gRPC 库将暴露一个 channel层级的 API 方法来 poll 出当前 channel 的状态。在C++中,该方法为 GetState 并返回一个枚举变量表示5种 channel 合法状态之一。它也接受一个布尔值 try_to_connect 来将 channel 的 IDEL 状态转换为 CONNECTING。布尔值应当表现的与一个RPC发生时一样,所以它会重置IDLE_TIMEOUT。
grpc_connectivity_state GetState(bool try_to_connect);
所有的库也应当暴露一个API用于确保应用(gRPC API 的用户)能够在 channel 状态发生改变时获得通知。因为状态改变可能十分迅速,与任何此类通知产生竞争,所以通知仅仅告知用户某些状态改变已经发生了,将channel当前状态的poll丢给用户去做。
同步版本的API如下:
bool WaitForStateChange(grpc_connectivity_state source_state, gpr_timespec deadline);
当状态与source_state不一致时,返回true;如果超时,返回false。Asynchronous-based 和 futures-based APIs也有对应的方法,用于channel状态改变时进行通知。
注意,每次有任何状态过渡时通知都会被递交。另一方面,对于可恢复的失败,合法状态转换的规则,需要从CONNECTING转换为TRANSIENT_FAILURE,然后返回CONNECTING,即使相应的指数backoff在重试前不需要等待。在共同作用下,应用收到的状态改变通知看上去像是虚假的,e.g.,一个应用等待一个channel上额状态改变,在收到一个状态改变通知后,发现状态仍然是CONNECTING,这是因为channel有极短持续时间的 TRANSIENT_FAILURE 状态。
gRPC channel
class ChannelInterface {
public:
virtual ~ChannelInterface() {}
// 获取当前 channel 状态。如果 channel 在 IDEL 状态且参数`try_to_connect`被设置为true,尝试连接。
virtual grpc_connectivity_state GetState(bool try_to_connect) = 0;
// 当channel状态变化时或者截止时间过期,返回在参数cq[in]上的参数tag[out],需要调用GetState()来
// 获取当期状态
template<typename T>
void NotifyOnStateChange(grpc_connectivity_state last_observed,
T deadline, ::grpc::CompletionQueue* cq, void*tag) {
TimePoint<T> deadline_tp(deadline);
NotifyOnStateChangeImpl(last_observed, deadline_tp.raw_time(), cq, tag);
}
// 阻塞等待channel状态改变或者截止时间过期。GetState 需要被调用来获得当前的状态
template <typename T>
bool WaitForStateChange(grpc_connectivity_state last_observed, T deadline){
TimePoint<T> point_tp(deadline);
return WaitForStateChangeImpl(last_observed, deadline_tp.raw_time());
}
// 等待该 channel 被连接
template <typename T>
bool WaitForConnected(T deadline) {
grpc_connectivity_state state;
while((state = GetState(true)) != GRPC_CHANNEL_READY) {
if(!WaitForStateChange(state, deadine)) return false;
}
return true;
}
}