分布式 RPC 深度拆解:Dubbo 与 gRPC 底层原理、核心差异与生产级调优实战

0 阅读29分钟

一、RPC核心本质与通用架构

RPC(Remote Procedure Call,远程过程调用)的核心价值,是屏蔽分布式系统中网络通信、序列化、服务寻址、集群容错等底层复杂度,让开发者可以像调用本地方法一样,调用部署在远程节点的服务能力,是分布式微服务架构的核心基础设施。

1.1 RPC调用全流程核心链路

一次完整的RPC调用,本质是一次跨进程的请求-响应交互,核心链路可拆解为6个核心阶段,流程如下:

1.2 RPC通用分层架构

所有成熟的RPC框架,都遵循分层设计理念,核心分为5层,每层职责单一且可扩展:

分层核心职责核心扩展点
服务接口层定义服务对外暴露的接口与方法契约,屏蔽底层实现服务注册、版本控制、分组隔离
代理层为服务接口生成动态代理,封装远程调用的所有细节动态代理实现(JDK/字节码生成)
序列化层实现对象与二进制流的双向转换,解决跨进程数据传输问题序列化协议(Hessian2/Protobuf/Kryo等)
网络传输层实现二进制数据的跨网络可靠传输,处理网络连接、IO读写传输协议(TCP/HTTP2)、IO模型(BIO/NIO/AIO)
集群治理层解决分布式环境下的服务发现、负载均衡、容错降级、流量管控问题注册中心、负载均衡策略、集群容错机制

二、Dubbo 3.x 底层核心原理

Dubbo是阿里开源的Java生态原生RPC框架,历经多年生产验证,3.x版本完成了云原生架构升级,是国内企业级微服务架构的主流选型。

2.1 Dubbo 3.x 整体架构

Dubbo 3.x 采用分层微内核架构,核心层可插拔扩展,整体架构如下:

2.2 核心模块底层原理详解

2.2.1 动态代理层

代理层是RPC框架的入口,Dubbo的代理层核心是ProxyFactory接口,提供两种动态代理实现:

  • Javassist字节码生成:Dubbo默认实现,通过字节码技术直接生成代理类的Class文件,避免JDK动态代理的反射开销,调用性能更高,支持无接口代理。
  • JDK动态代理:基于Java反射机制实现,要求服务必须定义接口,兼容性强,适合对字节码生成有限制的场景。

代理层的核心逻辑,是在消费端发起调用时,拦截所有方法调用,将方法名、参数类型、参数值、调用上下文封装为RpcInvocation请求对象,交给后续链路处理。

2.2.2 序列化层

序列化是RPC性能的核心瓶颈之一,Dubbo提供了全场景的序列化协议适配,核心特性如下:

序列化协议核心特点适用场景
Hessian2Dubbo默认协议,二进制序列化,跨语言兼容,性能稳定,支持对象循环引用常规Java微服务业务场景,默认首选
Protobuf谷歌开源的结构化数据序列化协议,压缩比极高,序列化速度快,强类型IDL,跨语言能力强跨语言微服务、大报文传输、高性能要求场景
Kryo/FSTJava专属序列化协议,性能远超Hessian2,压缩比更高Java生态内的高性能场景,大对象传输
Fastjson2JSON格式序列化,可读性强,跨语言兼容网关透传、调试场景、需要JSON明文的场景

序列化的核心性能指标有两个:序列化/反序列化耗时序列化后的二进制体积,两者直接决定RPC调用的网络开销与CPU开销。

2.2.3 网络传输层

Dubbo 3.x 基于Netty 4.x 实现高性能NIO网络传输,原生支持TCP与HTTP2双协议,核心采用Reactor主从多线程模型:

  • BossGroup:主Reactor线程组,默认线程数为CPU核心数,负责处理客户端的TCP连接请求,完成三次握手后,将连接注册到WorkerGroup。
  • WorkerGroup:从Reactor线程组,默认线程数为CPU核心数*2,负责处理已建立连接的IO读写事件,完成数据的接收与发送。

为了避免业务逻辑阻塞IO线程,Dubbo设计了灵活的线程调度模型Dispatcher,核心实现如下:

  • all:所有请求、响应、心跳、连接事件,全部派发到业务线程池处理,默认策略,适合绝大多数业务场景。
  • direct:请求消息派发到业务线程池,响应消息、心跳事件直接在IO线程处理,减少线程切换开销,适合响应处理逻辑简单的场景。
  • message:仅请求、响应消息派发到业务线程池,心跳、连接断开等事件直接在IO线程处理,是生产环境高并发场景的优选策略。
  • execution:仅请求消息派发到业务线程池,其余所有事件均在IO线程处理,极致减少线程切换,适合业务逻辑耗时稳定的场景。

2.2.4 服务注册与发现

Dubbo 3.x 完成了从接口级服务发现到应用级服务发现的架构升级,彻底对齐云原生Kubernetes的Service模型,核心优势如下:

  • 注册中心压力大幅降低:传统接口级注册,每个服务接口都会生成一条注册数据,应用级注册仅需注册应用维度的一条数据,注册数据量降低90%以上。
  • 云原生适配性更强:与K8s Service、Istio等云原生基础设施无缝对接,无需额外的适配层。
  • 元数据隔离:应用的接口元数据、配置信息统一存储在元数据中心,与注册中心的地址数据解耦,进一步提升注册中心的稳定性。

服务发现的核心流程:服务提供者启动时,将自身的应用地址、端口、元数据信息注册到注册中心;消费者启动时,从注册中心订阅对应应用的地址列表,本地缓存并监听地址变化,实现服务地址的动态感知。

2.2.5 集群容错与负载均衡

