
HTTP/1.1 时代我们习惯了“一次请求一个连接”“队头阻塞”“Header 冗余”这些老问题但移动互联网和微服务爆发之后接口调用越来越频繁、数据链路越来越复杂旧协议在高并发场景下越来越吃力。HTTP/2 和 gRPC 正是这一背景下被推上舞台的两大核心技术。本文用最短时间把 HTTP/2 的核心机制和 gRPC 的设计思路讲清楚再配一个可直接运行的 Java 示例让你既懂概念、也能动手。适合刚接触微服务、网络协议或 RPC 框架的开发者无论你是后端新人还是正在选型的老手读完都能掌握HTTP/2 凭什么更快、gRPC 为什么选择 HTTP/2、以及 gRPC 一个最小项目的完整搭建过程。1. 背景与核心概念先聊一个最朴素的问题为什么我们需要 HTTP/2在 HTTP/1.1 的场景里一个 TCP 连接同时只能处理一个请求。如果你要同时请求 6 个资源浏览器通常会建立多个 TCP 连接并行下载但连接数有上限而且每个请求都要带上完整的 Header。随着页面资源变多、API 调用变频繁这种“一个请求占一条路”的模式出现了两个明显问题队头阻塞前一个请求响应慢了后面的请求只能排队等待。冗余传输每个请求都重复发送大量相同的 Header 字段浪费带宽。HTTP/2 的目标不是改变 HTTP 的语义方法、状态码、Header 这些依然保留而是优化“传输方式”。它引入了二进制分帧、多路复用、Header 压缩、服务端推送四大特性让多个请求可以共享一条 TCP 连接互相不阻塞网络利用率大幅提升。那么 gRPC 又是什么gRPC 是 Google 开源的一个高性能 RPCRemote Procedure Call远程过程调用框架。简单理解RPC 就是让你像调用本地方法一样调用远程服务。gRPC 默认使用 Protocol Buffers简称 Protobuf作为接口定义语言IDL和序列化协议底层传输则基于 HTTP/2。gRPC 的典型应用场景集中在微服务内部调用、多语言服务通信、移动端与后端的长连接交互、流式数据处理等方向。相比传统的 REST 接口gRPC 的请求包更小、解析速度更快还天然支持双向流式通信。很多云原生组件之间的通信就是基于 gRPC 实现的比如 Prometheus 的远程写、Envoy 与控制平面的 xDS 协议等。需要区分的是gRPC 是一种 RPC 框架HTTP/2 是它的传输基石两者不是竞争关系而是“上层应用”与“底层协议”的搭配关系。2. HTTP/2 核心机制拆解HTTP/2 的官方规范是 RFC 7540于 2015 年发布。它没有改变 HTTP 的语义却在传输层做了一次大手术。下面逐个拆解它的四个核心特性。2.1 二进制分帧HTTP/2 把数据划分成更小的“帧”Frame每个帧用二进制格式表示。相比 HTTP/1.x 的纯文本解析二进制格式更紧凑、解析更高效。帧的类型有多种常见的有HEADERS传输 Header 数据。DATA传输 Body 数据。SETTINGS连接参数协商。PING心跳与延迟检测。RST_STREAM终止某个流。GOAWAY优雅关闭连接。每个帧都会携带所属的 Stream ID这样即使大量帧交织在一起接收方也能知道它们属于哪个请求。2.2 多路复用HTTP/2 在一条 TCP 连接上开启了多个“流”Stream每个流就是一个请求-响应交互。流与流之间相互独立可以乱序发送和接收这就是多路复用。打个比方HTTP/1.1 像单车道马路一次只能走一辆车HTTP/2 像多车道高速公路多辆车可以同时并排行驶每辆车都有自己的编号目的地不同也不会互相挡路。对用户而言最直观的感受是页面加载更快了因为多个资源请求不再排队等待。2.3 Header 压缩HPACKHTTP/2 使用 HPACK 算法对 Header 进行压缩。原理并不复杂客户端和服务端各自维护一张 Header 索引表常见的字段比如:method: GET、:path: /index.html用固定的静态索引表示动态出现的新字段会加入动态表并在后续请求中用索引号代替完整内容。这样做的好处是同一连接上的后续请求 Header 占用空间大幅下降尤其对 API 调用密集的场景收益非常明显。2.4 服务端推送HTTP/2 允许服务端在客户端没有明确请求的情况下主动推送资源。比如浏览器请求一个 HTML 页面服务端可以同时推送页面里引用的 CSS、JS 文件减少后续往返次数。不过服务端推送在实际使用中有很多争议比如推送了客户端缓存里已经存在的资源反而浪费带宽。现代 HTTP/2 实现中推送功能并不是默认推荐开启的很多团队选择关闭它。2.5 HTTP/2 的限制HTTP/2 解决了 HTTP/1.1 的队头阻塞问题但底层 TCP 的队头阻塞依然存在。因为 TCP 是可靠的字节流协议如果某个包丢失后续包即使到达了应用层也无法处理必须等待重传。真正彻底解决这一问题需要 HTTP/3 QUIC这是后话但了解这个边界有助于你正确评估 HTTP/2 的性能收益。另外HTTP/2 强制要求使用 TLS 吗规范的官方说法是“不强制”绝大多数浏览器和主流服务端实现都要求 HTTP/2 over TLS。实践中我们基本只会在 HTTPS 场景下使用 HTTP/2h2表示基于 TLS 的 HTTP/2h2c表示明文 HTTP/2。gRPC 也支持明文模式但生产环境通常使用 TLS。3. gRPC 框架核心设计3.1 gRPC 与 HTTP/2 的关系gRPC 选择 HTTP/2 作为传输层不是随意之举。RPC 框架需要解决四个问题方法调用映射、参数序列化、网络传输、错误处理。HTTP/2 的多路复用让 gRPC 可以在一条连接上同时处理成千上万个请求流HTTP/2 的流式支持让 gRPC 天然具备了流式通信能力。具体到协议层面gRPC 用 HTTP/2 的 HEADERS 帧传输请求元数据用 DATA 帧传输消息体。每个 gRPC 调用在一个 HTTP/2 Stream 上进行方法和请求体通过专门的头字段传递。3.2 Protobuf 与 IDLgRPC 接口的定义通常写在.proto文件中格式类似syntax proto3; service UserService { rpc GetUser (GetUserRequest) returns (User); } message User { int32 id 1; string name 2; }这个.proto文件就是 IDL它同时描述了接口签名和数据结构。通过 protoc 编译器可以生成 Java、Go、Python、C 等任意语言的代码这也是 gRPC 多语言互通的基础。Protobuf 的序列化性能比 JSON 好很多体积小、解析快。因为它采用二进制编码字段通过数字编号标识而不是像 JSON 那样把字段名作为字符串一遍遍重复。注意gRPC 并不强制要求必须使用 Protobuf你可以在 gRPC 中自定义其他序列化器。但默认的 Protobuf 生态最成熟、性能最好绝大多数项目都是搭配 Protobuf 使用。3.3 四种通信模式gRPC 支持四种调用模式这是它比普通 REST 接口更灵活的地方一元调用Unary客户端发送一个请求服务端返回一个响应。最常用类似普通方法调用。服务端流式Server Streaming客户端发一个请求服务端返回一组数据流。适合批量数据推送、日志订阅等场景。客户端流式Client Streaming客户端持续发送一组数据服务端最终只返回一个响应。适合上传大文件、上报统计数据等场景。双向流式Bidirectional Streaming客户端和服务端独立发送和接收数据互相不等待。适合聊天、实时交互、AI 推理的 token 流输出等场景。这四种模式在.proto文件中通过stream关键字声明。3.4 gRPC 与主流方案对比对比维度gRPCRESTHTTP/1.1 JSONThrift / Dubbo传输协议HTTP/2HTTP/1.1TCP / HTTP序列化Protobuf二进制JSON文本Thrift 二进制 / Hessian接口契约.proto 强类型OpenAPI 可选IDL 强类型流式支持四种模式原生支持SSE 有限支持部分支持浏览器兼容需要 gRPC-Web原生支持不支持多语言官方多语言任何语言生态较窄REST 最大的优势是简单、通用、浏览器友好gRPC 最大的优势是性能高、流式能力强、适合服务间通信。这也是为什么很多团队内部调用用 gRPC对外暴露 API 仍然用 REST。4. 环境准备与版本说明在动手写 gRPC 示例之前先确认你的本地环境。本文示例采用 Java 和 Maven这是 CSDN 读者最常用的技术栈之一。不过下面的流程也适用于 Go、Python 等语言思路完全一致。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路不建议照搬锁死版本号。JDK至少 JDK 8建议 JDK 11 及以上。构建工具Maven 3.6 或 Gradle 6。操作系统Windows / macOS / Linux 均可。开发工具IntelliJ IDEA 或 Eclipse。依赖库grpc-netty-shaded、grpc-protobuf、grpc-stub、protobuf-java。编译插件protobuf-maven-plugin用于从.proto文件生成 Java 代码。如果是 Go 生态只需要安装 protoc、protoc-gen-go、grpc-go 相关模块即可。本文后续以 Java 为例。如果你用的是 Maven建议先配置好阿里云镜像加速依赖下载。这一步不是必须的但能帮你节省大量等待时间。5. 实战用 gRPC 搭建一个用户查询服务下面完成一个最小可运行的 gRPC 项目。功能很简单客户端传入用户 ID服务端返回用户基本信息。项目完成后你可以用类似结构快速扩展到自己的业务中。5.1 创建项目结构先创建一个 Maven 项目推荐目录结构如下grpc-demo ├── pom.xml └── src ├── main │ ├── java │ │ └── com/example/grpc │ │ ├── UserServer.java │ │ ├── UserClient.java │ │ └── UserServiceImpl.java │ └── proto │ └── user.proto └── generated-sources └── (由插件生成不需要手动创建)注意src/main/proto目录是 Maven 插件的默认扫描目录插件会自动编译其中的.proto文件并把生成的 Java 类放入target/generated-sources下。5.2 编写 pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdgrpc-demo/artifactId version1.0-SNAPSHOT/version packagingjar/packaging properties maven.compiler.source11/maven.compiler.source maven.compiler.target11/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding !-- 请根据实际最新版本调整 -- grpc.version1.68.0/grpc.version protobuf.version3.25.5/protobuf.version /properties dependencies dependency groupIdio.grpc/groupId artifactIdgrpc-netty-shaded/artifactId version${grpc.version}/version /dependency dependency groupIdio.grpc/groupId artifactIdgrpc-protobuf/artifactId version${grpc.version}/version /dependency dependency groupIdio.grpc/groupId artifactIdgrpc-stub/artifactId version${grpc.version}/version /dependency dependency groupIdjavax.annotation/groupId artifactIdjavax.annotation-api/artifactId version1.3.2/version /dependency /dependencies build extensions extension groupIdkr.motd.maven/groupId artifactIdos-maven-plugin/artifactId version1.7.1/version /extension /extensions plugins plugin groupIdorg.xolstice.maven.plugins/groupId artifactIdprotobuf-maven-plugin/artifactId version0.6.1/version configuration protocArtifactcom.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}/protocArtifact pluginIdgrpc-java/pluginId pluginArtifactio.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}/pluginArtifact /configuration executions execution goals goalcompile/goal goalcompile-custom/goal /goals /execution /executions /plugin /plugins /build /project解释几个关键点grpc-netty-shaded包含了 Netty 及 HTTP/2 协议实现封装了依赖冲突问题推荐直接使用。grpc-protobuf提供 Protobuf 消息支持。grpc-stub提供客户端 Stub 和服务端基类。protobuf-maven-plugin在generate-sources阶段自动调用 protoc从.proto文件生成 Java 代码。os-maven-plugin用来识别当前操作系统确保 protoc 可执行文件与系统匹配。5.3 定义 user.proto 文件文件路径src/main/proto/user.protosyntax proto3; package com.example.grpc; option java_package com.example.grpc; option java_outer_classname UserProto; option java_multiple_files true; service UserService { rpc GetUser (GetUserRequest) returns (User) {} rpc ListUsers (ListUsersRequest) returns (stream User) {} } message GetUserRequest { int32 id 1; } message ListUsersRequest { } message User { int32 id 1; string name 2; string email 3; }java_multiple_files true表示每个 message 和 service 生成独立的 Java 文件方便管理。java_outer_classname是外层类名这里设为UserProto。执行 Maven 编译mvn clean compile编译成功后检查target/generated-sources/protobuf/java和target/generated-sources/protobuf/grpc-java目录可以看到自动生成的UserOuterClass.java、UserServiceGrpc.java等文件。5.4 编写服务端文件路径src/main/java/com/example/grpc/UserServiceImpl.javapackage com.example.grpc; import com.example.grpc.UserProto.GetUserRequest; import com.example.grpc.UserProto.ListUsersRequest; import com.example.grpc.UserProto.User; import io.grpc.stub.StreamObserver; public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase { Override public void getUser(GetUserRequest request, StreamObserverUser responseObserver) { User user User.newBuilder() .setId(request.getId()) .setName(Alice) .setEmail(aliceexample.com) .build(); responseObserver.onNext(user); responseObserver.onCompleted(); } Override public void listUsers(ListUsersRequest request, StreamObserverUser responseObserver) { // 演示服务端流式连续发送多个 User最后结束 for (int i 1; i 3; i) { User user User.newBuilder() .setId(i) .setName(User- i) .setEmail(user i example.com) .build(); responseObserver.onNext(user); } responseObserver.onCompleted(); } }这里重点理解StreamObserverUser它是 gRPC 的响应观察者onNext表示向客户端发送一条消息onCompleted表示本次调用正常结束。如果遇到异常可以调用onError(Throwable)。文件路径src/main/java/com/example/grpc/UserServer.javapackage com.example.grpc; import io.grpc.Server; import io.grpc.ServerBuilder; public class UserServer { public static void main(String[] args) throws Exception { Server server ServerBuilder.forPort(50051) .addService(new UserServiceImpl()) .build() .start(); System.out.println(gRPC Server started, listening on 50051); // 保持进程不退出 server.awaitTermination(); } }ServerBuilder.forPort(50051)会创建一个监听 50051 端口的安全 gRPC 服务端默认使用 HTTP/2 over TLS。本地调试时我们会改成明文模式见下文客户端配置。如果希望明文启动需要在服务端也启用明文Server server ServerBuilder.forPort(50051) .addService(new UserServiceImpl()) .usePlaintext() .build() .start();5.5 编写客户端文件路径src/main/java/com/example/grpc/UserClient.javapackage com.example.grpc; import com.example.grpc.UserProto.GetUserRequest; import com.example.grpc.UserProto.User; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; import java.util.concurrent.TimeUnit; public class UserClient { public static void main(String[] args) throws Exception { // 连接服务端本地测试使用明文 ManagedChannel channel ManagedChannelBuilder.forAddress(127.0.0.1, 50051) .usePlaintext() .build(); // 1. 一元调用同步阻塞式 Stub UserServiceGrpc.UserServiceBlockingStub blockingStub UserServiceGrpc.newBlockingStub(channel); GetUserRequest request GetUserRequest.newBuilder() .setId(1) .build(); User user blockingStub.getUser(request); System.out.println(一元调用结果: user.getName() , user.getEmail()); // 2. 服务端流式调用异步 UserServiceGrpc.UserServiceStub asyncStub UserServiceGrpc.newStub(channel); asyncStub.listUsers(UserProto.ListUsersRequest.newBuilder().build(), new StreamObserverUser() { Override public void onNext(User value) { System.out.println(服务端流式收到: value.getName()); } Override public void onError(Throwable t) { System.err.println(流式调用出错: t); } Override public void onCompleted() { System.out.println(服务端流式调用完成); } }); // 等待流式响应返回 Thread.sleep(2000); channel.shutdown(); channel.awaitTermination(5, TimeUnit.SECONDS); } }需要说明的是gRPC 的 Stub 有三种BlockingStub同步阻塞式返回结果前线程会等待适合简单调用。Stub异步式通过回调接收结果适合流式调用。FutureStub返回ListenableFuture可以用 Future 风格处理异步结果。5.6 运行与验证先启动服务端mvn exec:java -Dexec.mainClasscom.example.grpc.UserServer或者直接在 IDEA 中运行UserServer.main。服务端控制台输出gRPC Server started, listening on 50051再运行UserClient.main客户端控制台输出大致如下一元调用结果: Alice, aliceexample.com 服务端流式收到: User-1 服务端流式收到: User-2 服务端流式收到: User-3 服务端流式调用完成到这里你已经完成了一个同时包含一元调用和服务端流式调用的 gRPC 最小项目。6. 抓包观察 HTTP/2 帧如果你想直观感受 HTTP/2 和 HTTP/1.x 的差异可以用 Wireshark 抓一下本机回环流量。Windows 上需要安装 NpcapmacOS 上可能需要额外配置权限。抓包时的几个关键词过滤参考tcp.port 50051过滤 gRPC 服务端口。http2只看 HTTP/2 协议帧。grpcgRPC 报文通常会显示在 HTTP/2 的 DATA 帧内。观察重点多个请求是否复用了同一条 TCP 连接。HEADERS 帧中的:path字段对应 gRPC 的方法路径例如/com.example.grpc.UserService/GetUser。DATA 帧中携带的是 Protobuf 二进制消息肉眼无法直接阅读这是正常的。通过抓包你会发现gRPC 的方法路径命名格式非常规整/包名.服务名/方法名。这个规则在排查跨语言调用问题时特别重要如果客户端报Method not found通常就是路径拼写不一致或者.proto文件定义不一致。7. 常见问题与排查思路问题现象常见原因解决思路Connection refused服务端未启动或端口错误检查服务端进程、netstat查看端口监听Method not found客户端与服务端.proto版本不一致对比两边生成的代码确保接口路径一致TLS handshake failed客户端启用了 TLS服务端是明文本地调试统一走usePlaintext()UNAVAILABLE: Network is unreachable网络不通或防火墙拦截检查连通性确认安全组或防火墙放行端口Grpc max message size exceeded消息体超过默认 4MB 限制调整MaxInboundMessageSize和发送方参数protoc 无法生成代码Maven 插件缺少 os-maven-plugin确认已配置extensions和 protocArtifact依赖冲突grpc-netty 与项目其他 Netty 冲突改用grpc-netty-shaded其中消息体大小限制是线上最容易踩的坑。gRPC 默认单条消息上限是 4MB如果业务里传超大对象客户端和服务端都要设置// 服务端 ServerBuilder.forPort(50051) .maxInboundMessageSize(64 * 1024 * 1024) .addService(new UserServiceImpl()); // 客户端 ManagedChannelBuilder.forAddress(127.0.0.1, 50051) .maxInboundMessageSize(64 * 1024 * 1024) .usePlaintext();注意发送方也是有限制的具体参数在不同语言实现中名称略有差异Java 中通过setMaxOutboundMessageSize配置。此外channel.shutdown()记得在所有调用结束后执行否则 Java 进程可能不会正常退出。8. 最佳实践与工程建议8.1 接口设计.proto文件的字段编号一旦发布不要随便修改。Protobuf 依靠字段编号做二进制兼容重排编号会破坏线上协议。字段命名统一使用snake_case与各语言生成代码的转换规则兼容。不要一个.proto文件塞下所有服务的全部消息建议按业务模块拆分公共消息放到独立文件。接口版本可以通过package名区分例如com.company.user.v1和com.company.user.v2避免粗暴破坏兼容性。8.2 传输与安全生产环境必须启用 TLS。gRPC 支持多种安全机制常见的有 TLS、mTLS、以及 Google 的 STS 认证。内部服务间调用如果条件允许优先使用 mTLS 做双向认证。明文usePlaintext()只用于本地开发和测试不能直接部署到线上。为每个服务配置独立的证书和最小权限遵循最小权限原则不要用一个万能证书打天下。8.3 字符串与错误处理尽量避免在 gRPC 服务间传递大量字符串列表能结构化就结构化能用repeated用repeated。服务端通过Status返回业务错误码和描述客户端要统一捕获异常并转换为可读的业务错误不要让底层的StatusRuntimeException直接贯穿到上层。8.4 可观测性接入 gRPC 拦截器Interceptor / Middleware。Java 中有ServerInterceptor和ClientInterceptor可以统一处理日志、Metrics、认证。记录每个调用的方法名、耗时、状态码。如果公司已有 Prometheus可以接入 grpc-java-micrometer 或类似组件埋点。流式调用要特别注意超时和取消。客户端设置了超时时间后服务端要能及时响应取消事件避免连接资源和 goroutine/线程泄漏。8.5 上线流程先灰度再全量。gRPC 多语言互通能力强但升级.proto文件时建议让客户端和服务端采用“先兼容、再淘汰”的策略。修改涉及删除字段或关闭方法时要优先考虑老客户端是否还在调用所有变更应通过测试环境验证后才能进入生产。9. 总结与下一步学习通过这篇文章你已经理解了 HTTP/2 的四大核心特性二进制分帧、多路复用、Header 压缩和服务端推送也掌握了 gRPC 与 HTTP/2 的关系以及 Protobuf 在 gRPC 中的角色。动手层面你完成了一个 Java gRPC 项目包含一元调用和服务端流式调用两种模式并知道了如何抓包观察 HTTP/2 帧、如何处理 4MB 消息限制等高频问题。如果接下来想深入学习建议按下面的路线继续掌握 Protobuf 的字段类型、oneof、map、repeated、枚举和json_name等高级用法。尝试实现双向流式调用理解clientStreaming和bidiStreaming的状态机流转。研究 gRPC 拦截器封装一个自己的日志和鉴权拦截器。对比 gRPC 与 HTTP/3、QUIC 的结合了解 gRPC 在弱网环境下的新方案。如果你的团队还在使用 REST可以尝试把内部接口逐步迁移到 gRPC观察体积、延迟和连接数的变化。网络协议和 RPC 框架的知识看懂只是第一步真正上手写一次双端代码、抓一次包、调一次错才能把概念内化成自己的工程经验。建议你把上面的示例项目跑起来改一改服务端逻辑加一个双向流接口然后重新观察帧的变化。这个过程比看十篇文章都更有价值。