最佳实践
最佳实践
通用
- 请**使用回调 API**。
- **在** third_party/grpc/include/grpcpp **中查找带有注释的头文件**。
- **始终为 RPC 设置截止时间。** 这里有一篇博客文章对此进行了解释。对于长时间运行的流式 RPC 来说,设置截止时间更困难,但应用程序可以实现自定义逻辑来为消息添加截止时间。
流式 RPC
- 如果您需要所有已发送消息,请**读取所有消息直至失败**。对于回调 API,请读取直到使用 bool
ok=false
调用 reaction;对于异步 API,请读取直到 tag 具有ok=false
;对于同步 API,请读取直到 Read 失败。这比计算消息数量更可靠。 - 一次只能有一个读取和一个写入操作进行中。这不是最佳实践,而是 API 要求,但仍值得再次提及。
- 如果您的应用程序具有双向数据流,请使用双向流式传输,而不是客户端-服务器和服务器-客户端模型。这将允许一致的负载均衡,并且在 gRPC 中得到更好的支持。
回调 API 特定
在本节中,“操作”定义为 StartRead
、StartWrite
(及其变体)和 SendInitialMetadata
。Finish
也是一个操作,但通常与讨论无关。
“Reactions”定义为 reactor 中可重写的回调,例如 OnReadDone
、OnWriteDone
、OnCancel
和 OnInitialMetadataDone
。OnDone
也是一个 reaction,但作为最终 reaction,这里的说明可能与它无关。
最佳实践
- Reactions 应该快速执行。不要执行阻塞或长时间运行/高开销的任务,也不要休眠。这可能会影响进程中的其他 RPC。
流式传输
在 reactions 之外启动操作时使用 Holds。如果您在 reactions 之外在客户端上启动操作,您可能需要使用 holds。这会阻止 OnDone() 运行,直到 holds 被移除,从而在流发生错误时防止与未完成操作进行最终清理的竞争。reactions 中的
bool ok
值将反映流是否已结束,并且在此之后启动的操作都将具有ok=false
。同步 reactions。Reactions 可以并行运行。例如,
OnReadDone
可能与OnWriteDone
同时运行。请相应地进行同步。读取直到 false。与其计算消息数量等,不如读取直到
OnReadDone(ok=false)
。在服务器端,请注意这要求客户端调用 writes done,这是推荐的做法。服务器端无需做任何特殊操作 -
Finish
表示流的结束。应用程序通过
Finish
调用发送的状态,可能要等到所有传入消息都被消耗后,客户端才能看到。
常见问题
通用
如何调试 gRPC 问题?
请参阅故障排除。
回调流式 API
客户端半关闭是必需的还是预期的?
强烈建议客户端半关闭,以便服务器可以继续读取直到
OnReadDone(ok=false)
,这在服务器和客户端都推荐。但是,这不是必需的——服务器也可以始终选择在消耗所有客户端数据之前调用Finish()
。如何在客户端取消操作?
在调用
OnDone
之前,客户端是否需要读取线上的数据?最佳实践是始终在客户端读取直到
ok=false
。客户端必须读取所有传入数据,然后才能看到服务器
Finish
返回的 OK 状态。但是,错误状态(例如来自取消、截止时间或流中止)可能随时到达。无法保证带有错误状态的显式服务器
Finish
是排在服务器写入之后还是立即交付。因此,客户端应始终通过读取直到ok=false
来消耗所有传入消息,以保证状态的交付。由于如果服务器使用错误状态调用
Finish
,消息无法保证交付,因此不应使用错误状态来向客户端传达成功和进一步指示;而应使用尾随元数据来实现此目的。客户端何时调用
OnDone
?当客户端有可用状态(所有传入数据已读取且服务器已调用
Finish
或状态是立即交付的错误)、所有用户 reactions 已完成运行以及所有 holds 已移除时,会调用它。服务器何时调用
OnDone
?所有 reactions 必须完成运行(包括相关的
OnCancel
),并且服务器必须已调用Finish
。什么是“reactor 内部流”与“reactor 外部流”,为什么它很重要?
reactor 内部流是指从 reactions 内部(或 reactor 构造函数)启动操作,例如
OnWriteDone
启动另一个写入。这些操作是合理的,因为一次只能有一个读取和一个写入正在进行,并且从 reaction 启动它们有助于保持这一点。reactor 外部流是指从 reactions 外部启动流上的操作。这很合理,因为 reactions 不应该阻塞,并且应用程序可能尚未准备好执行下一次读取或写入。请注意,reactor 外部流需要在客户端使用 holds 进行同步。服务器端使用 Finish 来同步 reactor 外部调用;应用程序在调用 Finish 后不应启动更多操作。
什么是 holds,以及我何时何地使用它们?
它们用于在调用 OnDone 时进行同步,并且仅在使用 reactor 外部流时才需要。请注意,holds 仅存在于客户端。
如果服务器调用
Finish
但客户端继续启动新操作(例如StartWrite
),会发生什么?OnWriteDone(ok=false)
将在每次写入启动时调用,直到OnDone
被调用。我们如何知道何时可以删除 reactor?
reactor 可以在
OnDone
中删除。不得从OnDone
调用 reactor 基类上的任何方法,并且在调用OnDone
后,gRPC 不会再访问 reactor 对象。应用程序有责任确保在OnDone
运行时没有操作被启动。reactions 可以同时运行吗?例如,
OnReadInitialMetadataDone
可以与OnReadDone
同时运行吗?是的,大多数 reactions 可以并行运行。只有
OnDone
作为最终操作单独运行。即使元数据为空,
OnReadInitialMetadataDone
也会每次都调用吗?是的,这用于向客户端传达元数据为空。与所有 reactions 一样,用户应用程序无需重写此 reaction。
如果初始元数据随第一次写入一起发出,而不是因为显式调用
SendInitialMetadata
,服务器上会调用OnSendInitialMetadataDone
吗?不,它必须显式请求。隐式调用不会获得回调。
如果客户端调用
WriteLast
,他们会同时触发OnWriteDone
和OnWritesDoneDone
回调吗?如果他们调用Write(options.set_last_message = true)
会发生什么?如果有载荷,只会调用
OnWriteDone()
。OnWritesDoneDone
只会在响应WritesDone()
时调用。在有未完成的写入时(即尚未调用
OnWriteDone()
)可以调用WritesDone()
吗?是的,传输层会对其进行排序。
客户端何时调用
OnReadInitialMetadataDone
并传入ok=false
?这只会在发生错误时调用。
用户可以在不等待
OnSendInitialMetadataDone
的情况下,在中间调用SendInitialMetadata
、StartWrite
吗?这类似于
StartWrite
和WritesDone
。如果传输层进行了排序,我们不需要强制排序,但用户可能会以任何顺序获得回调调用。服务器
OnCancel
何时运行?请注意,这并非流式传输特有。它用于带外取消,即当流上发生错误(例如连接中止)、客户端或服务器端请求取消或截止时间到期时。它可以作为客户端不再处理任何数据的信号。
它可以与其他 reactions 并行运行。在
OnCancel
之后调用或在OnCancel
内部启动的操作,其 reactions 将以ok=false
调用。请注意,如果服务器调用
Finish
并指定错误状态,则OnCancel
不再运行,因为这不被视为带外取消。根据排序,如果调用
Finish
,OnCancel
可能会或可能不会运行。然而,OnDone
始终是最终回调。如果
OnCancel
运行,服务器还需要调用Finish
吗?是的,尽管传递给
Finish
的状态会被忽略。如果调用
OnCancel
,OnDone
还会被调用吗?是的,
OnDone
是最终回调,一旦服务器也调用了Finish
并且所有其他 reactions 都已完成运行,就会被调用。用户可以在
OnCancel
之后调用其他操作(Start*)吗?是的,但它们的所有 reactions 都将以
ok=false
运行。在调用服务器Finish
后调用它们是无效的。context
上的IsCancelled()
何时返回 true?当流上发生错误、客户端或服务器端请求取消或截止时间到期时,会设置此值。一旦 b/138186533 得到解决,如果此类错误是导致 reactions 使用
ok=false
调用的原因,它将在这些 reactions 调用*之前*设置。是否有每个操作(例如
StartRead
)的截止时间,还是只有每个 RPC 的截止时间?它只针对每个 RPC。
如果用户执行
stub->MyBidiStreamRPC(); context->TryCancel()
,用户是否仍需要调用StartCall
?是的,一旦调用
stub->MyBidiStreamRPC()
,它就是必需的。在有未完成的 Read 或 Write 时,服务器调用
Finish
是否合法?这对于读取是允许的,并且会调用
OnReadDone(ok=false)
。在有未完成写入的情况下调用Finish
不是有效的 API 用法,因为Finish
可以被视为没有数据的最终写入,这会违反一次一个写入的规则。当服务器在有未完成读取时调用
Finish
会发生什么?OnReadDone(ok=false)
被调用。您可以在客户端 reactor 的
OnDone
中启动另一个操作(例如读取或写入)吗?不,那不是 API 的合法用法。
在调用
Finish
后,您可以在服务器上启动操作吗?这不是一个好习惯。但是,如果您在 reactions 内部启动新操作,它们的相应 reactions 将以
ok=false
调用。使用 reactor 外部流启动它们是非法的并且存在问题,因为这些操作可能会与OnDone
产生竞争。