分布式环境下,服务调用不可避免会出现网络波动、节点宕机等问题,Dubbo提供了完善的集群容错机制,核心实现如下:

  • Failover:失败自动切换,默认策略,调用失败后自动重试其他节点,适合读操作等幂等性接口,可通过retries参数配置重试次数。
  • Failfast:快速失败,仅发起一次调用,失败立即报错,适合写操作等非幂等性接口,避免重复提交。
  • Failsafe:失败安全,调用出现异常时直接忽略,打印日志不抛出异常,适合日志上报、审计等非核心链路场景。
  • Failback:失败自动恢复,后台记录失败请求,定时重发,适合消息通知、短信发送等最终一致性场景。
  • Forking:并行调用多个节点,只要一个成功就返回结果,适合读操作对实时性要求极高的场景,可通过forks参数配置并行数。
  • Broadcast:广播调用所有节点,所有节点调用完成才返回,任意一个节点失败则调用失败,适合缓存刷新、配置同步等全节点更新场景。

负载均衡是集群流量分发的核心,Dubbo提供了多种高性能负载均衡策略:

  • Random:加权随机,默认策略,按照节点的权重随机分发流量,权重越高的节点被选中的概率越大,流量分发均匀。
  • RoundRobin:加权轮询,按照权重依次分发流量,解决了低权重节点流量饥饿问题,流量分发绝对均匀。
  • LeastActive:最小活跃数优先,优先分发请求到当前处理请求数最少的节点,自动解决慢节点堆积请求的问题,适合业务处理耗时差异大的场景。
  • ConsistentHash:一致性哈希,相同参数的请求始终分发到同一个节点,解决分布式会话、缓存本地化等问题,天然支持节点扩缩容的流量平滑迁移。
  • ShortestResponse:最短响应优先,优先分发请求到平均响应时间最短的节点,极致优化调用耗时,适合对延迟敏感的场景。

三、gRPC 底层核心原理

gRPC是Google开源的高性能、跨语言RPC框架,基于HTTP/2标准协议与Protocol Buffers(Protobuf)序列化协议设计,原生支持流式调用,是云原生、多语言微服务、跨平台服务调用的主流选型。

3.1 gRPC 整体架构

gRPC采用客户端-服务端架构,基于IDL定义服务契约,原生支持多语言代码生成,整体架构如下:

3.2 核心模块底层原理详解

3.2.1 IDL与代码生成

gRPC采用契约优先的设计理念,通过Protobuf IDL(接口定义语言)统一描述服务接口与数据结构,再通过protoc编译器与gRPC插件,生成对应语言的客户端与服务端代码,彻底解决跨语言的接口兼容问题。

Protobuf IDL的核心优势:

  • 强类型约束,提前发现数据结构定义问题,避免运行时类型错误。
  • 天然支持向前向后兼容,字段新增、删除不会影响旧版本服务的正常运行。
  • 一套IDL可生成几乎所有主流编程语言的代码,跨语言能力极强。

3.2.2 序列化层:Protobuf底层原理

Protobuf是gRPC默认的唯一序列化协议,也是gRPC高性能的核心支撑,其底层采用TLV(Tag-Length-Value)存储结构与Varint变长编码,核心原理如下:

  1. Varint变长编码 Varint是一种紧凑的数字编码方式,每个字节的最高位是标志位,1表示后续字节仍属于当前数字,0表示当前字节是数字的最后一个字节。例如:
  • 数字1,二进制为00000001,最高位为0,编码后仅占1个字节。
  • 数字300,二进制为100101100,Varint编码后为10101100 00000010,仅占2个字节,远小于int固定的4个字节。

对于绝大多数业务场景的数字,Varint编码可以将4字节的int压缩到1-2字节,8字节的long压缩到1-4字节,大幅降低序列化后的体积。

  1. TLV存储结构 Protobuf的每个字段都由TagLength(可选)、Value三部分组成:
  • Tag由字段编号(1-536870911)与字段类型组成,字段编号是字段的唯一标识,编码后仅占1个字节(编号1-15),是Protobuf兼容性的核心。
  • Length仅对变长类型字段生效,标识Value的字节长度。
  • Value是字段的实际值,采用对应类型的编码方式存储。

这种存储结构,使得Protobuf可以忽略不认识的字段,天然支持字段的新增与删除,实现向前向后兼容,同时序列化后的体积远小于JSON、XML等文本格式,序列化/反序列化速度是JSON的3-10倍。

3.2.3 网络传输层:HTTP/2核心特性

gRPC底层完全基于HTTP/2协议设计,原生继承了HTTP/2的所有高性能特性,这也是gRPC与传统RPC框架最大的区别:

  1. 二进制分帧 HTTP/2将所有传输数据拆分为二进制帧,帧是最小的传输单位,分为HEADERS帧(存储请求头、响应头)、DATA帧(存储请求体、响应体)等多种类型,二进制格式的解析效率远高于HTTP/1.x的文本格式。
  2. 多路复用 HTTP/2在同一个TCP连接上,可以同时开启多个双向流(Stream),每个流都有唯一的ID,流之间相互独立,互不影响。彻底解决了HTTP/1.x的队头阻塞问题,无需建立多个TCP连接即可实现并行请求,大幅降低网络连接开销。

