微服务架构与 Spring Cloud Alibaba 实战:注册发现、配置中心、网关、限流与分布式事务

Spring Boot 解决的是”怎么又快又好地写一个服务”,Spring Cloud 解决的是”一堆服务怎么协同、治理、不互相拖垮”。本文不重复 Spring Boot 自动装配、JVM 调优、并发编程那些已经写过的点,只聚焦”服务拆分之后那些绕不开的分布式基础设施”,并用 Spring Cloud Alibaba 这套国内最主流的落地方案串起来。

一、为什么需要微服务:从单体到分布式

早期一个 war 包打天下:Controller、Service、DAO 全在一个进程里,改一行要整体重发。业务小的时候很爽,大了就有三个典型痛点。

  • 扩展只能整体扩:下单模块 CPU 打满,却被迫连不忙的用户模块一起加机器,浪费资源。
  • 技术栈被锁死:想用 Go 写实时推送?不行,单体是 Java 一统天下。
  • 发布互相牵连:改个文案的手抖,可能把支付一起带崩。

微服务的核心思想就八个字:拆得开、各自治、独立发。每个服务拥有自己的进程、数据库、迭代节奏,通过 RPC/HTTP 协作。

但微服务不是银弹。它把”进程内的函数调用”变成了”跨网络的远程调用”,代价是:网络不可靠、数据要一致、链路要可观测、运维复杂度指数级上升。康威定律提醒我们:微服务边界 = 团队沟通边界,团队只有三五个人时,硬上微服务往往是灾难。

二、微服务核心基础设施全景

一个能跑起来的微服务集群,至少要有下面这几块能力。先建立全局认知,后面逐个击破:

能力 要解决的问题 Spring Cloud Alibaba 选型
服务注册与发现 服务地址不再硬编码,动态上下线 Nacos Discovery
统一配置中心 配置外置、动态刷新、多环境隔离 Nacos Config
服务通信 声明式、带负载均衡地调用他人 OpenFeign + Spring Cloud LoadBalancer
API 网关 统一入口、鉴权、限流、路由 Spring Cloud Gateway
容错限流 防止雪崩,熔断降级 Sentinel
分布式事务 跨服务的数据一致性 Seata(AT 模式)
链路追踪 一次请求穿过 N 个服务,如何定位慢点 Micrometer Tracing / SkyWalking

三、服务注册与发现:Nacos

没有注册中心时,A 调 B 得写死 http://192.168.1.23:8081/api。B 一扩容、一换 IP,A 就得跟着改、重新发版——这在分布式环境里不可接受。

Nacos 把”地址表”收归中心:每个服务启动时把自己的 IP、端口、健康状态注册上去;调用方拉取可用实例列表,再挑一个发起请求。

1
2
3
4
5
6
7
8
9
10
# 服务提供者 / 消费者 共有配置
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: dev # 命名空间隔离环境
group: DEFAULT_GROUP
1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient // 向 Nacos 注册并开启发现
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}

几个关键点:

  • 临时实例 vs 持久实例:临时实例靠心跳保活,掉线即剔除(默认);持久实例注册在 Nacos 磁盘,适合核心中间件。
  • 健康检查:Nacos 对临时实例做心跳探测,对持久实例做主动 TCP/HTTP 探测。
  • CAP 取向:Nacos 在临时实例下偏向 AP(高可用、最终一致),持久实例可切 CP。

调用方拿到的实例列表是”带过期时间的本地缓存 + 定时轮询”组合,所以某个实例刚挂时,调用方最多”盲调”一小段时间才会被剔除——这正是后面要加容错兜底的原因。

四、统一配置中心:Nacos Config

把 application.yml 写死在 jar 里,改个开关就要重新打包发布,灰度、回滚都别想了。Nacos Config 把配置搬到服务端,应用启动时拉取,并支持动态刷新。

定位一个配置需要三个坐标:

