尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring Cloud整合Dubbo实战:微服务高性能RPC调用方案

Spring Cloud整合Dubbo实战:微服务高性能RPC调用方案 直接上结论Spring Cloud 和 Dubbo 不是二选一的关系把它们放在同一个微服务架构里各干各的活儿是一套非常成熟且实战性极强的方案。我最早接触 Dubbo 的时候项目里根本没有 Spring Cloud 什么事儿服务注册发现用的是 Zookeeper配置丢在 Git 上网关也是自己拿 Netty 撸的。后来换了家公司整套技术栈变成了 Spring Cloud Nacos OpenFeign刚开始觉得挺新鲜RestTemplate/Feign 一把梭代码是爽了可 QPS 一上来HTTP 调用的性能问题就暴露了服务间慢调用、超时重试连坐一片。那段时间我天天在想要是能把 Dubbo 的高性能 RPC 能力搬进 Spring Cloud 生态里让 Spring Cloud Gateway 负责入口流量Nacos 做注册配置服务间调用全走 Dubbo岂不是两边的好处都占了后来真的在项目里这么干了而且一跑就是一年多。这套组合解决了我当时最头痛的几个问题高并发链路的响应时间降下来了、服务调用有了版本管理和更细粒度的治理手段、老项目的 Dubbo 接口也能平滑地融进新架构。这篇文章我就把自己从选型、依赖引入、配置编写到上线排坑的完整过程拆开讲适合正在做 Spring Cloud 项目实战、或者被微服务面试题里Dubbo 和 Spring Cloud 区别问住的人。内容偏实操我会把每一步配置、每一个坑和背后的原理都讲透。1. 为什么要在 Spring Cloud 里整合 Dubbo1.1 先搞清楚两套体系各自的定位Spring Cloud 是一个微服务全家桶式生态它提供的不只是服务调用这一个能力而是覆盖了接入层网关Gateway 或 Zuul、服务注册发现Nacos、Eureka、Consul、配置中心Spring Cloud Config、Nacos Config、负载均衡Spring Cloud LoadBalancer、熔断限流Sentinel、Resilience4j等一系列组件。它的服务调用默认走 OpenFeign 或 RestTemplate本质上基于 HTTP 协议传递的数据大多数是 JSON 格式开发体验非常友好接口也直观调试的时候直接用 Postman 就能打。Dubbo 则是一个高性能 RPC 框架它关心的核心问题就是怎么让一个应用调用另一个应用的某个方法像调用本地方法一样快。Dubbo 默认使用 TCP 长连接加自定义协议序列化方式也支持 Hessian2、Kryo 等多种高性能方案所以在同等条件下Dubbo 的调用时延通常明显低于 HTTP 调用吞吐能力也要高出一截。我这里打个比方Spring Cloud 像是一个物业公司管绿化、管保安、管消防、管水电费代缴服务很全面而 Dubbo 像是一条连接两栋楼之间的高速传送带只负责把货物又快又稳地从一个点送到另一个点。物业公司当然也可以自己骑三轮车送货但如果货物量大、时效要求高专用传送带就体现出优势了。这套整合的核心思路就是把两套体系的优势叠加Spring Cloud 的生态能力照单全收网关、配置、监控、微服务治理那些基础设施全部保留服务间的远程调用从 HTTP 切换到 Dubbo 协议让性能敏感链路跑在更快的通道上。注册中心不做两套统一交给 NacosDubbo 的注册发现也通过 Nacos 完成这样运维侧和开发侧的心智模型都收敛到一套体系里。1.2 什么场景下有必要做这种整合不是所有项目都需要把 Dubbo 塞进 Spring Cloud我总结下来适合做这种整合的典型场景主要有三类。第一类是已经在用 Dubbo 的老项目想引入 Spring Cloud 生态做统一网关和配置管理。这样的项目有大量的业务接口是 Dubbo 协议暴露的研发团队对 Dubbo 的调用开发方式已经很熟了如果为了拥抱 Spring Cloud 就把所有接口改成 HTTP 风格等于推倒重来风险和成本都不低。合理的演进路线是保留 Dubbo 服务调用层往上叠加 Spring Cloud 的网关、配置、监控能力。第二类是 Spring Cloud 体系下核心链路 QPS 特别高的项目。我在一个高峰 QPS 过万的服务里实测过OpenFeign 的 HTTP 调用在大量依赖方同时发起请求时每次调用的平均时延会比 Dubbo 高出几十毫秒积少成多整个链路的尾延迟就很难看。把服务间调用下沉到 Dubbo 协议后长连接复用减少了 TCP 频繁建连的开销性能提升非常明显。第三类是团队技术栈本身就需要桥接的项目。比如被并购过来的两个团队一边技术底座是 Dubbo一边是 Spring Cloud两边要互相调用。强行统一协议短期内不现实通过 Nacos 做注册中心把 Dubbo Consumer 和 Spring Cloud 服务同时纳管两边各用自己的客户端做调用是成本最低的过渡办法。1.3 Dubbo 与 OpenFeign 的对比不只是协议差异面试里常问Dubbo 和 Spring Cloud 区别我通常不会只答通信协议不同因为这是最表层的差异。它们背后的设计哲学、面向的开发场景和治理思路都不同对比维度DubboSpring Cloud OpenFeign通信协议默认 Dubbo 协议TCP 长连接 Hessian2也支持 Triple、gRPCHTTP/1.1数据格式通常是 JSON调用性能高长连接复用序列化体积小中等每次请求都有 HTTP 头开销负载均衡内置支持加权随机、轮询、一致性 Hash 等依赖 Spring Cloud LoadBalancer 或原 Ribbon服务治理集群容错、路由、权重、并发控制、版本管理是内置能力治理能力弱需要额外整合组件接口契约以 Java 接口为契约需共享接口描述以 HTTP 接口 数据模型为契约跨语言友好可观测性可通过接入 Sentinel、SkyWalking 等扩展对 Prometheus、Zipkin 等监控方案支持成熟团队上手成本需要理解 RPC 概念、接口 jar 包依赖管控上手快写个 FeignClient 就能调接口看到这个对比你应该能明白为什么很多大型项目最终选了Spring Cloud 管外围Dubbo 管核心这种混合架构。OpenFeign 这类 HTTP 客户端胜在简单和生态完善适合对性能不那么敏感、需要频繁对外暴露的业务场景Dubbo 胜在性能硬核、治理功能原生化适合高并发、强约束的核心调用链路。整合之后两种方式的优点都能利用起来而不是被迫二选一。2. 环境准备与依赖选型2.1 版本选型Spring Boot、Spring Cloud、Dubbo 怎么搭配不出错一上来就踩版本坑的人不在少数。Spring Cloud 和 Spring Boot 的版本对应关系本身就够绕再加一个 Dubbo 和 Spring Cloud Alibaba 的版本约束很容易在启动时报一堆 NoSuchMethodError 或者 ClassNotFoundException。我这边在生产环境验证过一套比较稳妥的组合照抄基本不会错Spring Boot2.7.xSpring Cloud2021.0.xSpring Cloud Alibaba2021.0.5.0Dubbo3.2.xNacos Server2.2.x为什么选 Spring Boot 2.7 而不是 3.x因为 Dubbo 3.2.x 对 Spring Boot 3 的支持虽然也有但如果你是第一次做整合2.7 这条线的资料最多、社区踩坑帖最全遇到问题好排查。Spring Boot 3 是基于 Jakarta EE 的很多老项目和公共组件的包名都要改没必要在最开始就给自己加难度。选 Nacos 而不是 Zookeeper 或者 Eureka原因也简单Nacos 同时具备注册中心和配置中心双重能力能跟 Spring Cloud Alibaba 的生态无缝衔接而且 Dubbo 官方从 2.7 开始就把 Nacos 作为重点支持的注册中心之一dubbo-registry-nacos 这个模块已经非常稳定。如果你用 Eureka 做注册中心Dubbo 也能接但 Eureka 本身只支持服务发现配置中心功能没有还得再引配置组件架构上多了一个中间件要维护。2.2 依赖引入Maven 坐标怎么配最省心先看服务提供者和消费者共用需要的最小依赖集。因为我用的是 Nacos 做注册中心所以 Spring Cloud Alibaba 的 Nacos Discovery 必须要引入否则 Spring Cloud 体系感知不到服务注册Dubbo 这边要引入 dubbo-spring-boot-starter 和 dubbo-registry-nacos让 Dubbo 也把服务注册到同一个 Nacos 上。properties spring-boot.version2.7.18/spring-boot.version spring-cloud.version2021.0.8/spring-cloud.version spring-cloud-alibaba.version2021.0.5.0/spring-cloud-alibaba.version dubbo.version3.2.6/dubbo.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version${spring-cloud.version}/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version${spring-cloud-alibaba.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies !-- Spring Cloud Nacos 注册发现 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency !-- Dubbo Spring Boot 集成 -- dependency groupIdorg.apache.dubbo/groupId artifactIddubbo-spring-boot-starter/artifactId version${dubbo.version}/version /dependency !-- Dubbo 接入 Nacos 注册中心 -- dependency groupIdorg.apache.dubbo/groupId artifactIddubbo-registry-nacos/artifactId version${dubbo.version}/version /dependency /dependencies这里有两个容易被忽略的细节。第一个是dubbo-registry-nacos不能省很多人只引入 dubbo-spring-boot-starter然后配置里写dubbo.registry.addressnacos://localhost:8848启动直接报找不到 NacosRegistryFactory就是因为缺了专门的适配模块。第二个是 Spring Cloud Alibaba 的依赖版本要和 Spring Cloud 主版本匹配用 2021.0.x 对应 2021.0.5.0 版本比较安全。如果你还需要让 Spring Cloud 的 OpenFeign 和 Dubbo 共存可以额外引入spring-cloud-starter-openfeign和spring-cloud-starter-loadbalancer这两个依赖并不冲突关键在于调用方的配置要区分开这个后面专门讲。2.3 注册中心方案对比为什么最终选了 Nacos网上经常有人拿 Zookeeper、Eureka、Consul、Nacos 放一起对比我做了整合之后就再没换过注册中心。拿实际体感来说注册中心Dubbo 支持度Spring Cloud 支持度配置中心能力运维复杂度Zookeeper原生支持经典组合支持但需额外适配弱需要自己实现中依赖 ZK 集群Eureka支持但已停更维护原生支持无低Consul支持支持有但用起来别扭中偏高Nacos官方适配支持度好Spring Cloud Alibaba 原生支持有且能力完善中偏低Nacos 的优势在于双中心合一服务注册发现和配置管理都能做。Dubbo 官方文档里也把 Nacos 作为最推荐的注册中心之一支持 Dubbo 3.x 的应用级服务发现模型这在地址数量多的时候会大大减少推送压力。另一个很实用的点是 Nacos 自带控制台服务上下线、健康状态、配置变更都能可视化查看排查问题比 Zookeeper 敲命令方便太多。3. 核心配置与实现细节3.1 服务提供者配置dubbo 协议端口和注册中心必须说清楚先准备一个服务提供者工程业务上我以订单服务为例。这个服务需要完成三件事把自己注册到 Nacos、把 Dubbo 服务暴露到指定端口、让 Spring Cloud 的其他组件发现它。spring: application: name: order-service cloud: nacos: discovery: server-addr: localhost:8848 namespace: dev dubbo: scan: base-packages: com.example.provider.service protocol: name: dubbo port: -1 registry: address: nacos://localhost:8848 parameters: namespace: dev application: name: order-service consumer: check: falsedubbo.protocol.port: -1这个配置很关键。如果写死一个端口比如 20880那么这台机器上如果部署了多个 Provider 实例后启动的进程就会报端口被占用。写成 -1 表示让 Dubbo 自动从 20880 开始往后找可用端口每创建一个 Provider 就占用一个新的端口。这是本地多实例联调时非常好用的配置。还有一个容易被搞混的点dubbo.application.name和spring.application.name要尽量保持一致。因为 Spring Cloud 服务发现和 Dubbo 注册都挂在 Nacos 上如果两者名字不一致在控制台里会看到同名服务下出现两种注册来源排查问题时非常容易懵。我当时第一次搭环境就吃了这个亏Nacos 服务列表里同一个服务出现了两条记录搞了半天才意识到是名字不一致导致的。3.2 服务消费者配置checkfalse 是新手救命配置消费者这边的配置相对简单但也有必须注意的细节。比如消费者启动时如果它配置的 Dubbo 服务提供者尚未启动默认行为会直接抛异常启动失败。我强烈建议在消费者配置中加上dubbo.consumer.checkfalse让消费者启动时不做 Provider 强校验。这样在本地开发环境先启动消费者、后启动提供者也不会报错。spring: application: name: user-service cloud: nacos: discovery: server-addr: localhost:8848 namespace: dev dubbo: registry: address: nacos://localhost:8848 parameters: namespace: dev application: name: user-service consumer: check: false cloud: subscribed-services: order-service消费者同样要把 Nacos 地址配好同时注意dubbo.cloud.subscribed-services这个配置。它表示当前服务订阅了哪些 Dubbo 服务。如果消费者的工程里同时引入了 Spring Cloud OpenFeign 和 Dubbo不配置这个字段的话Dubbo 会把所有在 Nacos 上注册的 Dubbo 服务都订阅下来在某些场景下会造成不必要的地址推送。我习惯显式列出需要订阅的服务名用逗号分隔清晰又可控。3.3 接口设计与注解使用DubboService 与 DubboReference 的配合Dubbo 的调用方式不是通过 HTTP 路径映射的而是直接以 Java 接口为契约。所以接口本身的定义通常放在一个公共 maven 模块里叫 api 工程或者 interface 工程Provider 和 Consumer 都依赖它。我这里的项目结构大致是这样order-api // 公共接口模块 └── OrderService order-service // 服务提供者 └── OrderServiceImpl user-service // 服务消费者 └── OrderController公共接口定义public interface OrderService { /** * 根据用户ID查询订单列表 */ ListOrderDTO listOrdersByUserId(Long userId); }服务提供者实现类DubboService(timeout 3000, retries 0) public class OrderServiceImpl implements OrderService { Override public ListOrderDTO listOrdersByUserId(Long userId) { // 模拟业务查询 return orderMapper.selectByUserId(userId); } }消费者调用RestController RequestMapping(/order) public class OrderController { DubboReference(check false, timeout 3000, retries 0) private OrderService orderService; GetMapping(/list) public ResultListOrderDTO list(RequestParam Long userId) { ListOrderDTO orders orderService.listOrdersByUserId(userId); return Result.success(orders); } }注意这里用的是DubboService和DubboReference这是 Dubbo 3.x 的新注解取代了旧版Service和Reference。因为Service和 Spring 的Service注解名称相同很容易在 import 时引入错误类我见过不少同事把 Spring 的Service用在 Dubbo 实现类上结果服务根本没有暴露出来。用新注解可以从根源上减少这种混淆。3.4 接口版本管理为什么我一定要加 versionDubbo 的接口定义里非常推荐加版本号这也是和 OpenFeign 一个很大的差异点。OpenFeign 接口一旦发布到处都是调用方你很难优雅升级Dubbo 天然支持多版本共存这为接口的平滑演进提供了很好的手段。比如 OrderService 接口一期版本是1.0.0后来要加新字段但是老的调用方还没改完这时候可以让新实现类用version 2.0.0暴露老的实现类继续以1.0.0提供服务。调用方订阅时指定自己要引用的版本号两边互不影响。等老版本调用方全部升级完再把老的 Provider 下线整个过程不需要停机。DubboService(version 1.0.0) public class OrderServiceImplV1 implements OrderService { ... } DubboService(version 2.0.0) public class OrderServiceImplV2 implements OrderService { ... }调用方指定版本DubboReference(version 2.0.0, check false) private OrderService orderService;这个能力在业务快速迭代的项目里非常实用。不需要额外引入网关做流量切换直接靠 Dubbo 的版本路由就能完成灰度发布的雏形。我当时在做订单中心重构的时候就是靠着这种双版本并存的方式把一个核心接口从旧的实现平滑迁移到了新实现上全程没有让调用方感知到任何异常。4. 实际调用链路的完整编排4.1 一次完整的服务间调用从 Controller 到 RPC 再回到前端前面配置好之后实际上你已经打通了一条完整的调用链路用户请求打到 Spring Cloud Gateway 或直接打到 Consumer 的 ControllerController 通过DubboReference注入的代理对象发起 RPC 调用这个调用通过 Dubbo 协议传到 Provider 对应的实现类上结果原路返回。我用一个最简单的场景来串一遍。用户服务 user-service 收到一个 HTTP 请求/order/list?userId1001它内部需要知道这个用户有哪些订单但订单数据在另一个服务 order-service 里于是 user-service 通过 Dubbo 调用 OrderService.listOrdersByUserId。这个过程对调用方来说就像调用本地方法一样你根本不需要知道 order-service 部署在哪台机器上也不需要手动拼接 HTTP 请求。Dubbo 在底层做的事情比你想的多它一定要从 Nacos 拿到 order-service 的地址列表然后根据负载均衡策略选一台机器建立长连接创建代理对象把方法参数序列化传输过去服务端反序列化后执行真实业务逻辑再把结果序列化传回来。这一整套流程对业务代码完全透明是 Dubbo 这类 RPC 框架最核心的价值点。从实际观察看这套链路在响应时间上比之前 OpenFeign 方案有了明显改善。我们一个核心查询接口在压测环境 200 并发下平均响应时间从 120ms 左右降到了 80ms 左右P99 从 300ms 降到了 180ms性能提升来自于长连接复用和二进制协议的双重作用。4.2 接入 Spring Cloud Gateway入口流量怎么统一走网关实践中我不建议让外部客户端直接请求内部的 Provider 服务而是统一经过 Spring Cloud Gateway 做路由和鉴权。整合之后网关仍然以 HTTP 协议对外提供入口内部再将请求路由到下游 Consumer 服务的 HTTP 接口上再由 Consumer 通过 Dubbo 调用最终的业务 Provider。网关路由配置依然是标准的 Spring Cloud Gateway 配置spring: cloud: gateway: routes: - id: user-service-route uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix1 - id: order-service-route uri: lb://order-service predicates: - Path/api/order/** filters: - StripPrefix1请求http://gateway:8080/api/order/list?userId1001会被网关转发到user-service的/order/list接口然后 user-service 内部再通过 Dubbo 把数据从 order-service 拉回来。对前端来说它只感知到一个统一的网关入口完全不知道后端存在两套远程调用体系。这种多层架构的好处是每一层职责单一网关只做路由、限流、鉴权Consumer 本身也是可以独立对外提供 HTTP API 的微服务Dubbo 则在服务间提供高性能的 RPC 通道。如果未来某个 Consumer 被认为不再需要 HTTP 入口网关可以直接把请求到下线的 Consumer不影响服务间 Dubbo 调用。4.3 服务上下线与 Nacos 的健康检查机制整合之后服务的上下线不再只是 Spring Cloud 或者 Dubbo 单方面的事情而是三者协同的结果。Provider 启动时Spring Cloud 的 Nacos Discovery 模块会把实例注册到 Nacos 的服务列表里同时 Dubbo 的注册模块也会把 Dubbo 服务元数据注册到 Nacos 的对应分组下。服务下线时钩子逻辑会通知 Nacos 摘除实例。这里有一个实际经验如果你改了代码要重启 Provider尽量走优雅停机流程不要直接 kill -9。Dubbo 和 Spring Cloud 在应用关闭时都会注册 shutdown hook它们会主动向 Nacos 注销自己的实例这样 Consumer 侧能尽快感知到地址变更。如果直接 kill -9注册中心要等心跳超时才能摘除坏节点期间 Consumer 可能会把请求路由到已经不可用的 Provider造成调用失败。Nacos 临时实例的心跳默认每 5 秒发一次15 秒没收到心跳会标记为不健康30 秒才会彻底摘除。这意味着强杀服务后最坏情况下要等 30 秒左右才会有恢复正常。对生产环境来说这几十秒的失败调用是不可接受的所以能优雅停机就优雅停机。4.4 超时、重试与线程池Dubbo 消费端的参数调优思路Dubbo 的核心配置参数里超时时间和重试次数最直接影响业务效果。我见过很多生产事故都是因为默认重试机制导致的。Dubbo 默认的retries2表示失败后还会自动重试两次。在接口不是幂等的情况下比如创建订单、扣减库存重试会导致业务被执行多次产生重复数据。我的建议是写操作必须把retries设置为 0读操作可以根据业务容忍度设置 0 或 1。在DubboReference注解中就能配置DubboReference(timeout 5000, retries 0) private OrderWriteService orderWriteService; DubboReference(timeout 3000, retries 1) private OrderQueryService orderQueryService;关于超时时间我一般不设置太长。如果业务接口是查询类2 到 3 秒足够如果是批量处理或报表计算5 到 10 秒也可以接受。不要单纯把超时时间调大来掩盖慢接口的问题那只会让线程池堆积更多请求最后整条链路被拖垮。正确的做法是先定位慢的原因优化 SQL、加缓存、做异步化而不是盲目调大超时。Provider 线程池也是重点。Dubbo 服务端默认的线程池模型是fixed默认 200 个线程。当业务高并发打上来时如果线程池被打满新的请求会进入等待队列超过queues阈值后直接拒绝。你可以根据业务量调整线程池的大小dubbo: provider: threads: 300 queues: 500线程数不是越多越好。线程越多CPU 上下文切换的开销就越大。一般经验是核心业务线程数不超过 CPU 核数的 2 倍再乘 2 到 3 的系数具体要看你的业务是 IO 密集型还是 CPU 密集型。我们是交易链路RPC 调用里夹杂了大量 DB 查询和缓存访问IO 等待时间长所以开 300 线程在 8 核机器上表现不错。5. 常见问题与排查技巧实录5.1 服务已经注册到 Nacos但 Consumer 就是调不通这是整合 Dubbo 后遇到的最常见问题。表现是 Nacos 控制台里能看到 Provider 和 Consumer 都注册了但 Consumer 启动后调用报No provider available for the service或者Address is null。我总结了几种原因按出现概率排序第一Provider 的DubboService注解漏标或者方法所在的类不在dubbo.scan.base-packages扫描范围内。Dubbo Spring Boot Starter 启动时会扫描配置的包路径如果实现类不在这条路径下服务根本不会暴露。排查思路是看 Nacos 中 Dubbo 元数据是否真的写入了如果在 Nacos 服务列表里只看到 Spring Cloud 的 instance说明 Dubbo 侧注册失败了。第二Consumer 和 Provider 的 Nacos namespace 不一致。我早期配置里spring.cloud.nacos.discovery.namespace和dubbo.registry.parameters.namespace分别设成了 dev 和 public两边注册到了不同 namespace互相自然找不到。把两处 namespace 统一后问题就消失了。第三接口的完整限定名不一致。Dubbo 的匹配是靠接口全名加版本号如果 Provider 实现了 A 接口Consumer 引用的却是 B 接口的 jar 包哪怕方法签名相同也会报没有 Provider。用dubbo协议的 URL 时Dubbo 会检查注册表里的接口名是否和当前引用接口一致不一致就过滤掉。5.2 Dubbo 端口冲突怎么处理多个 Provider 进程部署在同一台物理机上时端口冲突几乎是必现的。因为 Dubbo 默认协议端口是 20880两个进程同时用这个端口肯定会有一个启动失败报BindException: Address already in use。解决方案有两个一种是在 provider 配置里把dubbo.protocol.port改成随机端口dubbo: protocol: name: dubbo port: -1另一种是显式给每个进程分配不同端口比较适合本地多实例调试java -jar order-service.jar --dubbo.protocol.port20881线上部署一般一台机器只跑一个 Provider 实例写固定端口问题不大。但如果是本地联调或者部署在同一 Pod 里的多实例容器K8s 下少见随机端口更省心。注意使用随机端口时端口并不会自动写回 Nacos 某个固定字段而是 Dubbo 启动后把选中的端口注册到注册中心所以 Consumer 侧完全不需要关心端口是多少。5.3 Consumer 启动直接失败no provider 的原因和解决办法之前说过dubbo.consumer.checkfalse可以避免启动失败但我想补充一下原理。Dubbo 默认情况下Consumer 启动时会去注册中心找 Provider如果找不到对应的服务它认为这是个无法工作的配置于是抛异常。在持续集成流水线或者本地开发中Provider 和 Consumer 通常不是同时启动的先启动依赖方很常见。在线下环境我建议统一设置checkfalse让服务能独立启动。但在生产环境我反而建议把check设为 true因为生产环境如果 Provider 没注册好Consumer 起来也是空转还不如直接启动失败引起告警让运维意识到服务依赖有问题。还有一种启动不报错但第一次调用报错的情况多出现在 Consumer 启动后立即发起调用的场景。原因是 Nacos 的地址列表还没同步完成Consumer 拿不到可用的 Provider 地址。解决方式是调用前增加一段简单的预热等待或者通过定时状态检查确认服务注册完成后再放量。5.4 Dubbo 与 Feign 共存时的注意点有些小伙伴可能不想一次性把 OpenFeign 全换掉想逐步迁移那系统里就会出现 Dubbo 和 Feign 共存的阶段。这时要特别注意两点。第一是订阅范围。Feign 的调用是走 Spring Cloud LoadBalancer 的它根据spring.cloud.nacos.discovery注册的实例列表做负载均衡Dubbo 的调用则走 Dubbo 注册中心。两套发现机制都正常运行互不干扰。但 Dubbo Consumer 如果不加dubbo.cloud.subscribed-services可能会订阅 Nacos 上所有的 Dubbo 服务地址推送会无意义地消耗一些性能所以建议显式指定。第二是序列化方式。Feign 默认 JSON 传输Dubbo 默认 Hessian2 序列化。如果你的 Dubbo 接口参数里有比较特殊的对象比如包含接口类型或者复杂继承关系的对象Hessian2 有时会序列化失败。这种情况下可以考虑调整 Dubbo 的序列化方式为 Kryo或者尽量保证接口参数对象结构简单不要过度嵌套。5.5 多环境联调时接口参数变化的坑这个是老生常谈但每次都能踩到。Provider 和 Consumer 通过公共接口 jar 包沟通但一旦公共 jar 包里的接口参数对象加了字段Provider 升级了 jar 包Consumer 没有同步升级Hessian2 序列化时会遇到字段对不上的问题。常见表现是没有异常但 Consumer 侧拿到数据后某些新字段是 null或者极端情况下抛SerializationException。解决办法也比较笨但有效公共接口的 pojo 尽量只增不减字段不要改名删除字段前要确保所有调用方都升级完毕。Dubbo 官方也推荐对接口和参数对象做版本管理改动不兼容时直接提升版本号。我在团队里定了一条规矩接口 jar 包必须跟着接口变更一起走 release note任何字段变更都要在文档里说明并且上线的顺序必须是先 Provider 后 Consumer 再网关不能倒过来。6. 我在实际项目中的几点体会文章最后说几个我整合 Dubbo 和 Spring Cloud 后最真实的主观感受算不上总结就是一些经验。第一不要迷信 Dubbo 一定比 Feign 好。Dubbo 在性能和治理能力上确实有优势但它的开发和维护心智成本也高接口版本管理、公共 jar 包依赖、注册中心的复杂度都增加了。如果你的系统 QPS 低、服务间调用不频繁、团队对源码级治理没有强诉求用 OpenFeign 完全够没必要强行引一套新框架进来。第二Nacos 的稳定性是整个混合架构的基石。因为注册中心和配置中心都压在 Nacos 上Nacos 挂掉基本等于全站服务不可用。我强烈建议生产环境部署 Nacos 集群至少三节点并且把 Nacos 自身的数据目录挂到持久化磁盘上避免容器重启丢数据。第三整合的时机很重要。如果是全新项目直接用 Dubbo 或者 Spring Cloud Alibaba 里的全家桶方案是比较顺的如果是老项目迁移先在非核心链路上试点验证调用链路完整之后再逐步扩大范围。一上来就想把全部服务都切过去风险很大。最后日志和链路追踪一定要提前接好。Dubbo 的调用链如果不做埋点排查问题时就像在黑盒子里摸你只看到 Consumer 超时但不知道请求到底卡在哪个 Provider。接入 SkyWalking 或者 Zipkin 这类工具后Dubbo 跨服务调用链会自动串联起来性能瓶颈、异常节点一目了然。我们在整合之后第一时间就把链路追踪补上了后面几乎所有线上问题的定位都靠它。
返回列表