gRPC的每一次RPC调用,都会对应HTTP/2中的一个独立流,天然支持并行调用,同时流可以动态开启与关闭,资源占用极低。

  1. 头部压缩HPACK HTTP/2采用HPACK算法对请求头、响应头进行压缩,通过静态字典与动态字典,将重复的头部字段替换为索引,大幅降低头部开销。对于RPC调用,请求头中的大量重复字段(如Content-Type、服务名、方法名)可以被极致压缩,进一步降低网络传输开销。
  2. 原生支持流式传输 HTTP/2的流是双向的、持续的数据流,基于此gRPC原生支持4种调用模式:
  • 一元RPC:客户端发送一次请求,服务端返回一次响应,对应传统的RPC调用模式。
  • 服务端流式RPC:客户端发送一次请求,服务端可以持续返回多个响应,适合消息推送、日志实时同步等场景。
  • 客户端流式RPC:客户端可以持续发送多个请求,服务端最终返回一次响应,适合大文件上传、批量数据上报等场景。
  • 双向流式RPC:客户端与服务端可以同时双向持续发送数据,双方的读写完全独立,适合实时聊天、物联网设备数据交互、实时音视频信令传输等场景。
  1. 流量控制与服务器推送 HTTP/2基于滑动窗口实现了端到端的流量控制,避免发送方发送数据过快导致接收方无法处理;同时原生支持服务器推送,服务端可以主动向客户端推送数据,无需客户端发起请求,进一步提升交互效率。

3.2.4 名称解析与负载均衡

gRPC设计了可扩展的名称解析与负载均衡架构,核心分为两个组件:

  • Resolver:名称解析器,负责将服务名称解析为对应的服务端地址列表,默认提供DNSResolver,支持自定义实现对接Consul、Etcd、Nacos等注册中心。

  • LoadBalancer:负载均衡器,负责从地址列表中选择合适的服务端节点发起调用,核心实现包括:

    • PickFirst:默认策略,尝试连接列表中的第一个地址,连接成功则所有请求都使用该连接,连接失败则尝试下一个地址,适合单节点服务场景。
    • RoundRobin:轮询策略,依次向所有可用的服务端节点分发请求,流量均匀分发,适合多节点集群场景。
    • WeightedRoundRobin:加权轮询,按照节点的权重分发流量,权重越高的节点处理的请求越多。
    • LeastRequest:最小请求数优先,优先选择当前正在处理请求数最少的节点,避免慢节点堆积请求。

gRPC的负载均衡是客户端侧实现的,客户端本地缓存服务端地址列表,直接发起点对点调用,无需经过中间代理,减少了网络跳转开销。

四、Dubbo与gRPC核心差异深度对比

对比维度Dubbo 3.xgRPC
底层传输协议原生支持TCP私有协议、HTTP/2协议,TCP协议性能更优完全基于HTTP/2协议设计,协议通用性更强
序列化协议多协议适配,默认Hessian2,支持Protobuf、Kryo、FST、JSON等仅原生支持Protobuf,强绑定,序列化性能极致
服务治理能力企业级全链路服务治理,内置注册中心、配置中心、流量管控、熔断降级、限流、链路追踪等全套能力核心聚焦RPC调用本身,服务治理能力需通过拦截器、第三方组件扩展实现
跨语言能力Java生态原生,其他语言支持有限,跨语言能力较弱原生支持几乎所有主流编程语言,跨语言能力极强
流式调用支持3.x版本基于HTTP/2支持流式调用,能力完善度一般原生深度支持4种流式调用模式,流式场景适配性极强
云原生适配3.x版本完成云原生升级,支持应用级服务发现、K8s、Istio适配云原生原生设计,与K8s、云原生网关、服务网格无缝适配,是CNCF毕业项目
性能表现Java生态内TCP协议场景下,性能优于gRPC;HTTP/2场景与gRPC持平跨语言场景、流式场景、大报文场景下,性能优势明显
适用场景Java生态为主的企业级微服务架构,需要强服务治理能力的业务场景多语言微服务、云原生架构、跨平台服务调用、流式数据传输场景

五、生产级调优实战

5.1 Dubbo 3.x 核心调优策略

5.1.1 序列化调优

  • 常规业务场景,保持默认Hessian2协议即可,无需额外调整。
  • 大报文传输(超过1KB)、高并发场景,切换为Kryo/FST序列化协议,配置如下:
<dubbo:protocol name="dubbo" serialization="kryo"/>
  • 跨语言调用场景,切换为Protobuf序列化协议,配合Dubbo的Protobuf IDL生成代码使用。
  • 禁止在生产环境使用JSON序列化处理核心业务请求,仅用于调试与网关透传场景。

5.1.2 网络层调优

  • Netty线程数调优:BossGroup线程数保持默认CPU核心数即可;WorkerGroup线程数,CPU密集型场景设置为CPU核心数2,IO密集型场景设置为CPU核心数4,配置如下:
<dubbo:protocol name="dubbo" iothreads="8"/>
  • TCP参数调优:开启TCP_NODELAY禁用Nagle算法,减少小包延迟;调整SO_BACKLOG连接队列大小,应对高并发连接场景;开启SO_KEEPALIVE保活机制,清理无效连接,配置如下:
<dubbo:protocol name="dubbo" payload="8388608" accepts="1000">
    <dubbo:parameter key="tcp.nodelay" value="true"/>
    <dubbo:parameter key="so.backlog" value="1024"/>
    <dubbo:parameter key="so.keepalive" value="true"/>
</dubbo:protocol>
  • 报文大小限制:调整payload参数,默认8MB,禁止设置过大,避免大报文导致的OOM与网络阻塞。

5.1.3 线程模型调优

  • 高并发业务场景,优先使用message调度器,仅将请求响应消息派发到业务线程池,减少线程切换开销,配置如下:
<dubbo:protocol name="dubbo" dispatcher="message"/>
  • 业务逻辑耗时极短(小于1ms)的场景,使用direct调度器,极致减少线程切换。
  • 业务线程池调优:核心线程数设置为CPU核心数20,最大线程数设置为CPU核心数50,队列使用SynchronousQueue,拒绝策略使用AbortPolicy,避免队列堆积导致的请求超时,配置如下:
