微服务基础与核心概念

分布式与微服务的区别?

分布式是一种架构思想(把系统拆分到多台机器);微服务是分布式架构的一种具体实现方案:解耦,一个服务只负责单一业务。微服务一定是分布式,但分布式不一定是微服务。

微服务 vs 单体架构

单体架构:所有功能(用户、订单、支付等)打包在一个部署单元中运行。

  • 痛点:代码耦合度极高;只能整体扩容,资源利用率低;一个内存泄漏可能导致整个服务崩溃。

微服务架构:按业务边界拆分为多个独立进程,通过网络通信。

  • 优势:独立部署(发版快)、按需扩容(例如大促时只给订单服务加机器)、技术异构(不同服务可选不同语言)、故障隔离。
  • 劣势:网络延迟与不可靠性、分布式事务一致性问题、链路排查困难、运维成本剧增。

Boot vs Cloud vs Alibaba

技术栈 核心定位 解决的问题
Spring Boot 快速构建单个后端应用 通过“约定大于配置”消除繁琐的 XML,帮你快速启动一个独立的 Java Web 服务。
Spring Cloud 微服务全局治理“全家桶” 定义了服务发现、网关、负载均衡等一整套标准和接口,它依赖 Spring Boot。
Spring Cloud Alibaba 阿里开源的微服务实现 因为 Netflix 早期组件(Eureka、Hystrix 等)停更,阿里基于国情推出了性能更好、功能更全的落地组件(Nacos、Sentinel),完美接入 Spring Cloud 标准。

微服务通信:RPC 与 HTTP 调用

HTTP(如 Spring Cloud OpenFeign):基于应用层 HTTP 协议,数据格式多为 JSON。优势是跨语言支持极好、灵活、简单;劣势是 HTTP 报文头较重,解析开销大。

RPC(如 Dubbo、gRPC):远程过程调用,底层多基于 TCP 和自定义二进制序列化协议。优势是传输效率极高、延迟低,调用起来像本地方法一样;劣势是强绑定特定框架,跨语言相对麻烦。

如何保证通信

微服务引入了网络这个不稳定因素,所有的组件都是为了解决“网络不可靠”和“节点动态变化”的问题:

  • 服务注册与发现:微服务节点的 IP 是动态分配的。服务启动时把自己的地址写进注册中心(注册),调用方去注册中心查目标地址(发现),而不是把 IP 写死在代码里。
  • 服务雪崩:A 调用 B,B 调用 C。如果 C 宕机,B 因为等待 C 导致线程池阻塞耗尽,A 接着被 B 拖垮,最终整个微服务集群瘫痪。
  • 应对雪崩:
    1. 限流:限制进入系统的并发请求数,把多余的请求拒之门外,保护系统不被打满。
    2. 熔断:当下游服务持续报错时,直接“拉闸”切断调用,不再浪费时间等待。
    3. 降级:发生熔断或资源不足时,执行备用逻辑(如返回默认值或缓存数据),保证核心业务运转。

注册中心Nacos、Eureka

Nacos是 Spring Cloud Alibaba 生态中最核心的组件之一,它一个人干了原来 Spring Cloud Netflix 体系里两个人的活:Eureka(注册中心) + Spring Cloud Config(配置中心)。

Nacos 中的服务和实例有什么区别?

服务是业务能力的名称,实例是这个服务实际运行的进程。

比如写好了一个库存服务,服务名叫 stock-service,然后在三台机器上分别启动:

1
2
3
4
5
服务名:stock-service

实例 A:192.168.1.10:8081
实例 B:192.168.1.11:8081
实例 C:192.168.1.12:8081

Nacos 注册与发现的完整过程

服务注册:服务提供者(Provider)在启动时,通过 REST 请求将自己的元数据(IP、端口、服务名等)注册到 Nacos 服务端。

服务发现:服务消费者(Consumer)向 Nacos 获取服务实例列表,并且缓存在本地内存中。即使 Nacos 全面宕机,短时间内各个微服务仍然可以根据本地缓存的 IP 列表互相通信,只是无法感知到新机器的上下线。

