资讯动态

你的gRPC,早就该换官方版了

发布时间:2026/10/1 11:55:06 来源:尧图企业网站定制
在上个月进行技术评审的时候, 架构师提出了一个建议, 他提议把内部的服务之间所使用的通信方式改变为使用gRPC这种协议, 但是在这个提议被提出来之后, 我们组里面没有一个人做出回应。严格来说, 并不是我们完全没有打算去使用它。主要的原因在于, 上一次进行集成工作时留下的糟糕记忆实在太过深刻了。当时, 插件的版本和grpc-netty发生了严重的版本冲突, 与此同时, 第三方开发团队突然停止了更新维护。为了应对情况, 我们不得不自己编写多达两百行代码来重新实现健康检查的功能。然而, 最后的问题却卡在了某个地方整整三天时间都无法解决。在那之后不久, gRPC这三个字母在小组内部基本上就等同于“工期黑洞”的代名词了。在6月10号发生了Boot 4.1发布这件事儿, 这件事情总算是已经翻篇了。为什么 gRPC 在 Java 这个生态圈里面, 一直给人的感觉是“好用, 但是不好安装” 这一种情况存在。谁也不会对gRPC本身所具有的优势提出任何怀疑, 这具体体现在它基于HTTP/2进行了多路复用, 采用了二进制序列化技术, 并且原生支持那四种流模式。在同等的硬件机器上进行比较的话, gRPC的吞吐量能够达到REST结合JSON模式的数值的二倍至三倍之多, 而其延迟时间则仅仅只有该参照模式的三分之一的水平, 这二者构成了鲜明的对比关系。但是呢, 目前在Java这个生态系统里面, gRPC的落地情况一直都不怎么理想。这个问题的关键原因并不在于协议本身有什么毛病, 主要还是在于集成的成本比较高昂:第一, 官方没有提供 Boot 的支持渠道。社区里有一个名为 grpc--boot 的项目虽然可以使用, 但它的维护节奏并不稳定, 在版本升级的过程中经常出现断档的情况。当你升级 Boot 的时候, 它没有同步跟上, 这时你就必须在“回退框架的版本”和“自己维护兼容层”这两个选项当中选择一个。第二点, 基础设施需要依靠自己去进行组装, 健康检查、指标采集、链路追踪、异常处理, 这些 Boot 框架针对 HTTP 协议已经实现了自动完成的流程, 但是换成 gRPC 的话, 所有的配套工作全部都需要通过手动操作来完成。到了第三个方面, 团队成员在一起进行协作的门槛是比较高的。因为.proto文件需要经历编译流程还要进行Stub代码的生 成, 同时还要做到版本的对齐工作。每增加一个服务, 就需要多承担一份维护的成本。把这三个因素加在一起以后, 许多团队在经过详细的成本与效益核算之后, 便选择了放弃尝试。这并不说明gRPC技术存在不好的问题, 实际上真正的原因在于, 为了节省下大约百分之三十的时间延迟开销, 需要投入耗费整整两周的工作工期来进行搭建配置, 从宏观的角度来看这样的一比投入产出账目是严重不划算从而导致人们不得不做出放弃的决定。Boot 4.1怎么解决的总而言之, 应当将gRPC这一项技术整合到官方所提供的自动配置体系之中去。这不仅仅是增加一个项目这么简单, 团队在做的事情是把gRPC的生命周期完整地接入到容器里面去, 具体包含了服务注册、依赖注入、健康检查、指标采集、异常处理这些内容, 全部都是按照Boot的约定优于配置的哲学重做了一遍。增加了三个关键的模块。模块作用-boot--grpc-服务端这边会自动进行配置, 它会去扫描那些带有符号的标记, 并把这些东西进行注册。-boot--grpc-客户端会自动进行相关配置工作, 并且同时还可以支持使用这一符号来做注入操作。-boot-grpc-test提供测试支持的机制, 并且在其内部集成有gRPC协议的相关功能。以下就直接将代码展示出来, 看看彼此之间存在的差异。这是一个关于实战的环节, 其内容是一个订单查询服务, 通过连续三个步骤就可以将其启动并运行。在真实的业务环境之中, 跨语言的协作是非常常见的事情。具体来说, 订单服务是用 Java 语言来开发的, 它对外提供了一个基于 gRPC 的接口。然后呢, 库存服务是使用 Go 语言来编写的, 它会去调用这个 gRPC 接口, 以此来查询订单的状态信息。第一步, 进行Proto契约的定义。// src/main/proto/order.protosyntax proto3;package com.example.order;// 订单查询请求message OrderQueryRequest {int64 order_id 1; // 订单ID}// 订单查询响应message OrderQueryResponse {int64 order_id 1; // 订单IDstring status 2; // 订单状态CREATED/PAID/SHIPPEDint64 total_amount 3; // 金额分int64 created_at 4; // 创建时间戳}service OrderService {rpc Query(OrderQueryRequest) returns (OrderQueryResponse);}第二步服务端实现就一个注解的事import io.grpc.stub.StreamObserver;import org.springframework.grpc.server.service.GrpcService;// GrpcService Spring自动扫描 自动注册到gRPC Server不需要手动ServerBuilderGrpcServicepublic class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {private final OrderRepository orderRepository;// 构造函数注入完全走Spring DI体系public OrderGrpcService(OrderRepository orderRepository) {this.orderRepository orderRepository;}Overridepublic void query(OrderQueryRequest request,StreamObserver responseObserver) {// 从数据库查订单这里省去具体实现Order order orderRepository.findById(request.getOrderId()).orElseThrow(() - new OrderNotFoundException(request.getOrderId()));// 构建Protobuf响应OrderQueryResponse response OrderQueryResponse.newBuilder().setOrderId(order.getId()).setStatus(order.getStatus().name()).setTotalAmount(order.getTotalAmount()).setCreatedAt(order.getCreatedAt().toEpochMilli()).build();// 标准gRPC响应模式onNext发送结果onCompleted告知完成responseObserver.onNext(response);responseObserver.onCompleted();}}对比一下以前的做法// 以前手动管理Server生命周期与Spring IoC割裂Server server ServerBuilder.forPort(9090).addService(new OrderGrpcService()) // 不能走依赖注入.build();server.start();server.awaitTermination();在当下的操作环节里, 仅需要利用一个注解进行标记, 系统便能够自动代劳完成服务的注册工作、端口的绑定任务以及生命周期的管理事项。您所编写的代码内容仅仅表现为一个普通的 Bean 对象, 该对象具备注入任意类型依赖的能力。到了第三步这个阶段, 客户端需要去调用相关功能。import org.springframework.grpc.client.GrpcClient;import org.springframework.stereotype.Service;Servicepublic class OrderQueryClient {// GrpcClient 声明式注入像Autowired一样简单// value对应application.yml中配置的channel地址GrpcClient(order-service)private OrderServiceGrpc.OrderServiceBlockingStub orderStub;public OrderQueryResponse queryOrder(long orderId) {// 构建请求OrderQueryRequest request OrderQueryRequest.newBuilder().setOrderId(orderId).build();// 同步调用也支持异步Stub和FutureStubreturn orderStub.query(request);}}配置文件# application.ymlspring:grpc:server:port: 9090 # gRPC服务端口默认9090client:channels:order-service:address: static://localhost:9090 # 客户端channel地址negotiation-type: plaintext # 开发环境明文生产环境用TLS从前客户端得要手动去管那个Stub还有连接池以及重连逻辑的创建这些事儿, 如今全部都变成了自动配置然后来接管这一切。还有, 额外送了三个东西。以前需要写几百行代码才能完成的工作, 现在被简化了。第一项是统一异常处理机制。以前进行gRPC异常处理的时候是非常痛苦的, on方法的构造方式跟 注解完全对不上, 遇到业务异常还需要手动去进行转换。import io.grpc.Status;import org.springframework.grpc.server.advice.GrpcAdvice;import org.springframework.grpc.server.advice.GrpcExceptionHandler;GrpcAdvice // 对标ControllerAdvice全局拦截gRPC异常public class GrpcExceptionAdvice {GrpcExceptionHandler(OrderNotFoundException.class)public Status handleOrderNotFound(OrderNotFoundException e) {// 业务异常自动转为gRPC Status调用方直接拿到语义化的错误码return Status.NOT_FOUND.withDescription(订单不存在 e.getOrderId()).withCause(e);}GrpcExceptionHandler(Exception.class)public Status handleUnknown(Exception e) {return Status.INTERNAL.withDescription(服务内部异常).withCause(e);}}2. 进行健康检查活动此过程不需要任何代码。在Boot 4.1这个版本里面, 它是自动就把那个对接到gRPC的这个grpc..v1的协议给搞定了。然后像K8s的gRPC健康探针这种功能, 还有服务网格的健康检查这些东西呢, 全部都是已经开箱就可以直接使用的。# 一行命令验证gRPC服务健康状态grpcurl -plaintext localhost:9090 grpc.health.v1.Health/Check# 返回{ status: SERVING }3. 对相关的指标数据进行采集操作, 并对业务链路进行追踪记录。系统实现自动集成, 会将每个调用gRPC方法时的调用次数、延迟分布以及错误率进行自动上报。即使在Async异步方法里面发生的对gRPC的调用行为, 同样能够完成自动传递, 从而确保跨服务之间的调用链路始终处于完整无断开的状态。你需要搞清楚在哪个场合应当使用它, 同时也要明白在哪个场所不要把它用上。该用gRPC的场景继续用REST的场景有一句话要说, 那就是把内部服务之间进行高每秒查询率调用的接口更改为使用gRPC协议来传递信息, 而面向外部客户的接口以及访问频率较低的接口则继续维持原有状态, 使用风格的架构来进行数据传输, 不要采取那种不分青红皂白、全部统一替换的单一化做法。升级前注意三件事从Boot 4.0这个版本, 升级到版本是4.1的这一变动。4.在 0 那个版本里已经被废弃的 API, 到了 4.1 这个版本就被移除了。你先跑一遍编译检查吧, 如果之前你用过那个 grpc--boot 的第三方包, 官方那是提供了一个迁移指南的, 建议你去对照着进行升级操作, 绝对不要让它和原来的版本并存。生产环境记得要把这个给关掉 -type: , 要把 TLS 给启用上。大多数团队并不打算去更换为gRPC。并不是因为他们不明白gRPC运行速度快的特点。而是因为他们被所谓的“集成成本”这个问题。给劝退过太多次了, 因此不再愿意继续尝试。Boot 4.1把启动这件事的成本从两周压缩成只需要增加一个注解那么短。至于剩余的部分, 完全取决于你敢不敢不敢在下一个项目里头, 把它写进技术选型文档的第一行位置。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