<dubbo:protocol name="dubbo" threadpool="cached" threads="200" queues="0"/>
  • 核心业务与非核心业务使用不同的协议端口,实现线程池隔离,避免非核心业务线程池耗尽影响核心业务。

5.1.4 集群容错调优

  • 读操作、幂等性接口,使用Failover容错策略,重试次数设置为2次,避免过多重试导致的雪崩效应,配置如下:
<dubbo:reference interface="com.jam.demo.service.UserService" retries="2"/>
  • 写操作、非幂等性接口,强制使用Failfast容错策略,禁止重试,避免重复提交。
  • 非核心链路接口,使用Failsafe容错策略,调用失败不影响主流程。
  • 对延迟敏感的读操作场景,使用ShortestResponse负载均衡策略;业务处理耗时差异大的场景,使用LeastActive负载均衡策略;分布式会话、缓存本地化场景,使用ConsistentHash负载均衡策略。

5.1.5 服务发现调优

  • 生产环境强制使用应用级服务发现,降低注册中心压力,配置如下:
<dubbo:application name="demo-provider" registry-mode="instance"/>
  • 调整服务心跳间隔,默认60s,生产环境设置为30s,加快异常节点的剔除速度;调整地址缓存刷新间隔,避免频繁拉取注册中心数据。
  • 注册中心、配置中心、元数据中心使用独立的集群部署,避免单点故障。

5.2 gRPC 核心调优策略

5.2.1 Protobuf序列化调优

  • 字段编号优化:高频使用的字段,编号设置在1-15之间,Tag仅占1个字节;不常用的字段编号设置在16以上,减少序列化体积。
  • 禁止修改已发布字段的编号与类型,避免兼容性问题;新增字段使用可选类型,删除字段保留编号,禁止复用已删除的字段编号。
  • 大字段场景,开启Protobuf的递归消息限制,避免大消息导致的OOM;复用Message.Builder对象,减少对象创建的GC开销。
  • 禁止在Protobuf中嵌套过深的消息结构,最大嵌套层数不超过10层,避免序列化/反序列化栈溢出。

5.2.2 HTTP/2层调优

  • 流控窗口调优:增大HTTP/2的初始流控窗口大小,默认64KB,大报文传输场景设置为1MB,提升大文件传输的吞吐量,配置如下:
ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1"9090)
    .flowControlWindow(1024 * 1024)
    .build();
  • 并发流数调优:调整最大并发流数,默认100,高并发场景设置为1000,避免流数限制导致的请求阻塞,配置如下:
Server server = ServerBuilder.forPort(9090)
    .maxConcurrentCallsPerConnection(1000)
    .addService(new UserServiceImpl())
    .build();
  • KeepAlive调优:开启HTTP/2 KeepAlive机制,设置心跳间隔为30s,超时时间为5s,快速清理无效连接,配置如下:
ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", 9090)
    .keepAliveTime(30, TimeUnit.SECONDS)
    .keepAliveTimeout(5, TimeUnit.SECONDS)
    .keepAliveWithoutCalls(true)
    .build();
  • 最大报文大小调优:调整最大消息大小,默认4MB,大报文场景设置为16MB,避免大消息被拦截,配置如下:
Server server = ServerBuilder.forPort(9090)
    .maxInboundMessageSize(16 * 1024 * 1024)
    .addService(new UserServiceImpl())
    .build();

5.2.3 线程模型调优

  • Netty EventLoopGroup线程数调优:客户端与服务端的EventLoopGroup线程数,CPU密集型场景设置为CPU核心数2,IO密集型场景设置为CPU核心数4,配置如下:
EventLoopGroup bossGroup = new NioEventLoopGroup(4);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
Server server = NettyServerBuilder.forPort(9090)
    .bossEventLoopGroup(bossGroup)
    .workerEventLoopGroup(workerGroup)
    .addService(new UserServiceImpl())
    .build();
  • 业务线程池隔离:禁止在IO线程中执行耗时的业务逻辑,所有业务操作都必须提交到独立的业务线程池执行,避免阻塞IO线程导致的吞吐量下降,配置如下:
ExecutorService businessExecutor = new ThreadPoolExecutor(
    40,
    200,
    60L,
    TimeUnit.SECONDS,
    new SynchronousQueue<>(),
    new ThreadFactory() {
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "grpc-business-thread-" + threadNumber.getAndIncrement());
        }
    },
    new ThreadPoolExecutor.AbortPolicy()
);
Server server = ServerBuilder.forPort(9090)
    .executor(businessExecutor)
    .addService(new UserServiceImpl())
    .build();

5.2.4 负载均衡与重试调优

  • 多节点集群场景,强制使用RoundRobin负载均衡策略,配置如下:
ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1"9090)
    .defaultLoadBalancingPolicy("round_robin")
    .build();
  • 幂等性接口,配置重试策略,设置最大重试次数为2次,重试超时时间与调用超时时间匹配,避免重试风暴,配置通过gRPC的ServiceConfig实现。
  • 开启健康检查机制,客户端自动剔除不可用的服务端节点,避免请求分发到故障节点。

六、完整实战示例

6.1 Dubbo 3.x Spring Boot 实战示例

6.1.1 Maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>dubbo-demo</artifactId>
    <version>1.0.0</version>
    <name>dubbo-demo</name>
    <properties>
        <java.version>17</java.version>
        <dubbo.version>3.2.10</dubbo.version>
        <lombok.version>1.18.30</lombok.version>
        <fastjson2.version>2.0.49</fastjson2.version>
        <guava.version>33.1.0-jre</guava.version>
        <springdoc.version>2.5.0</springdoc.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>kryo</artifactId>
            <version>5.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