坐标 作用 示例
Data ID 具体配置文件名 order-service.yaml
Group 分组(默认 DEFAULT_GROUP) ORDER_GROUP
Namespace 环境/租户隔离(彼此不可见) dev / prod
1
2
3
4
5
6
@RestController
@RefreshScope // 配置变更时,该 Bean 自动重新注入
public class OrderController {
@Value("${order.promotion:off}")
private String promotion; // 改 Nacos 里的 order.promotion 无需重启
}

实践建议:把数据库密码、开关、限流阈值等易变配置放 Nacos;把端口、应用名这类几乎不变的留本地。配置要带版本与回滚能力,敏感配置走加密插件。

五、服务通信:OpenFeign + LoadBalancer

用 RestTemplate 拼 URL 调别人,代码又臭又长还容易出错。OpenFeign 让你”像调本地接口一样调远程服务”——写一个接口加注解,框架帮你拼地址、负载均衡、发请求。

1
2
3
4
5
6
7
8
@FeignClient(name = "user-service")        // 指向注册中心里的服务名
public interface UserClient {
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable("id") Long id);
}

// 业务里直接注入使用
UserDTO u = userClient.findById(order.getUserId());
  • 负载均衡:Spring Cloud LoadBalancer 从 Nacos 拉到的实例列表中,按轮询/加权挑一个,天然支持横向扩容。
  • 超时与重试:在 application.yml 里配 connectTimeout / readTimeout,并对非幂等写操作谨慎重试(重试可能放大故障)。
  • 兜底: Feign 调用务必配合后面的 Sentinel 降级,否则一个慢依赖会把你的线程池拖穿。

六、API 网关:Spring Cloud Gateway

十个服务对外暴露十个域名?客户端要记十个地址、做十次鉴权?网关就是这十个服务的统一前台:所有流量先过网关,再由它按规则转发。

