c# gRPC 和 Web API 在高并发场景下的性能对比

来源:这里教程网 时间:2026-02-21 17:36:11 作者:

gRPC 的
GrpcChannel
必须复用,否则性能断崖式下跌

很多刚上手 gRPC 的人会为每次调用都新建

GrpcChannel
,这在高并发下直接拖垮吞吐——因为每个
GrpcChannel
默认建立并维护独立的 HTTP/2 连接池,频繁创建/销毁连接引发 TLS 握手、TCP 建连、流控初始化等开销。实测在 1000 QPS 下,每请求新建 channel 的吞吐可能不足复用时的 1/5。

正确做法是将

GrpcChannel
作为单例或注入到 DI 容器中长期持有:

services.AddSingleton<GrpcChannel>(sp =>
    GrpcChannel.ForAddress("https://api.example.com", new GrpcChannelOptions
    {
        HttpHandler = new SocketsHttpHandler
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(5),
            KeepAlivePingDelay = TimeSpan.FromSeconds(60)
        }
    }));
GrpcChannel
是线程安全的,可被多线程并发使用
避免在
Dispose()
后继续调用,否则抛出
ObjectDisposedException
若服务地址动态变化(如多集群路由),需自行封装带刷新逻辑的 channel 工厂,而非简单 new

Web API 的
HttpClient
复用规则和 gRPC 完全一致

别以为 Web API 就“随便 new”,

HttpClient
同样必须复用。反复 new
HttpClient
会导致端口耗尽(TIME_WAIT 状态堆积)、DNS 缓存失效、SSL 会话复用失败等问题。它和
GrpcChannel
在底层共享
SocketsHttpHandler
,行为高度一致。

常见错误写法:

using var client = new HttpClient();
—— 这在高并发循环中等于自毁。

推荐注册为
AddHttpClient<iserviceclient>()</iserviceclient>
,由 DI 管理生命周期
手动管理时,用静态只读字段或
Lazy<httpclient></httpclient>
初始化一次
不要通过
client.DefaultRequestHeaders
动态设 token 等请求级头——应改用
HttpRequestMessage
实例设置,避免并发写冲突

序列化开销:Protobuf vs JSON 是真实瓶颈点

gRPC 默认用 Protobuf,Web API 默认用 System.Text.Json(.NET 6+)。在同等数据结构下,Protobuf 序列化/反序列化耗时通常只有 JSON 的 30%~50%,体积压缩率常达 60% 以上。这对高频小包场景(如微服务间状态同步)影响显著。

但注意:如果你的 payload 主要是大文本(如日志行、HTML 片段),JSON 的字符串直通优势可能抵消 Protobuf 的二进制优势;而 Protobuf 要求提前定义

.proto
文件、生成类型,开发链路更重。

Web API 也可接入 Protobuf:用
AddControllers().AddProtobufFormatters()
,但需客户端配合发送
application/x-protobuf
gRPC 不支持直接返回纯 HTML 或动态 JSON 字段(如
Dictionary<string object></string>
),必须强类型
调试时 Protobuf 二进制不可读,需依赖
grpcurl
或 Wireshark 解码,排查成本略高

HTTP/2 多路复用不是万能的,流控和超时配置不当照样卡死

gRPC 依赖 HTTP/2 的多路复用提升并发能力,但这不意味着“无限并发”。服务器端的

MaxStreamsPerConnection
(Kestrel 默认 100)、客户端的
MaxOutboundStreamsPerConnection
、以及流控窗口(
InitialStreamWindowSize
)都会成为瓶颈。当大量短生命周期流(如毫秒级 RPC)密集发起,可能触发流控阻塞,表现为你看到大量请求 hang 在
await call.ResponseAsync

典型症状:CPU 不高、连接数稳定,但 p99 延迟陡增、部分请求超时。

Kestrel 中调大流上限:
options.Limits.Http2.MaxStreamsPerConnection = 1000;
客户端降低初始窗口以减少内存占用(尤其小消息):
new GrpcChannelOptions { MaxReceiveMessageSize = 4 * 1024 * 1024 }
务必设置
CallOptions.Timeout
,否则默认无超时,失败请求会长时间占住 stream
Web API 的 HTTP/2 行为受相同底层限制,但因无 stream 概念,压力更多落在连接数和线程池上

实际压测中,gRPC 在 5k+ QPS、平均 payload 浏览器直连),Web API 的灵活性和可观测性优势立刻凸显。选型时别只盯数字,先看你的团队是否愿意为 Protobuf 合约管理和二进制调试多花 20% 时间。

相关推荐

热文推荐