6.1.2 服务接口定义

package com.jam.demo.service;

import com.jam.demo.dto.UserDTO;
import com.jam.demo.dto.UserQueryDTO;
import com.jam.demo.common.Result;

import java.util.List;

/**
 * 用户服务RPC接口
 * @author ken
 */
public interface UserService {

    /**
     * 根据用户ID查询用户信息
     * @param userId 用户ID
     * @return 用户信息
     */
    Result<UserDTO> getUserById(Long userId);

    /**
     * 根据条件查询用户列表
     * @param queryDTO 查询条件
     * @return 用户列表
     */
    Result<List<UserDTO>> listUserByCondition(UserQueryDTO queryDTO);

    /**
     * 新增用户信息
     * @param userDTO 用户信息
     * @return 新增结果
     */
    Result<Long> addUser(UserDTO userDTO);
}

6.1.3 数据传输对象定义

package com.jam.demo.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用户数据传输对象
 * @author ken
 */
@Data
@Schema(description = "用户信息DTO")
public class UserDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @Schema(description = "用户ID", example = "1")
    private Long userId;

    @Schema(description = "用户名", example = "jam")
    private String username;

    @Schema(description = "用户昵称", example = "果酱")
    private String nickname;

    @Schema(description = "邮箱", example = "jam@demo.com")
    private String email;

    @Schema(description = "手机号", example = "13800138000")
    private String phone;

    @Schema(description = "创建时间")
    private LocalDateTime createTime;

    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
}
package com.jam.demo.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;

/**
 * 用户查询条件DTO
 * @author ken
 */
@Data
@Schema(description = "用户查询条件DTO")
public class UserQueryDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @Schema(description = "用户名", example = "jam")
    private String username;

    @Schema(description = "用户昵称", example = "果酱")
    private String nickname;

    @Schema(description = "手机号", example = "13800138000")
    private String phone;

    @Schema(description = "页码", example = "1")
    private Integer pageNum = 1;

    @Schema(description = "每页条数", example = "10")
    private Integer pageSize = 10;
}
package com.jam.demo.common;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;

/**
 * 统一响应结果
 * @author ken
 * @param <T> 响应数据类型
 */
@Data
@Schema(description = "统一响应结果")
public class Result<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    @Schema(description = "响应码", example = "200")
    private Integer code;

    @Schema(description = "响应消息", example = "操作成功")
    private String message;

    @Schema(description = "响应数据")
    private T data;

    private Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(200"操作成功", data);
    }

    public static <T> Result<T> success(String message, T data) {
        return new Result<>(200, message, data);
    }

    public static <T> Result<T> fail(Integer code, String message) {
        return new Result<>(code, message, null);
    }

    public static <T> Result<T> fail(String message) {
        return new Result<>(500, message, null);
    }

    public boolean isSuccess() {
        return 200 == this.code;
    }
}

6.1.4 服务提供者实现

package com.jam.demo.service.impl;

import com.jam.demo.dto.UserDTO;
import com.jam.demo.dto.UserQueryDTO;
import com.jam.demo.common.Result;
import com.jam.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.util.StringUtils;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户服务RPC实现类
 * @author ken
 */
@Slf4j
@DubboService(version = "1.0.0", group = "demo", timeout = 3000)
public class UserServiceImpl implements UserService {

    private static final List<UserDTO> USER_DATA = Lists.newArrayList();

    static {
        UserDTO user1 = new UserDTO();
        user1.setUserId(1L);
        user1.setUsername("jam");
        user1.setNickname("果酱");
        user1.setEmail("jam@demo.com");
        user1.setPhone("13800138000");
        user1.setCreateTime(LocalDateTime.now());
        user1.setUpdateTime(LocalDateTime.now());
        USER_DATA.add(user1);

        UserDTO user2 = new UserDTO();
        user2.setUserId(2L);
        user2.setUsername("ken");
        user2.setNickname("Ken");
        user2.setEmail("ken@demo.com");
        user2.setPhone("13900139000");
        user2.setCreateTime(LocalDateTime.now());
        user2.setUpdateTime(LocalDateTime.now());
        USER_DATA.add(user2);
    }

    @Override
    public Result<UserDTO> getUserById(Long userId) {
        log.info("查询用户信息,userId:{}", userId);
        if (userId == null || userId <= 0) {
            return Result.fail("用户ID不能为空");
        }
        UserDTO userDTO = USER_DATA.stream()
                .filter(user -> userId.equals(user.getUserId()))
                .findFirst()
                .orElse(null);
        return Result.success(userDTO);
    }

    @Override
    public Result<List<UserDTO>> listUserByCondition(UserQueryDTO queryDTO) {
        log.info("条件查询用户列表,queryDTO:{}", queryDTO);
        if (queryDTO == null) {
            return Result.success(USER_DATA);
        }
        List<UserDTO> resultList = USER_DATA.stream().filter(user -> {
            boolean match = true;
            if (StringUtils.hasText(queryDTO.getUsername())) {
                match = user.getUsername().contains(queryDTO.getUsername());
            }
            if (match && StringUtils.hasText(queryDTO.getNickname())) {
                match = user.getNickname().contains(queryDTO.getNickname());
            }
            if (match && StringUtils.hasText(queryDTO.getPhone())) {
                match = user.getPhone().contains(queryDTO.getPhone());
            }
            return match;
        }).collect(Collectors.toList());
        return Result.success(resultList);
    }

