概述
交友项目的服务拆分完成后,第一个绕不开的问题就是:应用层要调域服务的业务方法,走 HTTP 还是走 RPC。这一课就是为回答它准备的。铺垫了 HTTP 与 RPC 的差别之后,把 Dubbo 从诞生、入门、高级特性到 SpringCloud 整合整条线串起来,最后落到项目工程里那几个 tanhua-dubbo-* 模块上。
纲要
- 为什么拆完微服务还得学 Dubbo:RPC 与 HTTP 的实际差距,以及两者在生产里的组合形态
- Dubbo 前置课的知识地图:认识 Dubbo → 快速入门 → 高级特性 → SpringCloud 整合
- 每一段解决什么问题、学完能做什么、涉及哪些注解与配置项
- 关键注解与配置速查表(暴露服务 / 引用服务 / 注册中心 / 协议 / 质量保障)
- 学习路径:哪些必须动手跑通,哪些可以先理解后补
- 与项目项目的衔接:
tanhua-dubbo-db/tanhua-dubbo-mongo怎样把业务能力暴露给应用层 - 下一篇的入口
拆完微服务,为什么还要学 Dubbo
服务拆分之后,一个工程被切成若干独立进程,"服务之间怎么调用"立刻成了必须解决的问题。SpringCloud 那套给的是 HTTP 方案:RestTemplate 直接发请求,或者用 Feign 声明式调用。能跑通,为什么还要引入 Dubbo?
先看历史脉络。软件架构从单体演进到微服务,中间还夹着一层过渡形态------分布式 SOA 架构。做法是把单体里核心的功能单元抽出来,通过 RPC 远程调用。SOA 与微服务最大的分水岭就在通信协议:微服务走 REST API(本质是 HTTP),SOA 走 RPC(基于 TCP)。Dubbo 正是 SOA 思路下的服务治理组件。
那今天为什么还要用它?两个现实理由。
性能与契约。RPC 用二进制序列化加长连接复用,省掉了 HTTP 每次请求的报文头和三次握手往返;接口契约走共享 api 模块,提供者和消费者编译的是同一份接口,字段对不上编译期就报错,而不是等联调时才发现 JSON 少了一个字段。超时、重试、多版本、负载均衡策略这些治理项,Dubbo 都在框架层给了配置入口。
组合使用是常态。课程最后专门讲了 SpringCloud 整合 Dubbo,用 SpringCloud 的注册与治理体系承载 Dubbo 的 RPC 调用。也就是说它并不站在 SpringCloud 的对立面,而是作为其中一环存在------加入 Dubbo 的主要目的就是替换 Feign,把相对低效的 HTTP 通信换成 RPC。
| 维度 | HTTP(RestTemplate / Feign) | RPC(Dubbo) |
|---|---|---|
| 传输协议 | HTTP/1.1,文本报文头 | 基于 TCP 的 Dubbo 协议,自定义二进制协议 |
| 序列化 | JSON 为主,可读性好、体积大 | Hessian2 等二进制序列化,体积小 |
| 连接 | 短连接为主(可配连接池) | 长连接复用,省握手开销 |
| 接口契约 | 靠文档或 OpenAPI,运行期才暴露不一致 | 共享 api 模块,编译期校验 |
| 服务治理 | 依赖 SpringCloud 组件补齐 | 超时 / 重试 / 多版本 / 负载均衡框架内置 |
| 适用场景 | 对外 API、跨语言、调试友好 | 内部服务间高频调用、追求吞吐 |
两者不是二选一。项目项目里应用层对域服务走 Dubbo,对外接口仍是 HTTP,各取所长。
Dubbo 前置课的知识地图
整门前置课是四段递进,后一段都建立在前一段跑通的基础上。
#mermaid-svg-hJ8yMvfNf7J1VCpk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hJ8yMvfNf7J1VCpk .error-icon{fill:#552222;}#mermaid-svg-hJ8yMvfNf7J1VCpk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hJ8yMvfNf7J1VCpk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .marker.cross{stroke:#333333;}#mermaid-svg-hJ8yMvfNf7J1VCpk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hJ8yMvfNf7J1VCpk p{margin:0;}#mermaid-svg-hJ8yMvfNf7J1VCpk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .cluster-label text{fill:#333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .cluster-label span{color:#333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .cluster-label span p{background-color:transparent;}#mermaid-svg-hJ8yMvfNf7J1VCpk .label text,#mermaid-svg-hJ8yMvfNf7J1VCpk span{fill:#333;color:#333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .node rect,#mermaid-svg-hJ8yMvfNf7J1VCpk .node circle,#mermaid-svg-hJ8yMvfNf7J1VCpk .node ellipse,#mermaid-svg-hJ8yMvfNf7J1VCpk .node polygon,#mermaid-svg-hJ8yMvfNf7J1VCpk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hJ8yMvfNf7J1VCpk .rough-node .label text,#mermaid-svg-hJ8yMvfNf7J1VCpk .node .label text,#mermaid-svg-hJ8yMvfNf7J1VCpk .image-shape .label,#mermaid-svg-hJ8yMvfNf7J1VCpk .icon-shape .label{text-anchor:middle;}#mermaid-svg-hJ8yMvfNf7J1VCpk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hJ8yMvfNf7J1VCpk .rough-node .label,#mermaid-svg-hJ8yMvfNf7J1VCpk .node .label,#mermaid-svg-hJ8yMvfNf7J1VCpk .image-shape .label,#mermaid-svg-hJ8yMvfNf7J1VCpk .icon-shape .label{text-align:center;}#mermaid-svg-hJ8yMvfNf7J1VCpk .node.clickable{cursor:pointer;}#mermaid-svg-hJ8yMvfNf7J1VCpk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .arrowheadPath{fill:#333333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hJ8yMvfNf7J1VCpk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hJ8yMvfNf7J1VCpk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hJ8yMvfNf7J1VCpk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hJ8yMvfNf7J1VCpk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hJ8yMvfNf7J1VCpk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hJ8yMvfNf7J1VCpk .cluster text{fill:#333;}#mermaid-svg-hJ8yMvfNf7J1VCpk .cluster span{color:#333;}#mermaid-svg-hJ8yMvfNf7J1VCpk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hJ8yMvfNf7J1VCpk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hJ8yMvfNf7J1VCpk rect.text{fill:none;stroke-width:0;}#mermaid-svg-hJ8yMvfNf7J1VCpk .icon-shape,#mermaid-svg-hJ8yMvfNf7J1VCpk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hJ8yMvfNf7J1VCpk .icon-shape p,#mermaid-svg-hJ8yMvfNf7J1VCpk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hJ8yMvfNf7J1VCpk .icon-shape .label rect,#mermaid-svg-hJ8yMvfNf7J1VCpk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hJ8yMvfNf7J1VCpk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hJ8yMvfNf7J1VCpk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hJ8yMvfNf7J1VCpk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 认识 Dubbo
前世今生 + RPC 概念
② 快速入门
搭环境 + 写一对 Provider/Consumer
③ 高级特性
启动检查 / 超时重试 / 多版本 / 负载均衡
④ SpringCloud 整合 Dubbo
用云原生注册体系承载 RPC
解决:Dubbo 是什么、和 HTTP 差在哪
解决:一个调用怎么跑起来,学完能独立搭起最小可运行工程
解决:生产环境怎么配,学完能调参、能定位雪崩
解决:已有 SpringCloud 项目怎么平滑接入 RPC
认识 Dubbo
Dubbo 于 2011 年由阿里巴巴开源,是国内服务治理与 SOA 理念落地的核心框架,后来捐给 Apache 基金会。这一节要建立三个概念:RPC 是什么、Dubbo 的四个角色(Provider / Consumer / Registry / Monitor)各干什么、以及它和 SpringCloud 的关系。后面所有配置都能挂到这套角色模型上理解。
快速入门
这一段的产出是一个能跑起来的最小工程:装好 Nacos 注册中心和 dubbo-admin 控制台,写一个服务提供者(实现并暴露接口),再写一个服务消费者(引用并调用)。走通之后做代码优化------把接口抽到公共的 user-api 模块,让双方共享同一份契约;再处理序列化。
调用链路上,Provider 启动时把服务注册到 Nacos,Consumer 启动时订阅并拿到地址列表,之后基于软负载均衡直接选一台调用。
Consumer Registry(Nacos) Provider Consumer Registry(Nacos) Provider #mermaid-svg-WECPqzmv8gLYPZFz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WECPqzmv8gLYPZFz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WECPqzmv8gLYPZFz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WECPqzmv8gLYPZFz .error-icon{fill:#552222;}#mermaid-svg-WECPqzmv8gLYPZFz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WECPqzmv8gLYPZFz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WECPqzmv8gLYPZFz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WECPqzmv8gLYPZFz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WECPqzmv8gLYPZFz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WECPqzmv8gLYPZFz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WECPqzmv8gLYPZFz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WECPqzmv8gLYPZFz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WECPqzmv8gLYPZFz .marker.cross{stroke:#333333;}#mermaid-svg-WECPqzmv8gLYPZFz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WECPqzmv8gLYPZFz p{margin:0;}#mermaid-svg-WECPqzmv8gLYPZFz .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WECPqzmv8gLYPZFz text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-WECPqzmv8gLYPZFz .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-WECPqzmv8gLYPZFz .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-WECPqzmv8gLYPZFz .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-WECPqzmv8gLYPZFz .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-WECPqzmv8gLYPZFz #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-WECPqzmv8gLYPZFz .sequenceNumber{fill:white;}#mermaid-svg-WECPqzmv8gLYPZFz #sequencenumber{fill:#333;}#mermaid-svg-WECPqzmv8gLYPZFz #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-WECPqzmv8gLYPZFz .messageText{fill:#333;stroke:none;}#mermaid-svg-WECPqzmv8gLYPZFz .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WECPqzmv8gLYPZFz .labelText,#mermaid-svg-WECPqzmv8gLYPZFz .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-WECPqzmv8gLYPZFz .loopText,#mermaid-svg-WECPqzmv8gLYPZFz .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-WECPqzmv8gLYPZFz .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-WECPqzmv8gLYPZFz .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-WECPqzmv8gLYPZFz .noteText,#mermaid-svg-WECPqzmv8gLYPZFz .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-WECPqzmv8gLYPZFz .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WECPqzmv8gLYPZFz .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WECPqzmv8gLYPZFz .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WECPqzmv8gLYPZFz .actorPopupMenu{position:absolute;}#mermaid-svg-WECPqzmv8gLYPZFz .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-WECPqzmv8gLYPZFz .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WECPqzmv8gLYPZFz .actor-man circle,#mermaid-svg-WECPqzmv8gLYPZFz line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-WECPqzmv8gLYPZFz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用失败换另一台提供者重试 启动时注册自己提供的服务 启动时订阅自己需要的服务 返回提供者地址列表(变更时长连接推送) 基于负载均衡算法发起 RPC 调用
高级特性
入门跑通只能算"能用",这段讲的是"敢不敢上线"。四个特性都是围绕稳定性展开的:
- 超时与重试 :消费者永久等待会拖干线程池,超时(
timeout,默认 1000ms)让线程及时归还;重试(retries,默认 2)用来兜住网络抖动,但要求接口幂等。 - 启动检查 :默认启动时校验依赖服务是否可用,不可用直接抛异常。生产环境该开,开发时没有提供者反而碍事,用
check=false关掉。 - 多版本 :灰度发布场景下,让新版本
UserServiceImpl2只服务一部分用户,通过version区分。 - 负载均衡:集群部署时从 Random / RoundRobin / LeastActive / ConsistentHash 四种策略里选。
SpringCloud 整合 Dubbo
最后一段回答"已经有 SpringCloud 项目,怎么加 Dubbo"。核心改动是把远程调用从 Feign 换成 Dubbo:抽出 dubbo-api 接口模块,提供者与消费者分别引入 Nacos 注册中心与 Dubbo 依赖,注册中心地址从 nacos://... 改成 spring-cloud://localhost,表示复用 SpringCloud 的注册中心。
关键注解与配置速查表
后续每篇都会用到这几组配置,先按用途归档,方便回过头来查。
| 用途 | 注解 / 配置项 | 位置 | 说明 |
|---|---|---|---|
| 暴露服务 | @DubboService |
提供者实现类 | 声明这是一个要被远程调用的服务实现 |
| 引用服务 | @DubboReference |
消费者字段 | 注入远程服务的代理对象 |
| 协议与端口 | dubbo.protocol.name / dubbo.protocol.port |
提供者 yml | 默认协议 dubbo,示例端口 20881 |
| 注册中心(独立) | dubbo.registry.address: nacos://127.0.0.1:8848 |
双方 yml | 直连 Nacos |
| 注册中心(整合) | dubbo.registry.address: spring-cloud://localhost |
双方 yml | 复用 SpringCloud 注册中心 |
| 包扫描 | dubbo.scan.base-packages |
提供者 yml | 扫描 @DubboService 所在包 |
| 超时 | timeout |
消费者 yml / 注解 | 单位毫秒,默认 1000 |
| 重试 | retries |
消费者 yml / 注解 | 默认 2,非幂等接口设 0 |
| 启动检查 | check |
消费者 yml / 注解 | 开发期设 false |
| 多版本 | version |
提供者与消费者 | 灰度发布时对齐版本号 |
| 负载均衡 | loadbalance |
消费者注解 | Random(默认)/ RoundRobin / LeastActive / ConsistentHash |
学习路径
按"必须动手跑通"和"可以先理解后补"分两拨推进。
必须跑通的,是能被验证的东西:
- 装 Nacos(
startup.cmd -m standalone,访问127.0.0.1:8848/nacos)和 dubbo-admin 控制台。 - 写一对 Provider / Consumer,在 dubbo-admin 里观察服务是否注册成功、消费者能否拿到地址列表。
- 故意把提供者方法改成
Thread.sleep(5000),把消费者timeout调到 1000,看超时异常长什么样;再把retries打开观察重试行为。这一步不做,后面的"超时配置"永远只是背下来的。
可以先理解后补的,是那些不改代码也看不出差别的东西:
- 多版本灰度、负载均衡策略的选择,理解语义即可,等真正做灰度发布时再回来细调。
- SpringCloud 整合的细节(
spring-cloud://localhost的含义、依赖坐标),先知道有这么回事,进项目后自然会对上。
与项目项目的关系
前置课不是孤立的语法练习,它的服务对象就是接下来的交友项目项目。
项目的工程里,业务能力被放进独立的 Dubbo 域服务模块,由应用层远程调用:
text
tanhua
├── tanhua-app-server # 应用层,面向接口的 HTTP 入口,通过 Dubbo 引用域服务
├── tanhua-dubbo
│ ├── tanhua-dubbo-db # 基于 MySQL 的域服务,负责用户、动态等数据
│ ├── tanhua-dubbo-mongo # 基于 MongoDB 的域服务,负责圈子、评论等文档数据
│ └── tanhua-dubbo-interface # 共享接口模块,Provider 与 Consumer 的契约所在
├── tanhua-model # 实体类
├── tanhua-commons # 公共工具
└── tanhua-autoconfig # 自动配置
应用层的 application.yml 引用这些服务时,用的就是前置课里的那套配置:
yaml
dubbo: #dubbo配置
registry:
address: spring-cloud://localhost # 复用 SpringCloud 的注册中心
consumer:
check: false # 开发期关闭启动检查
而域服务模块负责暴露能力:
yaml
dubbo:
protocol:
name: dubbo
port: 20881
registry:
address: spring-cloud://localhost
scan:
base-packages: com.tanhua.dubbo.api # 扫描 @DubboService 实现
对照一下就很清楚:spring-cloud://localhost 正是整合那一节的内容,check: false 是启动检查那一节的内容,scan.base-packages 是入门案例那一节的内容。前置课里每一个配置项,在项目工程里都有对应的落点。理解了这层关系,学 Dubbo 就不是在背配置,而是在为看懂项目铺路。
官方文档
总结
几个要点收一下。
HTTP 能解决服务调用,但 RPC 在性能、契约严谨度和治理能力上更贴合内部高频调用,而且常和 SpringCloud 组合出现------这是学 Dubbo 的现实理由。
前置课的知识地图是四段式:认识 Dubbo 建立概念模型,快速入门跑通最小工程并抽取公共接口,高级特性解决生产可用性,SpringCloud 整合回答存量项目怎么接入。四段层层递进,跳着看会断线。
学习上别只读不跑。装 Nacos、写一对 Provider/Consumer、改超时和重试看现象,这三件事的收益远大于把配置表背下来。多版本和整合细节属于"用到再补"。
最后落到项目上------项目的 tanhua-dubbo-db / tanhua-dubbo-mongo 就是用 Dubbo 暴露业务能力的,前置课的配置在工程里处处可见。把它当成为项目做的准备,而不是一节独立的框架语法课。
下一篇按讲义顺序进入 Dubbo 简介,先把 Dubbo 的内部结构和它在分布式调用里的定位讲透。