性能最佳实践

一份关于提高性能的通用和特定于语言的最佳实践的用户指南。

性能最佳实践

一份关于提高性能的通用和特定于语言的最佳实践的用户指南。

通用

  • 尽可能重复使用存根和通道

  • 使用保持活动 ping 在不活动期间保持 HTTP/2 连接处于活动状态,以便在没有延迟的情况下快速进行初始 RPC(即 C++ 通道参数 GRPC_ARG_KEEPALIVE_TIME_MS)。

  • 当处理从客户端到服务器、从服务器到客户端或在两个方向上长期存在的数据逻辑流时,使用流式 RPC。流可以避免连续的 RPC 启动,这包括客户端的连接负载平衡、在传输层启动新的 HTTP/2 请求以及在服务器端调用用户定义的方法处理程序。

    但是,流一旦开始就无法进行负载平衡,并且对于流故障可能很难调试。它们还可能在小规模上提高性能,但由于负载平衡和复杂性而降低可伸缩性,因此只有在它们为应用程序逻辑提供实质性的性能或简单性优势时才应使用它们。使用流来优化应用程序,而不是 gRPC。

    附注: 这不适用于 Python(有关详细信息,请参阅 Python 部分)。

  • (特殊主题) 每个 gRPC 通道使用 0 个或多个 HTTP/2 连接,并且每个连接通常对并发流的数量有限制。当连接上的活动 RPC 数量达到此限制时,其他 RPC 将在客户端中排队,并且必须等待活动 RPC 完成后才能发送。具有高负载或长期流式 RPC 的应用程序可能会因为此排队而出现性能问题。有两种可能的解决方案

    1. 为应用程序中每个高负载区域创建单独的通道

    2. 使用 gRPC 通道池将 RPC 分布到多个连接上(通道必须具有不同的通道参数以防止重复使用,因此定义一个特定于用途的通道参数,例如通道号)。

    附注: gRPC 团队计划添加一项功能来解决这些性能问题(有关更多信息,请参阅 grpc/grpc#21386),因此任何涉及创建多个通道的解决方案都是临时的解决方法,最终应该不需要。

C++

  • 不要对性能敏感的服务器使用同步 API。 如果性能和/或资源消耗不是问题,请使用同步 API,因为它是实现低 QPS 服务最简单的方法。

  • 对于大多数 RPC,优先使用回调 API 而不是其他 API,前提是应用程序可以避免所有阻塞操作或将阻塞操作移动到单独的线程。回调 API 比完成队列异步 API 更容易使用,但对于真正的高 QPS 工作负载,目前速度较慢。

  • 如果必须使用异步完成队列 API,则最佳的可扩展性权衡是拥有 numcpu 的线程。 完成队列相对于线程数的理想数量可能会随着时间而变化(随着 gRPC C++ 的发展),但截至 gRPC 1.41(2021 年 9 月),每个完成队列使用 2 个线程似乎可以提供最佳性能。

  • 对于异步完成队列 API,请确保为所需的并发级别注册足够多的服务器请求,以避免服务器连续卡在慢速路径中,从而导致本质上串行的请求处理。

  • (特殊主题) 当在 proto 序列化上花费大量争用/CPU 时间时,gRPC::GenericStub 在某些情况下很有用。此类允许应用程序直接发送 原始 gRPC::ByteBuffer 作为数据,而不是从某些 proto 进行序列化。如果多次发送相同的数据,并且在多次 ByteBuffer 发送之前进行一次显式的 proto 到 ByteBuffer 序列化,这也会很有帮助。

Java

  • 使用非阻塞存根来并行化 RPC。

  • 提供一个自定义的执行器,该执行器根据您的工作负载限制线程数(缓存(默认)、固定、forkjoin 等)。

Python

  • 流式 RPC 为接收和可能发送消息创建额外的线程,这使得 流式 RPC 在 gRPC Python 中比单向 RPC 慢得多,这与 gRPC 支持的其他语言不同。

  • 使用 asyncio 可以提高性能。

  • 在同步堆栈中使用 future API 会导致创建额外的线程。如果可能,避免使用 future API

  • (实验性) 通过 SingleThreadedUnaryStream 通道选项 提供了一个实验性的单线程单向流实现,这可以为每个消息节省高达 7% 的延迟。

上次修改时间:2024 年 11 月 12 日:在不同的网页中嵌入 YouTube 视频 (#1380) (196f408)