实时更新机制(Pull + Push 结合):

  • Consumer 会开启定时任务(定时 Pull)去同步最新的实例列表。
  • 当 Provider 发生上下线时,Nacos 服务端会通过 UDP 协议主动推送(Push)变更信息给 Consumer,这比单纯靠拉取的延迟要低得多。

注册中心怎么判断服务是否存活?

临时实例 vs 永久实例与健康检查

维度 临时实例 (Ephemeral) - 默认 永久实例 (Persistent)
适用场景 普通的无状态微服务(如订单服务、网关) 必须强一致性的有状态服务(如数据库)
健康检查机制 客户端主动上报(心跳)。默认每 5 秒发一次心跳。 服务端主动探测。Nacos 建立 TCP/HTTP 连接去探测。
宕机处理逻辑 15秒无心跳:标记为不健康。 30秒无心跳:直接从列表中剔除。 探测失败:标记为不健康,但绝对不会剔除,直到手动干预。
对应的 CAP 理论 AP(高可用性),基于 Distro 协议 CP(强一致性),基于 Raft 协议

CAP:一致性,可用性,分区容错性

当网络分区发生时,CAP只能三选二

image-20261013162926484

Nacos 集群

如果只有一台 Nacos,它宕机后:

  • 已运行的服务可能依靠本地缓存继续调用。
  • 新实例的注册、实例变化的获取等操作会受到影响。

Distro 协议(针对临时实例与非持久化配置):

  • AP 协议。
  • 每个节点负责一部分数据,并把自己的数据定期异步同步给其他节点。

Raft 协议(针对永久实例与持久化配置):

  • 选举 Leader,由 Leader 统一处理写请求并同步给 Follower 节点,确保强一致性(CP)。

Nacos 与 Eureka 的区别

功能定位:Nacos = 注册中心 + 配置中心;Eureka 仅仅是注册中心。

CAP 模型:Eureka 只支持 AP 模式;Nacos 默认 AP(针对临时实例),但也支持 CP(针对永久实例),支持动态切换。

发现机制:Eureka 完全依赖客户端定时拉取(通常有 30 秒延迟);Nacos 采用定时拉取 + 服务端 UDP 主动推送,实例上下线感知极快。

社区状态:Eureka 1.x 已经进入维护期不再更新,Nacos 由阿里开源,社区极其活跃。

配置中心Nacos

为什么需要配置中心?

假设商城有订单、库存、商品等多个服务,每个服务又部署了多个实例。

如果配置都放在各自的 application.yml 中,修改一个共享配置时,就可能需要修改多个应用的配置,并重新发布或重启才能生效,管理起来比较麻烦。

配置中心就是把需要集中管理的配置放到统一的位置,由各个服务读取。

主要解决三个问题:

能力 作用
集中管理 在统一位置管理多个服务的配置
环境隔离 开发、测试、生产使用各自的配置
动态更新 部分配置修改后,可以在运行期间生效

配置中心的动态刷新

Nacos 配置中心是如何做到修改配置后,不重启微服务就能实时生效的?

业务侧用法:在需要动态刷新的类上加上 @RefreshScope 注解:能让刷新发生后,让相关 Bean 在下一次使用时重新创建,从而获取新的配置值。

原理:

① 平时通过代理访问 Bean

Spring 为 @RefreshScope Bean 提供代理。业务调用经过代理,转交给实际的目标对象。

目标对象创建后,会被缓存起来,后续调用继续使用,不是每次请求都创建一个对象。

② 刷新时清除目标对象缓存

刷新触发后,作用域中对应的目标对象缓存会被清除。

③ 下次调用时重新创建

下一次通过代理调用时,发现没有目标对象,就重新创建 Bean,并从更新后的配置环境中注入属性。Spring 官方文档将其描述为具有延迟初始化能力的代理。

配置中心宕机了,应用还能正常运行吗?