    @Override
    public Result<Long> addUser(UserDTO userDTO) {
        log.info("新增用户信息,userDTO:{}", userDTO);
        if (userDTO == null) {
            return Result.fail("用户信息不能为空");
        }
        if (!StringUtils.hasText(userDTO.getUsername())) {
            return Result.fail("用户名不能为空");
        }
        boolean exist = USER_DATA.stream()
                .anyMatch(user -> user.getUsername().equals(userDTO.getUsername()));
        if (exist) {
            return Result.fail("用户名已存在");
        }
        Long maxUserId = USER_DATA.stream()
                .map(UserDTO::getUserId)
                .max(Long::compareTo)
                .orElse(0L);
        userDTO.setUserId(maxUserId + 1);
        userDTO.setCreateTime(LocalDateTime.now());
        userDTO.setUpdateTime(LocalDateTime.now());
        USER_DATA.add(userDTO);
        return Result.success(userDTO.getUserId());
    }
}

6.1.5 服务提供者配置文件

spring:
  application:
    name: dubbo-demo-provider
dubbo:
  application:
    name: ${spring.application.name}
    registry-mode: instance
  registry:
    address: nacos://127.0.0.1:8848
    group: demo
  protocol:
    name: dubbo
    port: 20880
    serialization: kryo
    dispatcher: message
    threadpool: cached
    threads: 200
    iothreads: 8
    payload: 8388608
    parameters:
      tcp.nodelay: true
      so.backlog: 1024
      so.keepalive: true
  provider:
    version: 1.0.0
    group: demo
    timeout: 3000
    retries: 0

6.1.6 服务消费者Controller

package com.jam.demo.controller;

import com.jam.demo.dto.UserDTO;
import com.jam.demo.dto.UserQueryDTO;
import com.jam.demo.common.Result;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 用户服务前端控制器
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "用户信息管理接口")
public class UserController {

    @DubboReference(version = "1.0.0", group = "demo", check = false, timeout = 3000, retries = 2)
    private UserService userService;

    @GetMapping("/{userId}")
    @Operation(summary = "根据用户ID查询用户信息", description = "通过用户ID获取用户详细信息")
    public Result<UserDTO> getUserById(
            @Parameter(description = "用户ID", required = true, example = "1")
            @PathVariable Long userId) {
        return userService.getUserById(userId);
    }

    @PostMapping("/list")
    @Operation(summary = "条件查询用户列表", description = "根据查询条件获取用户列表")
    public Result<List<UserDTO>> listUserByCondition(@RequestBody UserQueryDTO queryDTO) {
        return userService.listUserByCondition(queryDTO);
    }

    @PostMapping("/add")
    @Operation(summary = "新增用户", description = "新增用户信息")
    public Result<LongaddUser(@RequestBody UserDTO userDTO) {
        return userService.addUser(userDTO);
    }
}

6.1.7 服务消费者配置文件

spring:
  application:
    name: dubbo-demo-consumer
server:
  port: 8080
dubbo:
  application:
    name: ${spring.application.name}
    registry-mode: instance
  registry:
    address: nacos://127.0.0.1:8848
    group: demo
  consumer:
    version: 1.0.0
    group: demo
    timeout: 3000
    retries: 2
    check: false
springdoc:
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true
    path: /swagger-ui.html

6.2 gRPC Java 实战示例

6.2.1 Maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>grpc-demo</artifactId>
    <version>1.0.0</version>
    <name>grpc-demo</name>
    <properties>
        <java.version>17</java.version>
        <grpc.version>1.65.1</grpc.version>
        <protobuf.version>3.25.3</protobuf.version>
        <lombok.version>1.18.30</lombok.version>
        <guava.version>33.1.0-jre</guava.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</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:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                    <protoSourceRoot>src/main/proto</protoSourceRoot>
                    <outputDirectory>src/main/java</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

6.2.2 Protobuf IDL定义

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.jam.demo.grpc";
option java_outer_classname = "UserServiceProto";
option objc_class_prefix = "USR";

package user;

import "google/protobuf/timestamp.proto";

// 用户信息
message User {
  int64 user_id = 1;
  string username = 2;
  string nickname = 3;
  string email = 4;
  string phone = 5;
  google.protobuf.Timestamp create_time = 6;
  google.protobuf.Timestamp update_time = 7;
}

// 用户查询请求
message UserQueryRequest {
  int64 user_id = 1;
}

// 用户列表查询请求
message UserListQueryRequest {
  string username = 1;
  string nickname = 2;
  string phone = 3;
  int32 page_num = 4;
  int32 page_size = 5;
}

// 用户新增请求
message UserAddRequest {
  string username = 1;
  string nickname = 2;
  string email = 3;
  string phone = 4;
}

// 通用响应
message CommonResponse {
  int32 code = 1;
  string message = 2;
  User data = 3;
}

// 用户列表响应
message UserListResponse {
  int32 code = 1;
  string message = 2;
  repeated User data = 3;
}

// 用户新增响应
message UserAddResponse {
  int32 code = 1;
  string message = 2;
  int64 user_id = 3;
}

// 用户服务定义
service UserService {
  // 根据用户ID查询用户信息
  rpc GetUserById(UserQueryRequest) returns (CommonResponse);
  // 条件查询用户列表
  rpc ListUserByCondition(UserListQueryRequest) returns (UserListResponse);
  // 新增用户信息
  rpc AddUser(UserAddRequest) returns (UserAddResponse);
}

6.2.3 gRPC服务实现类

package com.jam.demo.service.impl;

import com.google.protobuf.Timestamp;
import com.jam.demo.grpc.*;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import com.google.common.collect.Lists;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.stream.Collectors;

/**
 * gRPC用户服务实现类
 * @author ken
 */
@Slf4j
public class UserGrpcServiceImpl extends UserServiceGrpc.UserServiceImplBase {

