MateCloud 为什么在 Spring Cloud 里,把 RPC 交给了 Dubbo
2026/7/27 16:56:03 网站建设 项目流程

在 Spring Cloud 的地盘上,
我为什么把 RPC 交给 Dubbo

Dubbo 3.3 · TRI · 一个内部调用到底该长什么样

先花两分钟科普,再聊选型 · 附 MateCloud 真实配置与代码

几乎所有 Spring Cloud 教程都默认一件事:服务间调用 = OpenFeign。

MateCloud 偏偏没这么做,它把服务间调用交给了 Dubbo。这不是念旧,也不是为了简历上多写一行。它背后是一个我想认真聊聊的问题——两个服务之间的一次调用,到底该长什么样。不过在聊选型之前,得先把 Dubbo 是什么说清楚。

01

先花两分钟:Dubbo 到底是个啥

已经熟 Dubbo 的朋友,可以直接跳到第 02 节。这里只给还不熟的读者铺个底。

一句话:Dubbo 是一个 RPC 框架。RPC 是 Remote Procedure Call(远程过程调用)的缩写,说人话就是——让你「调用另一台机器上的一个方法」,写起来跟「调用本地的一个方法」几乎一样。你只管写userService.getById(id),它在背后帮你把参数打包、找到对方机器、发过去、再把结果拿回来。

一次 Dubbo 调用里有三个角色,记住它们,后面就好懂了:

  • Provider(提供者)

    :真正干活、对外提供服务的一方。

  • Consumer(消费者)

    :发起调用、要结果的一方。

  • Registry(注册中心)

    :一本「服务通讯录」。Provider 启动时把「我是谁、我在哪」登记进去;Consumer 要调用前,先来这儿查到地址,再直连过去。MateCloud 用 Nacos 兼任这个角色。

那它和 Spring Cloud 里最常见的 OpenFeign 是什么关系?先说个很多人没留意的出身问题——它俩其实来自两个不同的「家族」。

组件

出身 · 所属家族

OpenFeign

源自 Netflix OSS,后由 Spring 官方接手,如今是Spring Cloud 官方全家桶的一员(spring-cloud-openfeign)

Dubbo

阿里巴巴开源,2018 年捐给 Apache、2019 年成为Apache 顶级项目;在 Spring Cloud 世界里经Spring Cloud Alibaba融入

这点值得多说一句:MateCloud 整套走的是Spring Cloud Alibaba这一系——Nacos 管注册与配置、Dubbo 管 RPC、Sentinel 管限流、Seata 管事务。Dubbo 本就是这个家族的正式成员,所以「在 Spring Cloud 的地盘上用 Dubbo」一点都不违和,它俩本来就是同门;反倒是 Netflix 那套老全家桶(Eureka / Feign / Ribbon / Hystrix)如今大多进了维护期。(所以标题那句「Spring Cloud 的地盘」,其实是句玩笑话——Dubbo 是这地盘上的正牌住户。)

抛开出身,两者解决的是同一个问题——服务之间怎么互相调用。区别在「用什么姿势」:Feign 本质是发一个 HTTP 请求(REST 风格),Dubbo 走的是 RPC。这就引出了本文真正想聊的——同样是服务间调用,这两种姿势差在哪,为什么 MateCloud 选了后者。

02

先亮观点:内部调用,不该拿 REST 的账单来结

Feign 的做法,是把一次内部调用包装成一次 HTTP 请求:拼 URL、塞 header、发出去、再按状态码解释结果。REST 这套东西是给「谁都能调、跨团队、跨公网」的开放接口设计的,它的复杂度换来的是通用性、可缓存、无状态这些好处。

可服务和服务之间的调用不是这种场景。它是东西向流量,双方同属一个系统、共享一套版本、跑在同一张内网里。你付了 REST 的税,却几乎享受不到 REST 的任何红利。Dubbo 的思路正相反:一次远程调用,就该像一次本地方法调用。

先把话说在前面,免得你以为我要吹性能——性能不是我选 Dubbo 的理由。TRI 协议确实比 HTTP/1.1 + JSON 快,但在大多数团队的真实流量下,这点差距淹没在数据库和网络抖动里,属于基准测试上好看、生产环境无感的那类优势。真正让我下决心的,是另一件更朴素的事——契约。

03

契约这件事,Feign 输在起跑线

Feign 的契约,本质是注解里的一个 URL 字符串。provider 悄悄把返回体删了个字段、改了个路径,consumer 这边照样编译通过、照样打包上线——直到某个请求真的打过去,才在生产环境里 404 或反序列化失败。错误被推迟到了运行时,而且是别人的运行时。

MateCloud 把所有跨服务接口收在一个独立模块mate-api里,provider 和 consumer 都编译期依赖它:

provider 用@DubboService实现它,consumer 用@DubboReference引用它,两边带上同一个 version + group。现在,provider 改一个方法签名,consumer 立刻编译不过——CI 上一个红叉,比 Grafana 上一条变红的曲线,便宜太多了。

顺带说个细节:接口上那个@CrossTenantRpc不是装饰。多租户系统里,跨租户查询是危险动作,MateCloud 要求你在契约上就把它标出来,没标的调用默认按「带租户」处理。契约不只描述形状,也顺手把安全语义钉死了。