配置中心宕机后,已经运行的应用通常可以使用内存中的现有配置继续运行,但无法正常接收配置更新。新启动的应用能否启动,取决于是否存在可用快照、本地默认配置,以及远程配置加载失败时的处理策略。对于缺少必要配置且没有可用备用来源的应用,启动可能失败。

负载均衡Spring Cloud LoadBalancer

什么是负载均衡

负载均衡是将请求分配给多个服务实例,避免请求集中在单个实例上,提高系统的吞吐量和可用性。

常见策略:

  • 轮询:按顺序选择实例。
  • 随机:随机选择实例。
  • 加权策略:根据实例权重分配请求。

在 Spring Cloud 中,常见的客户端负载均衡方案是 Spring Cloud LoadBalancer,默认轮询策略。它可以与服务发现、OpenFeign 配合工作。

@LoadBalanced

它可以让我们通过服务名调用服务,并在发送请求前选择具体实例。

1
2
3
4
5
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

使用这个 Bean 发请求:

1
2
3
4
String result = restTemplate.getForObject(
"http://stock-service/stock/1001",
String.class
);

发送请求时,拦截器从 URL 中取出 stock-service,自动去 Nacos 查找同名服务,负载均衡组件从该服务的实例列表中选一个实例,把服务名替换成选中实例的地址,保留原来的路径和参数。

LoadBalancer 和 Nginx 区别

根本区别:Nginx 是服务端(集中式)负载均衡,请求必须先发给 Nginx,由 Nginx 转发,多了一层网络开销。LoadBalancer 是客户端负载均衡,调用方自己从 Nacos 拉取地址列表,在自己的进程内存里执行算法选出一个 IP,直接发起点对点直连。

常见策略:默认是 轮询(RoundRobin) 和 随机(Random)。

负载均衡选中的实例调用失败情况

负载均衡自身只决定这次请求发给谁,是否重试,要看调用客户端、依赖和相关配置,不能认为引入 LoadBalancer 后所有失败请求都会自动换实例重试。

同时超时只说明没有及时收到结果,不代表对方执行失败。

比如:

订单服务选择 A,发送扣库存请求。

A 已经扣减成功。

返回响应时网络异常,订单服务等待超时。

订单服务改为调用 B,再扣一次。

在实际设计时,应该判断当前操作是否适合重试,同时写操作做好幂等,例如重试扣库存都携带同一个业务请求号。

服务调用:OpenFeign

OpenFeign 是什么?

只定义接口 + 加注解,不用手写 RestTemplate 发请求、序列化、解析响应;框架底层用 JDK 动态代理自动生成实现类,帮你完成 HTTP 调用,远程调用像调用本地方法一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 启动类开启
@EnableFeignClients

// 定义远程调用接口
@FeignClient(value = "user-service") // 服务名,注册中心找
public interface UserApi {
@GetMapping("/user/{id}")
UserDTO getUser(@PathVariable("id") Long id);
}

// 业务代码直接注入调用,像本地方法
@Autowired
private UserApi userApi;
UserDTO user = userApi.getUser(1L);

底层调用流程

启动扫描:Spring Boot 启动时,@EnableFeignClients 会扫描所有标注了 @FeignClient 注解的接口。

动态代理生成:通过 JDK 动态代理,为这些接口生成代理对象,并注入到 Spring 容器中。

请求模板构造:当业务代码调用这个接口的方法时,会被代理对象的 InvocationHandler 拦截。拦截器会解析方法上的 Spring MVC 注解(如 @GetMapping、@PathVariable),将参数拼接,构造出一个完整的 HTTP 请求模板(RequestTemplate)。

负载与执行:提取 @FeignClient 中的服务名,交由 LoadBalancer 从 Nacos 本地缓存列表中解析出一个具体的 IP:Port,最后底层通过 HTTP 客户端发起真实的请求。

Gateway网关

在没有网关时,前端需要记录成百上千个微服务的不同 IP,且每个微服务都要自己写一套鉴权逻辑,非常混乱。网关的作用就是:统一入口、统一鉴权、统一限流、统一跨域。

