第一章:2 楼住的是谁? ------ 写在 Java 代码里的 Spring Cloud / Dubbo
先把这句话刻在脑子里:
Spring Cloud / Dubbo = 跑在 JVM 里的"微服务 SDK"
它们关心的是:服务和服务之间,怎么优雅地"互相打电话"。
很多同学第一次接触微服务的时候,是从各种注解开始的:
-
@EnableDiscoveryClient -
@FeignClient -
@DubboService -
@DubboReference
一堆看不懂的 starter、一堆 YAML 配置,代码还能跑,但脑子已经乱了:
"我不就想调一下别的服务吗?为啥要整这么多玩意?"
别急,我们把问题拆开来。
1. 微服务世界里,最原始的需求其实就一句话:
"我这个服务,需要去调一下另一个服务。"
如果只看这句话,你大概会想到:
发一个 HTTP 请求:
RestTemplate/WebClient/HttpClient
或者 RPC:
- gRPC、Dubbo 自己的协议之类的
在一个很小的系统里,真的就可以这么干:
写死 IP+端口,发个 HTTP 请求,能通就行。
但一旦你做到微服务,问题就会一口气冒出来:
1)IP 能写死吗?
- 要是对面挂了,拉起一个新实例,IP 变了咋办?
2)要调哪一个实例?
- 对面启动了 10 个副本,你随便挑一个吗?怎么负载均衡?
3)慢怎么办?挂怎么办?
- 超时、重试、熔断、降级,是谁来管?
4)环境一多,配置怎么管?
- dev / test / prod 一堆环境,地址、开关、限流规则都不一样。
这些东西如果你全都 手写 if/else + 自己封装工具类,代码会变成:
-
一半是业务逻辑;
-
一半是"为了活在分布式世界"写的乱七八糟的辅助代码。
这时候,2 楼的住户登场了。
2. Spring Cloud / Dubbo 把一堆"分布式麻烦事",包成了一套 SDK
你可以把它们想象成:
"把服务发现、负载均衡、熔断、配置拉取、链路追踪这些'基础设施活',打包进 Java SDK 和注解里。"
2.1 Spring Cloud:给 Spring Boot 装上一层"云能力"
典型的用法长什么样?
你写一个接口:
@FeignClient(name = "order-service")
public interface OrderClient {
@GetMapping("/api/orders/{id}")
OrderDTO getOrder(@PathVariable("id") Long id);
}
然后在业务代码里就当 本地接口 用:
OrderDTO order = orderClient.getOrder(123L);
你会感觉:
-
好像就是调了一个普通 Java 接口;
-
完全没看到 HTTP、IP、端口、重试之类的细节。
但背后发生了什么?
1)name = "order-service" → 去 注册中心 问,"order-service 现在有哪些实例?"
2)得到一堆 IP:port → 按你配置的策略做 客户端负载均衡
3)发 HTTP 请求时:
-
带上各种 header(授权、traceId 等)
-
如果超时/失败,按策略 重试 / 熔断 / 降级
你写的代码: 只有接口 + 注解
Spring Cloud 帮你干的活: 找服务、选实例、发请求、处理失败、打链路
所以可以一句话概括:
Spring Cloud = 帮 Spring Boot 应用穿上一层"微服务外骨骼"。
2.2 Dubbo:更偏 RPC、强调高性能和服务治理
Dubbo 的感觉会再"底层一点",更像是:
"帮你把远程调用伪装成本地调用的 RPC 框架 + 一整套服务治理能力。"
典型用法大概是这样的:
1)暴露服务:
@DubboService
public class UserServiceImpl implements UserService {
@Override
public UserDTO getUser(Long id) { ... }
}
2)引用服务:
@DubboReference
private UserService userService;
public void foo() {
UserDTO user = userService.getUser(1L); // 看起来像本地调用
}
你会发现:
-
接口就是 Java 接口,调用就是普通方法调用;
-
但实际上,
userService.getUser(1L)已经穿越网络,跑到另一台机器上去了。
Dubbo 在后台帮你干了这些事:
-
把方法调用 → 序列化成二进制 / 特定协议 → 发给远端;
-
去注册中心(Zookeeper / Nacos 等)拿服务列表;
-
在客户端做负载均衡、重试、路由;
-
暴露自己的指标给监控系统。
可以粗暴点说:
Spring Cloud 更偏 HTTP REST + Spring 全家桶;
Dubbo 更偏 RPC 协议 + 高性能调用。
但 它们本质做的事是同一类:
在 JVM 里,用 SDK 的方式,接管"服务怎么互相调用"。
3. 为什么说它们都属于"2 楼"?
我们这个"三层楼模型"里:
2 楼:应用层治理(写在 Java 代码里的那层)
- Spring Cloud / Dubbo 就在这里;
1 楼:Kubernetes(管容器、Pod、Service、扩缩容)
地下室:Service Mesh(管流量、路由、加密、跨语言治理)
那 2 楼有什么典型特征?
1)以代码依赖的形式存在
-
你在
pom.xml里加的是:spring-cloud-starter-*、dubbo-spring-boot-starter... -
它们和你的业务代码是同一个进程,同 JVM 里的一家人。
2)通过注解 / 配置"侵入"到你的代码结构
-
@FeignClient、@DubboService、@DubboReference -
这些直接出现在你的业务代码里,影响你的代码设计。
3)很多"架构上的决定",会被固化在代码里
-
比如:你用的是 Feign 还是 Dubbo?
-
超时/重试怎么配?
-
日后如果你想从 Spring Cloud 换成 Mesh 来做治理,就要改大量代码。
所以你可以这样记:
只要是通过 Jar 依赖 + 注解 + 配置,渗透进你 Java 代码里的微服务能力,
都可以先归类为"2 楼"的东西。
Spring Cloud / Dubbo,就是 2 楼最典型的两位住户。
4. 小结:2 楼到底帮 Java 开发挡了哪些 "子弹"?
再帮你回顾一下,如果 没有 Spring Cloud / Dubbo,你要自己扛哪些事:
1)每次调用别的服务:
-
自己维护一份 IP 列表
-
自己做负载均衡
-
自己写重试逻辑
-
自己打日志 + TraceId
2)配置变更:
-
每个服务自己滚动重启
-
环境多了之后,全靠人肉记忆哪台机器是什么配置
3)服务越来越多后:
-
谁依赖谁,完全靠 wiki / 脑补
-
出了问题,从日志里一层层 grep
而有了 2 楼(Spring Cloud / Dubbo)之后:
-
调用别的服务,只需要一个接口 / 一个注解;
-
配置中心帮你统一拉配置;
-
注册中心帮你找服务;
-
大多数调用问题,在框架层就有"默认答案"。
代价是:
-
你的代码会比较深地绑定某一套框架;
-
而且很多"本该由平台干的事",被迫塞进了 JVM 里(这一点,为后面 1 楼 & 地下室登场埋下伏笔)。
这里你就先把一个感觉抓住:
Spring Cloud / Dubbo 都是"住在 2 楼的住户":
用 Java 代码 + 注解的方式,接管了微服务调用这件事。
第二章:1 楼和地下室干嘛的? ------ Kubernetes + Service Mesh 的分工
先把上一章那句话翻出来:
2 楼(Spring Cloud / Dubbo)关心的是:
"服务和服务之间怎么优雅地互相打电话?"
那 1 楼和地下室呢?
1 楼:Kubernetes
→ 关心的是:"这些服务实例,到底跑在哪?怎么活下去?"
地下室:Service Mesh
→ 关心的是:"这些服务之间的流量,怎么路由、怎么保护、怎么观测?"
也就是说:
-
2 楼 = 应用代码里的"客户端治理"
-
1 楼 = 应用外的"调度 + 扩缩容平台"
-
地下室 = 应用外的"网络 & 流量控制层"
你写 Java 的时候,大部分时间都在 2 楼。
但你那行 orderClient.getOrder(1L) 能不能跑顺利,其实要靠 1 楼和地下室一起兜底。
1. 1 楼:Kubernetes 只关心一件事 ------ "你这玩意要怎么跑?"
先说一句大实话:
Kubernetes 根本不关心你是 Spring Cloud 还是 Dubbo,
它只关心:"我要跑一个叫 X 的容器,给它留多少 CPU / 内存,要不要暴露端口?"
1.1 在 K8s 眼里,你只是一个"镜像 + 一堆配置"
对 Java 开发来说,典型的流程是:
1)用 Maven/Gradle 打包:
→ 得到一个 xxx.jar
2)写个 Dockerfile,做成镜像:
→ FROM eclipse-temurin:21-jre ...
3)写 K8s YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
template:
spec:
containers:
- name: user-service
image: my-registry/user-service:1.0.0
ports:
- containerPort: 8080
注意这整个过程:
Spring Cloud / Dubbo 在哪?
→ 在 jar 里面,在镜像里面,K8s 根本不管。
K8s 看的是:
-
这个 Pod 叫什么名字?
-
要跑几个副本?
-
挂了怎么重启?
-
要不要加探针(健康检查)?
所以从"楼层模型"的角度:
Kubernetes 是 1 楼:
**负责管理"这一栋楼里所有住户的房间数量、钥匙、空调、电梯"。
至于你房间里面是 Spring Cloud 还是 Dubbo,1 楼不管。
1.2 Service:给你一个"虚拟 IP",别再手写一堆地址
你可能知道,在 K8s 里每个 Pod 的 IP 都是会变的。
那别的服务如果要调你,该怎么找你?
答案就是:Service。
一个最简单的 Service:
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 80
targetPort: 8080
效果就是:
-
K8s 给你分配一个 ClusterIP(虚拟 IP);
-
任何 Pod 想访问你,只要访问
http://user-service:80就行; -
背后 K8s 会帮你负载到那 3 个副本上。
对 Java 应用来说,这意味着:
- 你在 Spring Cloud 里配置的
http://user-service其实底下是通过 K8s DNS → Service → Pod 来找到你的。
所以可以这样理解:
以前要靠注册中心(Eureka/Nacos)来给服务取名字;
在 K8s 里,多了一个"平台级的注册中心":Service + DNS。
2. 地下室:Service Mesh 把 "流量魔法" 挪出了 JVM
再往下走一层,到地下室:Service Mesh。
一句话版:
Service Mesh = 把原来 Spring Cloud / Dubbo 做的一部分"客户端治理",
搬到一个"透明的网络代理层"里去做。
2.1 Sidecar 是啥玩意?
在有 Mesh 的 K8s 里,一个 Pod 通常长这样:
之前是:
- 只有你自己的容器(
user-service)
有了 Mesh 之后:
- 旁边会自动多出一个容器,比如
istio-proxy(本质是 Envoy)
所有流量的路径会变成:
出去的流量:
- 你的容器 → Sidecar → 目标服务的 Sidecar → 目标容器
进来的流量:
- 对方容器 → 对方 Sidecar → 你的 Sidecar → 你的容器
你作为 Java 程序员在代码里看到的还是:
OrderDTO order = orderClient.getOrder(1L);
但请求其实走的是:
你代码的 HTTP 客户端 → localhost:某端口(被拦到 Sidecar)→ 对方 Sidecar → 对方应用
2.2 那 Service Mesh 具体帮你干嘛?
因为所有流量都经过 sidecar,它可以很自然地接管:
-
重试 / 熔断 / 限流
-
流量路由 / 灰度发布 / AB Test
-
mTLS 加密 / 证书轮换
-
请求指标 / 时延统计 / 调用图
过去这些事情,你可能都是在 Spring Cloud / Dubbo 配置里搞:
-
@HystrixCommand -
Resilience4j 配置
-
Dubbo 的路由规则、熔断规则
但在 Mesh 里,它变成了一堆 YAML + 控制面板上的策略,比如:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10
这段配置的意思是:
-
user-service v1 接 90% 的流量
-
user-service v2 接 10% 的流量
你 Java 代码一行不动,就能实现灰度发布。
这就是地下室的价值:
它不侵入你的 Java 代码,
却把大量"通用流量治理能力"统一拉到了平台层。
3. 一口气串起来:从浏览器到你的 Controller,一次请求到底怎么走?
现在我们把三层楼拉到一张"动画"里,你脑子里一起过一遍就行。
假设前端要请求你的 user-service:
步骤 1:浏览器 → API Gateway / Ingress
-
用户访问
https://api.xxx.com/user/1 -
云厂商 API Gateway / K8s Ingress Controller 把这个请求转发到集群里某个 Service,比如
user-service的 Service。
这一步通常跟 Java 代码无关,是 1 楼 + 一点点地下室 做的事情。
步骤 2:Ingress / Gateway → K8s Service → Pod
-
K8s 看:
user-service对应哪些 Pod? -
当前有 3 个副本:Pod A / Pod B / Pod C
-
K8s 做一层负载均衡,把请求打到其中一个 Pod,比如 Pod B。
到这里,Kubernetes 完成了:
- "请求打到哪一栋楼、哪一层、哪一间房" (更准确地说:打到哪一个 Pod)
步骤 3:Pod 里的 Sidecar → 你的 Spring Boot 应用
如果有 Service Mesh:
1)请求先进入 Pod B 的 Sidecar(Envoy)
2)Sidecar 判断:
-
有没有全局限流规则?
-
这个路径要不要打日志 / 打 trace?
-
要不要做 mTLS 解密?
3)再把请求转发到你的应用容器(user-service 的 JVM)
到这一步才轮到:
-
Spring Boot 帮你路由到对应的
@RestController -
你的 Controller / Service 开始跑业务逻辑
步骤 4:你的应用要调另一个服务(比如 order-service)
在 Controller 里,你写的是:
OrderDTO order = orderClient.getOrder(userId);
这时候会发生什么?
1)在 2 楼(Spring Cloud / Dubbo 视角)
Feign / Dubbo 看 order-service 这个服务名
如果你用的是 Spring Cloud + Eureka:
- 去 Eureka 拿
order-service的实例列表;
如果你用的是 K8s DNS:
- 直接访问
http://order-service(背后实际是 K8s Service)
应用里做客户端负载均衡、超时、重试......
2)流量离开 JVM,进入地下室 & 1 楼的世界
-
请求被发到 Pod 内的 Sidecar(Envoy)
-
Sidecar 应用灰度 / 熔断 / mTLS 等策略
-
通过 K8s Service 找到 order-service 的某个 Pod
-
对方 Pod 的 Sidecar 收到请求,再转给对方的 Spring Boot
你脑子里可以记住一个简化版路径:
1)用户 →(Gateway/Ingress)→ Service → Sidecar → Spring Boot(2 楼)
2)Spring Boot 再调别人时:
→ Spring Cloud / Dubbo → Sidecar → Service → 对方 Sidecar → 对方 Spring Boot
小结:2 楼 + 1 楼 + 地下室 的分工再强调一遍
2 楼:Spring Cloud / Dubbo(在 JVM 里)
负责:
-
怎么发起调用(HTTP/RPC)
-
如何在代码里配置重试 / 熔断 / 降级
-
如何跟注册中心 / 配置中心打交道
特点:贴近业务、跟 Java 强绑定
1 楼:Kubernetes
负责:
-
这个服务跑几个副本?
-
挂了怎么重启?
-
Pod 间如何通过 Service 互相可达?
特点:只看镜像和资源,不关心你用什么框架
地下室:Service Mesh
负责:
-
所有服务间的流量统一治理(重试、熔断、限流、灰度)
-
安全(mTLS)
-
观测(请求指标、调用图)
特点:不侵入代码、跨语言统一
当你能在脑子里把这条路径顺顺当当过一遍,
以后听到"某公司用了 K8s + Spring Cloud + Istio",你就不会慌:
"哦,知道了,你们是 2 楼、1 楼、地下室全都开。
那我只要搞清楚:哪些事是写在代码里的,哪些是写在 YAML 里的,就行。"
第三章:作为 Java 开发,你的"居住方案"怎么选?
前两章我们把三层楼都逛了一圈:
-
2 楼:Spring Cloud / Dubbo 这类写在 Java 代码里的微服务 SDK
-
1 楼:Kubernetes,帮你管容器、实例数、Service
-
地下室:Service Mesh,帮你管流量、灰度、mTLS、观测
那回到最现实的问题:你所在的团队,适合住在几楼?你自己,又该先搞懂哪一层?
可以先记一句:
不是"选一个",而是"你们目前 realistically 能用到哪几层"。
我们分三种典型"居住方案"聊:只住 2 楼、2 楼 + 1 楼、三层全开。
1. 只住 2 楼:Spring Boot + 少量 Spring Cloud / Dubbo 的小团队模式
先说大部分人最常见的情况:小团队 / 初创公司 / 业务刚起步阶段。
1.1 典型长相是什么样?
部署:
-
可能是 "一台(或几台)云服务器 + Docker Compose / 脚本"
-
或者简单用一下云厂商的容器服务,但还没上 K8s
技术栈:
-
Spring Boot 单体 or 几个简单服务
-
加一点点 Spring Cloud(OpenFeign、Config、Gateway)或者 Dubbo
核心特点:
大部分"复杂度"还在 2 楼里解决:
-
服务发现:用 Nacos/Eureka,或者干脆写域名
-
调用:Feign / Dubbo
-
配置:Spring Cloud Config / Nacos Config / 本地 YAML
1.2 这种方案的优势
上手快:
团队只要会 Spring Boot,学一点 Spring Cloud / Dubbo,就能跑起来。
成本低:
不需要专门的 K8s / Mesh 平台团队。
适合"先做出来再说":
对小团队来说,活下来比"架构多优雅"重要。
1.3 对 Java 开发者来说,重点该放哪?
如果你现在就在这种环境(或者你刚起步),建议你:
1)把 Spring Boot 玩熟:
Web、数据访问、配置、测试、Actuator
2)选择 Spring Cloud 或 Dubbo 其一先搞透:
比如 Spring Cloud:重点理解
-
Feign 是怎么帮你封装 HTTP 调用的?
-
Eureka / Nacos 背后"服务名 → 实例列表"怎么来的?
-
Gateway 跟 Nginx / Ingress 的区别是啥?
3)把"一次调用从 Controller 到下游服务"这条链画清楚。
你可以暂时不急着上 K8s / Mesh,但是要有个感觉:
"如果有一天公司要上 K8s / Mesh,我现在用的这些 Spring Cloud / Dubbo 功能,有哪些是将来可以下沉的?"
先有这个"预判",以后就不会被动挨打。
2. 2 楼 + 1 楼:Spring Cloud / Dubbo 上 K8s 的中型公司模式
往上升级一档:项目数量上来了,机器多了,靠手工运维撑不住了,开始上 K8s。
2.1 现实世界里长什么样?
运维 / 平台团队搞了一套:
-
Kubernetes 集群
-
CI/CD(Jenkins / GitHub Actions / GitLab CI)
-
镜像仓库
业务团队继续写:
- Spring Boot + Spring Cloud / Dubbo
只是部署方式从:
- "JAR 丢到服务器 + Supervisor / shell 脚本"
变成:
- "打镜像 → 推镜像仓库 → 用 YAML 部署到 K8s"。
这就是最常见的"2 楼 + 1 楼"组合。
2.2 这种模式有什么特点?
1)应用层治理继续在 2 楼
-
服务发现:还是 Eureka / Nacos / ZK
-
调用:Feign / Dubbo
-
熔断 / 降级:还是在应用里配置
2)部署 & 扩缩容交给 1 楼(K8s)
-
副本数、滚动升级、健康检查 → Deployment / StatefulSet
-
对外暴露 → Ingress / NodePort / LoadBalancer
3)Service 的角色变得很关键
-
你在 Spring Cloud 里看到的服务名
-
很可能是注册中心 + K8s DNS 混合在一起用
2.3 对 Java 开发的"进阶要求"
在这种环境里,你想显得不是"只会写 Controller 的人",建议做到:
1)看得懂几种关键 YAML:
-
Deployment:知道副本数、资源限制、探针是怎么配的
-
Service:知道
ClusterIP、selector的含义 -
Ingress:大致明白是怎么把流量从域名引到你的 Service 的
2)能把 2 楼和 1 楼的关系说清楚:
-
Spring Cloud / Dubbo 的服务发现 vs K8s Service 的服务发现
-
哪些配置应该放在 ConfigMap/Secret,哪些放在 Spring Config 里
3)排查问题时脑里有完整链路:
调用失败时,你能一步步问自己:
-
Pod 在不在?
-
Service 配对了吗?
-
Ingress/gateway 规则对不对?
-
最后才去怀疑 Spring Cloud / Dubbo 配置。
一句话:
在"2 楼 + 1 楼"模式下,
你要开始学会站到楼道里,看整个楼是怎么接线的,而不是只盯着自己房间。
3. 三层全开:大厂里的混合架构 & 迁移思路
最后一档,就是 大厂 / 历史悠久的中大型团队 常见的状态:
2 楼、1 楼、地下室,全都在用,而且还是多代共存。
3.1 真实世界通常是这样的混合局面
老系统:
- 部分还跑在 VM 上,用 Dubbo 2.x + ZK
中代系统:
- Spring Cloud / Spring Cloud Alibaba + K8s 部署
新系统:
- 全 K8s,强依赖 Mesh(Istio / Linkerd),用 gRPC / HTTP + 轻量 Spring Cloud
所以这句"我们现在是 K8s + Spring Cloud + Istio",
很可能背后其实是:"三代技术堆在一起,我们在慢慢收敛"。
3.2 三层全开时,一个 Java 工程师最需要的能力是啥?
重点已经 不再是某个框架 API 的细节 了,而是:
1)你能不能读懂这家公司"这几层的分工图"?
-
有些事是 Mesh 管的(重试 / 熔断)
-
有些事还是写在 Spring Cloud / Dubbo 里
-
有些老系统还在用注册中心,不完全依赖 K8s Service
2)你能不能帮团队做"减法"而不是再加一层?
比如:
-
既然 Mesh 已经做熔断了,那 Spring Cloud 里的 Hystrix/Resilience4j 要不要弱化?
-
既然 K8s Service + Mesh 已经做负载均衡了,Dubbo 那些复杂路由还能不能简化?
3)你能不能把这些东西,翻译成"业务价值"的语言?
-
不是说:"我们用了 Istio + Sidecar,很酷!"
-
而是说:"我们把熔断从代码挪到 Mesh 后,可以对所有语言统一配置降级策略,灰度规则也可以由平台团队集中管理,业务团队自己不需要写那么多技术细节。"
3.3 现实建议:如何从"只会写 2 楼"长成"看懂整栋楼"的人?
1)阶段 1:扎实站稳 2 楼
Spring Boot 基础
选一个微服务框架(Spring Cloud 或 Dubbo)玩熟:
- 服务发现、调用、配置、熔断、简单链路追踪
2)阶段 2:学会爬楼,看懂 1 楼
会看/改基础的 K8s YAML:
- Deployment / Service / Ingress / ConfigMap / Secret
知道一个服务从"打包 → 镜像 → 部署 → 发布"的完整路径
能把"某个线上问题"用一张图画出来
3)阶段 3:可以下地下室,理解 Mesh 的存在感
不一定要你去运维 Istio,但至少:
-
懂 VirtualService / DestinationRule 概念
-
懂 Sidecar 的流量路径
能说清:
- "这件事可以不写在 Spring Cloud / Dubbo 里,交给 Mesh 比较好。"
最后,把这一整套"楼层模型"消化一下,你就能说出这样一句很有味道的话:
"我不会再问'到底该选 Spring Cloud 还是 K8s 还是 Mesh',
我会先问:这件事应该放在 应用层、平台层还是网络治理层?
框架只是实现,分工才是底层逻辑。"
这时候,你就不再是"只住在 2 楼的小租客",
而是那个 拿着整栋楼楼层图、知道每一层干嘛的 Java 工程师了。