04

TRI:Dubbo 甩掉「Java 孤岛」那顶帽子

很多人对 Dubbo 的印象还停在老版本:一个私有的二进制协议,抓包看不懂、跨语言费劲,活脱脱一座 Java 孤岛。这个印象该更新了。MateCloud 用的是 Dubbo 3 的TRI(Triple)协议——本质就是 gRPC over HTTP/2

这一步的意义被严重低估了:你不用写一行.proto,直接用 Java 接口定义服务,却拿到了 gRPC 的整套生态——HTTP/2 多路复用、标准化的流式、跨语言互通、以及那些成熟的可观测工具。历史上「Dubbo 只能 Java 玩」这条最大的劝退理由,就这么悄悄没了。

序列化选的是fastjson2,不是 Hessian,也不是 protobuf。我知道有人一看到 fastjson 就想起当年的 autotype 漏洞——这个担心我得认,但也得说清楚:fastjson2 默认关闭了 autotype,无需 schema、速度也够。

它能安心用,有个前提:Dubbo 端口只在内网、绝不暴露公网。而这正好和上一篇讲的网关 HMAC 签名接上了——网关到下游的每一跳都签名验身,Dubbo 网格活在一个可信内网里。安全从来不是单点,是一整条链一起兜。

05

最好的 Dubbo,是你看不见的 Dubbo

这是我在 MateCloud 里最欣赏的一个克制:整个 domain 和 application 层,你搜不到一个 Dubbo 的 import。@DubboReference只被允许出现在一个地方——infrastructure 层的适配器里,而且这个适配器实现的是 domain 自己定义的端口接口。

这不是架构洁癖。正因为 Dubbo 被关进了这个小盒子,MateCloud 才能靠一个属性mate.rpc.mode=local把它整个换成进程内直调的LocalAdapter,把三个微服务打成一个单体 JAR——上一篇讲的「双形态」,落点就在这里。

反过来说句得罪人的:如果 @DubboReference 出现在你的 Service 里、Controller 里,你其实已经悄悄失去了变回单体的能力,只是很多团队要到上线半年、想合并服务省成本时才发现。顺便一提,MateCloud 全项目只有一处@EnableDubbo,就在那个受 mode 开关控制的自动配置类上——单体模式下,Dubbo 连启动都不启动。

06

分布式上下文,不会自己跟过来

一次本地方法调用里,有四样东西是白送的:当前线程上的用户身份、一个事务、一条完整的异常栈、一条贯穿的 traceId。它们都躺在 ThreadLocal 里,你不用管。

可 RPC 调过去,provider 是另一个进程里的另一个线程。ThreadLocal 一个字都不会跟过来。traceId 断了、tenantId 没了,日志对不上、租户还可能串。MateCloud 的做法是在 Dubbo 的 consumer / provider 过滤器里,把这些上下文塞进调用的 attachment 随请求过网,provider 侧取出来重建,处理完在 finally 里清干净——防止线程池复用时上一条请求的身份残留串味。

这套逻辑,和异步队列、线程池要处理的其实是同一课:凡是「换了线程」的地方,上下文都得手动搬运,不搬就丢。

而我最想让你留意的,是藏在配置注释里的两个默认值——判断一个脚手架成不成熟,看的往往不是它有多少功能,而是它的默认值有没有把踩过的坑写进去:

  • provider 侧 retries = 0

    。Dubbo 默认是 2。可自动重试一个写操作,就是重复下单、重复扣款的经典配方。写操作绝不该由框架替你悄悄重放。

  • enable-empty-protection = true

    。注册中心在某个 provider 重启的瞬间可能推来一个空列表,关掉这个保护,缓存地址会被清空且不再恢复,直接 "No provider available"。这行配置不在任何入门教程里,它是重启竞态踩出来的疤。

07

什么时候,别用 Dubbo

聊了半天 Dubbo 的好,也得把边界划清楚。同步 RPC 有个天然的代价——它把两个服务的生命周期绑在了一起。provider 挂了,正在等它返回的 consumer 当场跟着挂;超时、重试、降级,一样都不能少,全得你自己兜。

所以判断标准其实很简单,一个问题就能分开:这次跨服务交互,调用方需要「立刻拿到返回值」吗?

认证要查用户、鉴权要查权限——要结果、要一致、要立刻,这是 Dubbo 的主场。用户注册完发条通知、写条审计、加点积分——不该阻塞主流程,provider 一时不在也无所谓,这是领域事件(MQ)的主场。MateCloud 两个都用,各归各位。

把什么都塞进 RPC,就是把微服务重新拼回一个「分布式单体」——
还倒贴上了网络的不可靠。

08

写在最后

回到最初那个问题:两个服务之间的一次调用,该长什么样。我的答案是——它该是一次有编译期契约、能一键退回单体、上下文不会中途丢失的方法调用。MateCloud 选 Dubbo,不是为了炫技,是为了让「跨服务调用」这件事,回到它本该有的朴素样子。

你当然可以不同意。Feign 的 REST 语义在对外开放、多语言异构的场景里依然是更好的选择。但在一套边界清晰、同构演进的微服务底座里,我更愿意把这份「像方法调用一样」的确定性,握在编译器手里

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询