    private static final List<User> USER_DATA = Lists.newArrayList();

    static {
        Timestamp now = Timestamp.newBuilder()
                .setSeconds(Instant.now().getEpochSecond())
                .setNanos(Instant.now().getNano())
                .build();
        User user1 = User.newBuilder()
                .setUserId(1L)
                .setUsername("jam")
                .setNickname("果酱")
                .setEmail("jam@demo.com")
                .setPhone("13800138000")
                .setCreateTime(now)
                .setUpdateTime(now)
                .build();
        USER_DATA.add(user1);

        User user2 = User.newBuilder()
                .setUserId(2L)
                .setUsername("ken")
                .setNickname("Ken")
                .setEmail("ken@demo.com")
                .setPhone("13900139000")
                .setCreateTime(now)
                .setUpdateTime(now)
                .build();
        USER_DATA.add(user2);
    }

    @Override
    public void getUserById(UserQueryRequest request, StreamObserver<CommonResponse> responseObserver) {
        log.info("gRPC查询用户信息,userId:{}", request.getUserId());
        CommonResponse.Builder responseBuilder = CommonResponse.newBuilder();
        try {
            long userId = request.getUserId();
            if (userId <= 0) {
                responseBuilder.setCode(500).setMessage("用户ID不能为空");
                responseObserver.onNext(responseBuilder.build());
                responseObserver.onCompleted();
                return;
            }
            User user = USER_DATA.stream()
                    .filter(u -> userId == u.getUserId())
                    .findFirst()
                    .orElse(null);
            responseBuilder.setCode(200).setMessage("操作成功");
            if (user != null) {
                responseBuilder.setData(user);
            }
        } catch (Exception e) {
            log.error("查询用户信息异常", e);
            responseBuilder.setCode(500).setMessage("系统异常");
        }
        responseObserver.onNext(responseBuilder.build());
        responseObserver.onCompleted();
    }

    @Override
    public void listUserByCondition(UserListQueryRequest request, StreamObserver<UserListResponse> responseObserver) {
        log.info("gRPC条件查询用户列表,request:{}", request);
        UserListResponse.Builder responseBuilder = UserListResponse.newBuilder();
        try {
            List<User> resultList = USER_DATA.stream().filter(user -> {
                boolean match = true;
                if (StringUtils.hasText(request.getUsername())) {
                    match = user.getUsername().contains(request.getUsername());
                }
                if (match && StringUtils.hasText(request.getNickname())) {
                    match = user.getNickname().contains(request.getNickname());
                }
                if (match && StringUtils.hasText(request.getPhone())) {
                    match = user.getPhone().contains(request.getPhone());
                }
                return match;
            }).collect(Collectors.toList());
            responseBuilder.setCode(200).setMessage("操作成功").addAllData(resultList);
        } catch (Exception e) {
            log.error("查询用户列表异常", e);
            responseBuilder.setCode(500).setMessage("系统异常");
        }
        responseObserver.onNext(responseBuilder.build());
        responseObserver.onCompleted();
    }

    @Override
    public void addUser(UserAddRequest request, StreamObserver<UserAddResponse> responseObserver) {
        log.info("gRPC新增用户信息,request:{}", request);
        UserAddResponse.Builder responseBuilder = UserAddResponse.newBuilder();
        try {
            if (!StringUtils.hasText(request.getUsername())) {
                responseBuilder.setCode(500).setMessage("用户名不能为空");
                responseObserver.onNext(responseBuilder.build());
                responseObserver.onCompleted();
                return;
            }
            boolean exist = USER_DATA.stream()
                    .anyMatch(user -> user.getUsername().equals(request.getUsername()));
            if (exist) {
                responseBuilder.setCode(500).setMessage("用户名已存在");
                responseObserver.onNext(responseBuilder.build());
                responseObserver.onCompleted();
                return;
            }
            long maxUserId = USER_DATA.stream()
                    .map(User::getUserId)
                    .max(Long::compareTo)
                    .orElse(0L);
            long newUserId = maxUserId + 1;
            Timestamp now = Timestamp.newBuilder()
                    .setSeconds(Instant.now().getEpochSecond())
                    .setNanos(Instant.now().getNano())
                    .build();
            User newUser = User.newBuilder()
                    .setUserId(newUserId)
                    .setUsername(request.getUsername())
                    .setNickname(request.getNickname())
                    .setEmail(request.getEmail())
                    .setPhone(request.getPhone())
                    .setCreateTime(now)
                    .setUpdateTime(now)
                    .build();
            USER_DATA.add(newUser);
            responseBuilder.setCode(200).setMessage("操作成功").setUserId(newUserId);
        } catch (Exception e) {
            log.error("新增用户信息异常", e);
            responseBuilder.setCode(500).setMessage("系统异常");
        }
        responseObserver.onNext(responseBuilder.build());
        responseObserver.onCompleted();
    }
}

6.2.4 gRPC服务端启动类

package com.jam.demo;

import com.jam.demo.service.impl.UserGrpcServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * gRPC服务端启动类
 * @author ken
 */
@Slf4j
@SpringBootApplication
public class GrpcServerApplication {

    private Server grpcServer;
    private static final int GRPC_PORT = 9090;

    public static void main(String[] args) {
        SpringApplication.run(GrpcServerApplication.class, args);
    }