Gateway 的底层是基于 Spring 5 的 WebFlux 响应式编程(非阻塞 I/O,性能极高)。它有三个绝对核心的概念:

  • Route(路由):网关最基础的构建模块。它包含一个 ID、一个目标 URI(去哪)、一组断言(条件)和一组过滤器(操作)。
  • Predicate(断言):判断条件。决定请求是否匹配某个路由。最常用的是 Path 路径断言(例如:如果请求路径是 /user/**,就匹配这个路由)。
  • Filter(过滤器):拦截器。分为路由过滤器(只对特定路由生效)和全局过滤器(对所有路由生效)。可以在请求转发前(Pre)添加请求头,或者在响应返回前(Post)修改响应体。可以通过order配置执行顺序,order 越小,越先执行。

断言判断是否匹配,路由确定转发目标,过滤器执行处理。

Sentinel

Sentinel 是用于流量控制和服务保护的组件,主要提供流量控制、熔断降级、系统保护等能力。

它可以针对资源配置规则,例如限制 QPS,或者在异常比例达到一定阈值时触发熔断。

限流

指标维度:QPS vs 线程数

  • QPS 限流:严格限制每秒进入的请求数量,超过就拒绝。
  • 线程数限流:当调用下游某个慢接口导致当前服务的线程被大量挂起占用时,限制该接口最多只能占用多少个线程,防止拖垮整个微服务。

限流算法

快速失败(默认):超过阈值直接抛出 FlowException。

Warm Up(预热):基于令牌桶算法。如果系统很久没流量,突然来了一波大流量,直接放行可能会把冷启动的系统(如数据库连接池未初始化)打死。Warm Up 会让通过的流量在设定的时间内缓慢增加,最终达到设定的阈值。

排队等待(匀速排队):基于漏桶算法。在优惠券秒杀抢购等场景中,瞬间涌入的请求远超数据库处理能力。排队等待会把“突刺流量”拉平,让请求以固定的时间间隔通过(例如严格每 100ms 放行一个),超时未通过的请求才被丢弃。

热点参数限流

普通流控是整个接口(资源)维度限流;热点参数限流是【同一个参数值】单独统计 QPS,对热点参数做精细限流,防止单个热点把数据库 / 缓存打崩,典型用来防缓存击穿、秒杀热点商品。

熔断

Sentinel 的熔断机制底层依靠一个状态机来运作:

  • Closed(关闭状态):一切正常,请求正常放行。当统计窗口内的“慢调用比例”、“异常比例”或“异常数”达到配置的阈值时,触发熔断。
  • Open(熔断状态):断路器打开。此时所有发往该下游的请求都会被直接拦截(Fast-Fail),并触发自定义的 Fallback 降级逻辑(比如返回“当前排队人数过多,请稍后再试”)。
  • Half-Open(半开状态):经过一个设定的“熔断时间窗口”(如 5 秒)后,Sentinel 不会立刻恢复所有流量,而是进入半开探测状态。它会尝试放行一个或几个探测请求去请求下游:
    • 如果探测请求成功,说明下游恢复了,状态机切回 Closed。
    • 如果探测请求依然失败,说明下游还没喘过气,状态机切回 Open,重新开始倒计时。

Sentinel 根据什么触发熔断?

策略 关注什么 示例
慢调用比例 响应太慢的请求占比 超过 500ms 算慢调用,慢调用比例超过 50%
异常比例 发生异常的请求占比 异常请求比例超过 30%
异常数 发生异常的请求数量 统计窗口内异常数超过 10 次

降级

监控某个资源(接口 / Feign 远程调用)的健康指标,如果下游持续慢、报错多,就打开熔断器,一段时间内不再调用下游,直接走兜底逻辑,防止服务雪崩。

Sentinel规则持久化

Sentinel 默认控制台推送的规则保存在应用内存中,重启后会丢失。可以接入 Nacos 动态数据源,将规则持久化到配置中心,由应用加载并监听更新。要注意,仅接入数据源不会让控制台修改自动写回 Nacos,需要统一规则发布入口为nacos,再让nacos去更新sentinel。

分布式事务与 Seata

微服务中,各服务通常独立管理数据库,本地事务只能保证自身事务范围内的原子性,无法自动覆盖远程服务已经提交的操作。因此,需要通过分布式事务框架或可靠消息、补偿机制,保证跨服务业务最终达到一致状态。

三大组件

角色 职责 对应位置
TM,事务管理器 发起全局事务,请求提交或回滚 订单服务中的事务入口
RM,资源管理器 管理分支事务,注册分支、报告状态、执行提交或回滚 订单、库存服务中的 Seata 客户端
TC,事务协调器 维护事务状态,协调各分支提交或回滚 通常独立部署的 Seata Server

TM 发起,RM 执行分支,TC 统一协调,XID 串联事务。

AT 模式

  • 一阶段(Prepare)

RM 拿到 XID,执行业务 SQL,先查询数据前后镜像 写入 undo_log(同本地事务内)

1
2
修改前:库存 100(前镜像)
修改后:库存 99(后镜像)

执行业务 update,拿数据库本地行锁

向 TC 申请全局锁(全局锁由 TC 维护)

本地事务提交:业务 SQL + undo_log 一起提交,释放数据库本地行锁

重点:一阶段本地事务已经提交,数据已经对其他 SQL 可见,这是 AT 隔离问题根源。 全局锁不锁数据库行,是 TC 内存 / DB 里的逻辑锁,用来防止脏写。

  • 二阶段 Commit TC 收到全局提交决议后通知 RM commit;RM异步删除 undo_log,直接结束,非常快。

  • 二阶段 Rollback TC 通知 RM 回滚;RM 读取 undo_log,校验镜像,反向 SQL 补偿回滚业务数据;回滚成功后删除 undo_log;失败会不断重试,保证最终一致。

两个锁区分:

本地行锁:数据库原生锁,一阶段事务提交就释放

全局锁:TC 维护的逻辑排他锁,一阶段提交前拿到,二阶段完成才释放,用来防止脏写

TCC 模式

TCC 要由业务代码实现三个操作:Try 预留资源、Confirm 确认使用、Cancel 释放资源。虽然有三个方法,但属于两阶段:第一阶段执行 Try,第二阶段执行 Confirm 或 Cancel。

操作 做什么 可用库存 冻结库存
Try 检查库存,把 2 件转为冻结 98 2
Confirm 确认售出,消耗冻结库存 98 0
Cancel 取消购买,把冻结库存退回 100 0

相比 AT 依赖 undo_log 自动补偿,TCC 需要业务自己实现三个方法,控制更灵活,但实现更复杂。

Saga 模式

Saga 将长事务拆成多个独立提交的本地事务,失败后通过业务补偿撤销已完成步骤的影响。相比 TCC 的资源预留模式,Saga 更适合长流程,但不保证全局隔离,需要处理幂等、补偿失败和中间状态可见等问题。

不用 Seata,怎么通过消息保证跨服务数据一致?

本地消息表

① 在同一个数据库事务里,创建订单并写入待发送事件

1
2
3
4
开启订单库事务
→ 创建订单,状态为“待确认”
→ 插入“请求扣库存”事件
→ 提交事务

因为在同一本地事务中,订单和事件要么都写入,要么都不写入,避免“订单创建了,却没留下发消息的记录”。

② 后台任务发送消息

扫描待发送事件,发送到 MQ。收到发送成功确认后,再标记事件已发送;失败则重试。

如果消息已经发成功,但还没标记就宕机,恢复后可能重复发送,所以不能依赖它保证绝对不重复。

③ 库存服务幂等消费

库存服务根据事件 ID 等做防重复,在自己的本地事务中完成:

  • 扣库存。
  • 记录处理结果。
  • 需要反馈订单时,同时记录结果事件。

④ 根据结果推进订单状态

扣库存成功,订单变为“已确认”;库存不足,订单变为“已取消”。

结果通知也要可靠发送,订单侧也要幂等处理,不能只保证前半段。