Gateway 的三大件:

  • Route(路由):ID + 目标 URI + 断言 + 过滤器,一条转发规则。
  • Predicate(断言):匹配条件,如 Path=/order/**、Header、时间窗口。
  • Filter(过滤器):对请求/响应做加工,如加头、限流、改写路径。
1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: order-route
uri: lb://order-service # lb:// 表示走负载均衡
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1 # 去掉 /api 前缀再转发
- name: RequestRateLimiter # 这里可挂限流

全局过滤器(GlobalFilter)是做统一鉴权、灰度标记、埋点、CORS 的最佳位置:一次写好,所有服务免重复。但别把重业务逻辑塞进网关——它本应是”薄薄一层转发”,变胖了反而成瓶颈。Gateway 基于 WebFlux 响应式模型,注意它和 Spring MVC 的 Tomcat 不是一套线程模型。

七、服务容错与限流:Sentinel

微服务最怕雪崩:A 调 B,B 调 C,C 慢了 → B 的线程被占满 → A 也被拖死 → 全盘 502。Sentinel 提供四把保护伞:

  • 限流:QPS 或并发线程数超阈值就拒绝,保护自身。
  • 熔断:被调方错误率/慢调用比例过高,直接”先断一段时间”,给对端喘息。
  • 降级:资源不稳定时自动返回兜底,而非一直卡着。
  • 隔离:信号量/线程池隔离,限制单个依赖占用的资源。
1
2
3
4
5
6
7
8
9
10
11
@SentinelResource(value = "createOrder",
blockHandler = "blockHandler", // 被限流/熔断时走这里
fallback = "fallback") // 业务异常时走这里
public OrderDTO createOrder(OrderReq req) {
return orderService.create(req);
}

// 兜底方法签名需与原方法一致(blockHandler 需加 BlockException 参数)
public OrderDTO blockHandler(OrderReq req, BlockException ex) {
return OrderDTO.degraded(); // 返回降级结果,别抛异常
}

流控阈值类型分 QPS(每秒请求数)和线程数(并发占用);流控模式有”直接、关联、链路”三种,比如可让”写接口”被”读接口”关联限流,保护写库。阈值一定要靠压测得出,拍脑袋定的数字在真实流量面前毫无意义。

八、分布式事务:Seata

本地事务靠 @Transactional 一个方法内 ACID。可一旦下单要”扣库存 + 减余额 + 写订单”跨三个服务,本地事务就管不到别人了——中间某一步失败,前面已经提交的就成了脏数据。

常见方案对比:

方案 一致性 侵入性 适用场景
两阶段提交(2PC) 强一致 中 传统数据库,锁资源久
TCC 强一致 高(写三套接口) 资金等强一致核心
Saga 最终一致 中(写补偿) 长流程业务
本地消息表 / 事务消息 最终一致 低 可异步的对账类
Seata AT 最终一致 极低(加注解) 大多数业务首选

Seata 的 AT 模式对业务几乎零侵入:一阶段本地事务提交的同时,框架自动生成 before/after 镜像写入 undo_log;二阶段若全局提交就异步清理,若回滚就用 undo_log 反向补偿。

1
2
3
4
5
6
@GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
public void placeOrder(OrderReq req) {
storageClient.deduct(req.getSkuId(), req.getCount()); // 扣库存(远程)
accountClient.debit(req.getUserId(), req.getAmount()); // 减余额(远程)
orderMapper.insert(buildOrder(req)); // 写订单(本地)
}

能用”最终一致”就别上”强一致”:优先把跨服务操作设计成可补偿、幂等的异步流程(如发消息 + 对账),Seata 只在确实需要强一致的关键链路启用。分布式事务越用越贵,能不跨服务就不要跨。

九、可观测性:让请求”可被看见”

拆成几十个服务后,一个慢请求到底卡在哪一跳?靠猜不行,要三件套:

  • 链路追踪:给每次请求发一个全局 TraceId,跨服务透传,把各段耗时串成一条瀑布图(SkyWalking / Micrometer Tracing)。
  • 指标(Metrics):QPS、RT、错误率、线程池水位,配告警阈值。
  • 日志(Logging):统一采集,关键处打印 TraceId 方便检索。

三者通过 TraceId 关联,是定位分布式问题的命脉。采样率别拉满(生产可按 1%~10% 采样),否则链路存储成本爆炸。

十、微服务 ≠ 银弹:什么时候不该上

维度 单体更合适 微服务更合适
团队规模 1~3 人 多团队、职责清晰
业务复杂度 简单、变化慢 模块多、迭代快
交付要求 一个包发完 各模块独立发布
运维能力 弱 有 CI/CD、监控、链路底座

Spring Boot 帮你”把每个服务写好”,Spring Cloud 帮你”把这些服务管好”。没有监控、没有配置中心、没有链路追踪就上微服务,等于蒙眼开赛车。

十一、生产避坑清单(10 条)

  1. 数据库按服务私有:坚决不跨服务直连别人的库,一律走接口;共享库是分布式耦合的最大根源。
  2. 能最终一致就别强一致:优先异步 + 补偿 + 对账,Seata 只在关键链路启用。
  3. 所有远程调用必须兜底:Feign + Sentinel 降级,避免一个慢依赖拖垮整条链路。
  4. 网关只做薄转发:鉴权/灰度/CORS 放全局过滤器,重业务逻辑别塞进去。
  5. 限流阈值靠压测定:QPS/线程数阈值拍脑袋 = 线上事故,务必基于真实容量。
  6. 注册中心要高可用:Nacos 至少集群部署,否则它挂了全集群”失明”。
  7. 配置要可回滚:Nacos 配置变更带版本,敏感项加密,变更走审批。
  8. 接口要幂等:网络重试、消息重投无处不在,写接口靠唯一键/Token 防重复。
  9. 链路追踪采样的度:全量采集成本太高,按业务重要度分级采样。
  10. 服务粒度别过细:按”能独立部署、独立扩容、独立Owner”切,别切到”一个接口一个服务”的自我感动。