一、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提供了全场景的序列化协议适配,核心特性如下:
| 序列化协议 | 核心特点 | 适用场景 |
| Hessian2 | Dubbo默认协议,二进制序列化,跨语言兼容,性能稳定,支持对象循环引用 | 常规Java微服务业务场景,默认首选 |
| Protobuf | 谷歌开源的结构化数据序列化协议,压缩比极高,序列化速度快,强类型IDL,跨语言能力强 | 跨语言微服务、大报文传输、高性能要求场景 |
| Kryo/FST | Java专属序列化协议,性能远超Hessian2,压缩比更高 | Java生态内的高性能场景,大对象传输 |
| Fastjson2 | JSON格式序列化,可读性强,跨语言兼容 | 网关透传、调试场景、需要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变长编码,核心原理如下:
- 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字节,大幅降低序列化后的体积。
- TLV存储结构Protobuf的每个字段都由
Tag、Length(可选)、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框架最大的区别:
- 二进制分帧HTTP/2将所有传输数据拆分为二进制帧,帧是最小的传输单位,分为HEADERS帧(存储请求头、响应头)、DATA帧(存储请求体、响应体)等多种类型,二进制格式的解析效率远高于HTTP/1.x的文本格式。
- 多路复用HTTP/2在同一个TCP连接上,可以同时开启多个双向流(Stream),每个流都有唯一的ID,流之间相互独立,互不影响。彻底解决了HTTP/1.x的队头阻塞问题,无需建立多个TCP连接即可实现并行请求,大幅降低网络连接开销。
gRPC的每一次RPC调用,都会对应HTTP/2中的一个独立流,天然支持并行调用,同时流可以动态开启与关闭,资源占用极低。
- 头部压缩HPACKHTTP/2采用HPACK算法对请求头、响应头进行压缩,通过静态字典与动态字典,将重复的头部字段替换为索引,大幅降低头部开销。对于RPC调用,请求头中的大量重复字段(如Content-Type、服务名、方法名)可以被极致压缩,进一步降低网络传输开销。
- 原生支持流式传输HTTP/2的流是双向的、持续的数据流,基于此gRPC原生支持4种调用模式:
- 一元RPC:客户端发送一次请求,服务端返回一次响应,对应传统的RPC调用模式。
- 服务端流式RPC:客户端发送一次请求,服务端可以持续返回多个响应,适合消息推送、日志实时同步等场景。
- 客户端流式RPC:客户端可以持续发送多个请求,服务端最终返回一次响应,适合大文件上传、批量数据上报等场景。
- 双向流式RPC:客户端与服务端可以同时双向持续发送数据,双方的读写完全独立,适合实时聊天、物联网设备数据交互、实时音视频信令传输等场景。
- 流量控制与服务器推送HTTP/2基于滑动窗口实现了端到端的流量控制,避免发送方发送数据过快导致接收方无法处理;同时原生支持服务器推送,服务端可以主动向客户端推送数据,无需客户端发起请求,进一步提升交互效率。
3.2.4 名称解析与负载均衡
gRPC设计了可扩展的名称解析与负载均衡架构,核心分为两个组件:
- Resolver:名称解析器,负责将服务名称解析为对应的服务端地址列表,默认提供DNSResolver,支持自定义实现对接Consul、Etcd、Nacos等注册中心。
- LoadBalancer:负载均衡器,负责从地址列表中选择合适的服务端节点发起调用,核心实现包括:
- PickFirst:默认策略,尝试连接列表中的第一个地址,连接成功则所有请求都使用该连接,连接失败则尝试下一个地址,适合单节点服务场景。
- RoundRobin:轮询策略,依次向所有可用的服务端节点分发请求,流量均匀分发,适合多节点集群场景。
- WeightedRoundRobin:加权轮询,按照节点的权重分发流量,权重越高的节点处理的请求越多。
- LeastRequest:最小请求数优先,优先选择当前正在处理请求数最少的节点,避免慢节点堆积请求。
gRPC的负载均衡是客户端侧实现的,客户端本地缓存服务端地址列表,直接发起点对点调用,无需经过中间代理,减少了网络跳转开销。
四、Dubbo与gRPC核心差异深度对比
| 对比维度 | Dubbo 3.x | gRPC |
| 底层传输协议 | 原生支持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<Long> addUser(@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 常见坑点
- 超时时间配置优先级问题Dubbo超时时间配置优先级从高到低为:方法级 > 接口级 > 消费者全局 > 提供者全局,生产环境需明确配置方法级超时时间,避免全局配置覆盖导致的超时时间不符合预期。非幂等接口必须关闭重试,避免重复提交。
- 序列化版本兼容问题Java序列化对象新增字段后,必须显式声明
serialVersionUID,否则反序列化会出现InvalidClassException异常;Hessian2序列化不支持对象循环引用,需提前处理循环依赖的对象。 - 线程池隔离问题核心业务与非核心业务必须使用不同的协议端口,实现线程池隔离,避免非核心业务耗时过长耗尽线程池,导致核心业务无法处理请求。禁止在业务代码中使用ThreadLocal,Dubbo的线程池是共享的,会导致ThreadLocal数据错乱。
- 注册中心集群故障问题生产环境必须开启Dubbo的本地地址缓存,即使注册中心宕机,消费者仍可通过本地缓存的地址列表调用服务,避免注册中心单点故障导致整个集群不可用。
7.2 gRPC 常见坑点
- Protobuf字段兼容问题禁止修改已发布字段的编号与类型,否则会导致反序列化失败;删除字段必须保留编号,禁止复用已删除的字段编号,避免新旧版本服务数据错乱。字段编号1-15仅用于高频字段,避免浪费。
- IO线程阻塞问题禁止在gRPC的IO线程中执行耗时的业务逻辑、数据库操作、网络调用,必须提交到独立的业务线程池执行,否则会导致IO线程阻塞,吞吐量急剧下降,甚至出现请求超时。
- HTTP/2流控与连接管理问题大报文传输场景必须增大流控窗口大小,否则会导致传输吞吐量极低;长连接场景必须开启KeepAlive机制,避免防火墙清理空闲连接导致的请求失败。客户端必须复用Channel对象,禁止每次请求都创建新的Channel,否则会导致TCP连接耗尽。
- 流式调用内存泄漏问题流式调用场景下,必须正确处理StreamObserver的onError与onCompleted事件,及时释放资源,否则会导致内存泄漏;客户端与服务端的流数量必须限制,避免流数量过多导致OOM。
八、总结
RPC框架是分布式微服务架构的核心基础设施,Dubbo与gRPC作为当前最主流的两款RPC框架,各有其核心优势与适用场景。
Dubbo 3.x 深度适配Java生态,提供了企业级全链路的服务治理能力,在Java为主的微服务架构中,有着天然的优势,适合需要强服务治理、复杂业务场景的企业级应用。
gRPC基于HTTP/2与Protobuf设计,跨语言能力极强,原生支持流式调用,深度适配云原生架构,适合多语言微服务、跨平台服务调用、实时流式数据传输的场景。
在实际选型中,需根据业务的技术栈、场景需求、团队能力综合判断,同时掌握框架的底层原理与调优策略,才能充分发挥RPC框架的性能,构建稳定、高性能的分布式微服务系统。