最佳实践
最佳实践
通用
- 请**使用回调 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产生竞争。