🚀 前言:为什么是 gRPC?
在微服务架构中,RESTful API 虽然通用,但在内部服务调用场景下,它的效率成为了瓶颈(JSON 序列化慢、HTTP/1.1 头部冗余)。
gRPC基于HTTP/2和Protobuf,提供了二进制传输、多路复用、双向流等特性,性能比 REST 高出一个数量级。
本文将带你从 0 构建一个包含普通调用和流式调用的订单服务,并手写拦截器和单元测试。
🛠️ 第一阶段:环境搭建与 IDL 定义
gRPC 的核心是Contract First(契约优先)。我们需要先写.proto文件。
1. Maven 依赖与插件
这是最容易劝退新手的环节。你需要配置protobuf-maven-plugin来自动生成 Java 代码。
<dependencies><dependency><groupId>io.grpc</groupId><artifactId>grpc-netty-shaded</artifactId><version>1.54.0</version></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-protobuf</artifactId><version>1.54.0</version></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-stub</artifactId><version>1.54.0</version></dependency><dependency><groupId>org.apache.tomcat</groupId><artifactId>annotations-api</artifactId><version>6.0.53</version><scope>provided</scope></dependency></dependencies><build><extensions><extension><groupId>kr.motd.maven</groupId><artifactId>os-maven-plugin</artifactId><version>1.7.1</version></extension></extensions><plugins><plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.6.1</version><configuration><protocArtifact>com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}</protocArtifact><pluginId>grpc-java</pluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:1.54.0:exe:${os.detected.classifier}</pluginArtifact></configuration><executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions></plugin></plugins></build>2. 定义order.proto
我们在src/main/proto下创建文件。为了演示深度,我们定义两个接口:一个简单调用,一个服务端流式调用(Server Streaming)。
syntax = "proto3"; package com.example.grpc; option java_multiple_files = true; option java_package = "com.example.grpc.lib"; option java_outer_classname = "OrderProto"; service OrderService { // 1. 简单一元调用:根据 ID 查订单 rpc GetOrder (OrderRequest) returns (OrderResponse); // 2. 服务端流式调用:实时获取订单状态流 rpc WatchOrderStatus (OrderRequest) returns (stream OrderStatus); } message OrderRequest { int32 order_id = 1; } message OrderResponse { int32 order_id = 1; string product_name = 2; double price = 3; } message OrderStatus { string status = 1; string timestamp = 2; }执行mvn compile,你会发现在target/generated-sources下生成了 Java 代码。
💻 第二阶段:服务端实现 (Server)
服务端需要继承生成的ImplBase类。
publicclassOrderServiceImplextendsOrderServiceGrpc.OrderServiceImplBase{// 1. 实现简单调用@OverridepublicvoidgetOrder(OrderRequestrequest,StreamObserver<OrderResponse>responseObserver){System.out.println("收到查询请求 ID: "+request.getOrderId());// 模拟数据库查询OrderResponseresponse=OrderResponse.newBuilder().setOrderId(request.getOrderId()).setProductName("MacBook Pro M3").setPrice(12999.00).build();// 返回数据responseObserver.onNext(response);// 结束调用 (必须调用,否则客户端会一直等待)responseObserver.onCompleted();}// 2. 实现服务端流式调用 (Server Streaming)@OverridepublicvoidwatchOrderStatus(OrderRequestrequest,StreamObserver<OrderStatus>responseObserver){// 模拟订单状态变化:每隔1秒推送一次String[]statuses={"已下单","支付成功","仓库配货","已发货","派送中"};for(Stringstatus:statuses){try{Thread.sleep(500);}catch(InterruptedExceptione){}OrderStatusorderStatus=OrderStatus.newBuilder().setStatus(status).setTimestamp(System.currentTimeMillis()+"").build();// 推送一条流数据responseObserver.onNext(orderStatus);}responseObserver.onCompleted();}}启动 Server:
publicclassGrpcServer{publicstaticvoidmain(String[]args)throwsIOException,InterruptedException{// 添加拦截器 (后面讲)Serverserver=ServerBuilder.forPort(9090).addService(ServerInterceptors.intercept(newOrderServiceImpl(),newAuthInterceptor())).build();server.start();System.out.println("gRPC Server started on port 9090");server.awaitTermination();}}🔌 第三阶段:客户端调用 (Client)
客户端通常使用Stub (存根)来调用。
publicclassGrpcClient{publicstaticvoidmain(String[]args){// 1. 创建通道 (Connection)ManagedChannelchannel=ManagedChannelBuilder.forAddress("localhost",9090).usePlaintext()// 测试环境使用明文传输.build();// 2. 创建 Stub// BlockingStub: 同步阻塞调用OrderServiceGrpc.OrderServiceBlockingStubblockingStub=OrderServiceGrpc.newBlockingStub(channel);// 3. 简单调用OrderResponseresponse=blockingStub.getOrder(OrderRequest.newBuilder().setOrderId(1001).build());System.out.println("查询结果: "+response.getProductName());// 4. 流式调用 (需要用 Iterator)Iterator<OrderStatus>statusIterator=blockingStub.watchOrderStatus(OrderRequest.newBuilder().setOrderId(1001).build());while(statusIterator.hasNext()){OrderStatusstatus=statusIterator.next();System.out.println("当前状态: "+status.getStatus());}channel.shutdown();}}🛡️ 第四阶段:进阶 —— 拦截器 (Interceptor)
在生产环境中,我们不可能裸奔。我们需要拦截器来做Auth 鉴权或日志记录。
gRPC 使用Metadata(类似 HTTP Header) 来传递元数据。
服务端拦截器:验证 Token
publicclassAuthInterceptorimplementsServerInterceptor{privatestaticfinalMetadata.Key<String>TOKEN_KEY=Metadata.Key.of("auth-token",Metadata.ASCII_STRING_MARSHALLER);@Overridepublic<ReqT,RespT>ServerCall.Listener<ReqT>interceptCall(ServerCall<ReqT,RespT>call,Metadataheaders,ServerCallHandler<ReqT,RespT>next){// 获取客户端传来的 TokenStringtoken=headers.get(TOKEN_KEY);if("secret-123".equals(token)){// 验证通过,放行returnnext.startCall(call,headers);}else{// 验证失败,直接关闭连接call.close(Status.UNAUTHENTICATED.withDescription("Token无效"),headers);returnnewServerCall.Listener<>(){};}}}客户端发送 Token:
// 客户端需要自定义一个 CallCredentials 或者使用 ClientInterceptor// 这里使用 MetadataUtils 快速注入OrderServiceGrpc.OrderServiceBlockingStubstub=OrderServiceGrpc.newBlockingStub(channel);Metadatameta=newMetadata();meta.put(Metadata.Key.of("auth-token",Metadata.ASCII_STRING_MARSHALLER),"secret-123");// 将 Metadata 附着在 Stub 上stub=MetadataUtils.attachHeaders(stub,meta);🧪 第五阶段:单元测试 (Test)
这是区分新手和高手的地方。
不要启动真实的 Server 去测,太慢且依赖网络。
gRPC 提供了grpc-testing库,支持InProcessServer (进程内服务器),速度极快。
引入依赖:
<dependency><groupId>io.grpc</groupId><artifactId>grpc-testing</artifactId><version>1.54.0</version><scope>test</scope></dependency>编写 JUnit 5 测试用例:
publicclassOrderServiceTest{privateServerinProcessServer;privateManagedChannelinProcessChannel;privateOrderServiceGrpc.OrderServiceBlockingStubblockingStub;@BeforeEachpublicvoidsetUp()throwsException{// 生成唯一的服务器名称StringserverName=InProcessServerBuilder.generateName();// 1. 启动进程内服务器 (绑定我们实现的 Service)inProcessServer=InProcessServerBuilder.forName(serverName).directExecutor()// 不使用线程池,直接在当前线程执行,方便测试.addService(newOrderServiceImpl()).build().start();// 2. 创建连接到该服务器的 ChannelinProcessChannel=InProcessChannelBuilder.forName(serverName).directExecutor().build();blockingStub=OrderServiceGrpc.newBlockingStub(inProcessChannel);}@AfterEachpublicvoidtearDown(){inProcessChannel.shutdown();inProcessServer.shutdown();}@TestpublicvoidtestGetOrder(){// givenOrderRequestrequest=OrderRequest.newBuilder().setOrderId(888).build();// whenOrderResponseresponse=blockingStub.getOrder(request);// thenassertEquals(888,response.getOrderId());assertEquals("MacBook Pro M3",response.getProductName());}}📝 总结
通过这篇文章,你掌握了 gRPC 开发的完整闭环:
- IDL 定义:
proto文件是核心契约。 - 流式通信:用
StreamObserver实现实时推送。 - 拦截器:用
Metadata实现 Token 鉴权。 - InProcess 测试:不依赖网络的极速单元测试。
gRPC 不仅仅是高性能,更是规范化微服务调用的利器。