    @PostConstruct
    public void startGrpcServer() throws Exception {
        ExecutorService businessExecutor = new ThreadPoolExecutor(
                40,
                200,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000),
                new ThreadFactory() {
                    private final AtomicInteger threadNumber = new AtomicInteger(1);
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "grpc-business-thread-" + threadNumber.getAndIncrement());
                    }
                },
                new ThreadPoolExecutor.AbortPolicy()
        );
        grpcServer = ServerBuilder.forPort(GRPC_PORT)
                .executor(businessExecutor)
                .maxInboundMessageSize(16 * 1024 * 1024)
                .maxConcurrentCallsPerConnection(1000)
                .addService(new UserGrpcServiceImpl())
                .build()
                .start();
        log.info("gRPC server started on port {}", GRPC_PORT);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            log.info("shutting down gRPC server");
            stopGrpcServer();
        }));
    }

    @PreDestroy
    public void stopGrpcServer() {
        if (grpcServer != null) {
            grpcServer.shutdown();
        }
    }
}

6.2.5 gRPC客户端Controller

package com.jam.demo.controller;

import com.jam.demo.grpc.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * gRPC用户服务前端控制器
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/grpc/user")
@Tag(name = "gRPC用户管理", description = "gRPC用户信息管理接口")
public class GrpcUserController {

    private final ManagedChannel channel;
    private final UserServiceGrpc.UserServiceBlockingStub userServiceStub;

    public GrpcUserController() {
        this.channel = ManagedChannelBuilder.forAddress("127.0.0.1"9090)
                .usePlaintext()
                .defaultLoadBalancingPolicy("round_robin")
                .flowControlWindow(1024 * 1024)
                .keepAliveTime(30, TimeUnit.SECONDS)
                .keepAliveTimeout(5, TimeUnit.SECONDS)
                .keepAliveWithoutCalls(true)
                .build();
        this.userServiceStub = UserServiceGrpc.newBlockingStub(channel);
    }

    @GetMapping("/{userId}")
    @Operation(summary = "根据用户ID查询用户信息", description = "通过gRPC调用获取用户详细信息")
    public CommonResponse getUserById(
            @Parameter(description = "用户ID", required = true, example = "1")
            @PathVariable Long userId) {
        UserQueryRequest request = UserQueryRequest.newBuilder().setUserId(userId).build();
        return userServiceStub.getUserById(request);
    }

    @PostMapping("/list")
    @Operation(summary = "条件查询用户列表", description = "通过gRPC调用获取用户列表")
    public UserListResponse listUserByCondition(@RequestBody UserListQueryRequest request) {
        return userServiceStub.listUserByCondition(request);
    }

    @PostMapping("/add")
    @Operation(summary = "新增用户", description = "通过gRPC调用新增用户信息")
    public UserAddResponse addUser(@RequestBody UserAddRequest request) {
        return userServiceStub.addUser(request);
    }
}

七、生产环境常见坑点与避坑指南

7.1 Dubbo 常见坑点

  1. 超时时间配置优先级问题 Dubbo超时时间配置优先级从高到低为:方法级 > 接口级 > 消费者全局 > 提供者全局,生产环境需明确配置方法级超时时间,避免全局配置覆盖导致的超时时间不符合预期。非幂等接口必须关闭重试,避免重复提交。
  2. 序列化版本兼容问题 Java序列化对象新增字段后,必须显式声明serialVersionUID,否则反序列化会出现InvalidClassException异常;Hessian2序列化不支持对象循环引用,需提前处理循环依赖的对象。
  3. 线程池隔离问题 核心业务与非核心业务必须使用不同的协议端口,实现线程池隔离,避免非核心业务耗时过长耗尽线程池,导致核心业务无法处理请求。禁止在业务代码中使用ThreadLocal,Dubbo的线程池是共享的,会导致ThreadLocal数据错乱。
  4. 注册中心集群故障问题 生产环境必须开启Dubbo的本地地址缓存,即使注册中心宕机,消费者仍可通过本地缓存的地址列表调用服务,避免注册中心单点故障导致整个集群不可用。

7.2 gRPC 常见坑点

  1. Protobuf字段兼容问题 禁止修改已发布字段的编号与类型,否则会导致反序列化失败;删除字段必须保留编号,禁止复用已删除的字段编号,避免新旧版本服务数据错乱。字段编号1-15仅用于高频字段,避免浪费。
  2. IO线程阻塞问题 禁止在gRPC的IO线程中执行耗时的业务逻辑、数据库操作、网络调用,必须提交到独立的业务线程池执行,否则会导致IO线程阻塞,吞吐量急剧下降,甚至出现请求超时。
  3. HTTP/2流控与连接管理问题 大报文传输场景必须增大流控窗口大小,否则会导致传输吞吐量极低;长连接场景必须开启KeepAlive机制,避免防火墙清理空闲连接导致的请求失败。客户端必须复用Channel对象,禁止每次请求都创建新的Channel,否则会导致TCP连接耗尽。
  4. 流式调用内存泄漏问题 流式调用场景下,必须正确处理StreamObserver的onError与onCompleted事件,及时释放资源,否则会导致内存泄漏;客户端与服务端的流数量必须限制,避免流数量过多导致OOM。

八、总结

RPC框架是分布式微服务架构的核心基础设施,Dubbo与gRPC作为当前最主流的两款RPC框架,各有其核心优势与适用场景。

Dubbo 3.x 深度适配Java生态,提供了企业级全链路的服务治理能力,在Java为主的微服务架构中,有着天然的优势,适合需要强服务治理、复杂业务场景的企业级应用。

gRPC基于HTTP/2与Protobuf设计,跨语言能力极强,原生支持流式调用,深度适配云原生架构,适合多语言微服务、跨平台服务调用、实时流式数据传输的场景。

在实际选型中,需根据业务的技术栈、场景需求、团队能力综合判断,同时掌握框架的底层原理与调优策略,才能充分发挥RPC框架的性能,构建稳定、高性能的分布式微服务系统。