第九篇:微服务与分布式——把一个大系统拆成多个小服务
2026/7/15 22:26:32 网站建设 项目流程

别再背组件名了,你只需要知道“拆开了之后,怎么让它们好好聊天、不出乱子”

写在前面

微服务是Java面试里**最“唬人”**的一块。什么服务注册、服务发现、负载均衡、熔断降级、分布式事务、分布式ID……名词一大堆,感觉很高大上。

但微服务的本质就一句话:把一个系统拆成多个小系统,各自独立部署。

为什么要拆?因为一个系统太大,100个人改一个代码仓库,天天冲突;一台服务器扛不住所有流量,一个功能挂了整个系统都瘫了。

拆开之后问题就来了——拆开的服务之间怎么通信?怎么知道对方在哪?对方挂了怎么办?怎么保证数据一致性?

Spring Cloud 就是解决这些问题的“工具箱”。

一、注册中心:本质上还是个Map

你前面的理解完全正确:A调用B,A怎么知道B在哪?注册中心就是解决这个问题的。

1.1 注册中心干了什么?

  1. 服务注册:B启动时,把自己的IP、端口、服务名上报给注册中心(比如Nacos、Eureka)。
  2. 服务发现:A要调B时,从注册中心拉取B的地址列表。
  3. 健康检查:B定期发心跳给注册中心,告诉“我还活着”。如果B挂了,注册中心把它踢掉。

底层还是那个Map(key=服务名,value=IP列表)

1.2 细节:不是每次都去查注册中心(面试常问)

如果每次调用B都去注册中心查,注册中心会被高并发打爆。

真实流程

  1. A启动时,从注册中心拉取全量服务列表缓存到本地内存
  2. A调用B时,直接从本地缓存拿地址。
  3. A和注册中心保持一个长连接(心跳),注册中心发现B有变动(比如B多了一个节点),会主动Push(推送)给A,A更新本地缓存。

所以注册中心不是每次调用时的“中介”,而是“启动时的信息同步器 + 变更时的通知器”。

1.3 Nacos vs Eureka(选型理由)

对比NacosEureka
健康检查支持主动心跳 + 服务端主动探测只支持客户端心跳
保护模式支持支持
配置中心自带没有(要配合Spring Cloud Config)
状态活跃开发(Spring Cloud Alibaba)已进入维护状态(Netflix宣布停更)

现在国内大厂基本不用Eureka了,Nacos是标配(因为阿里的技术栈渗透率太高了)。

二、负载均衡:从多个节点里选一个

B部署了多个节点(比如B1、B2、B3),A调用B时,到底调哪一个?

2.1 Ribbon / Spring Cloud LoadBalancer

负载均衡器从本地缓存的地址列表里,按照某种策略选一个出来。

默认策略轮询(Round Robin)——轮流来,第一个请求B1,第二个B2,第三个B3,第四个又B1。

其他策略

  • 随机(Random):随机挑一个(很少用)。
  • 加权响应时间:响应时间短的节点多分配请求(复杂但精准)。
  • 最小并发数:当前处理请求最少的节点优先(BestAvailableRule,适合长连接或处理时长不均的场景)。

2.2 负载均衡发生在哪?

客户端负载均衡:Ribbon/LoadBalancer是嵌入在A服务里的,A自己选一个节点发起HTTP请求。没有独立的负载均衡器机器。

这和Nginx(服务端负载均衡)不同:Nginx是独立的代理服务器,请求先到Nginx,Nginx转发到后端。

Spring Cloud用的是客户端负载均衡,因为在微服务架构里,每个服务都是调用方也是被调用方,不需要单点代理层。

三、服务调用:封装HTTP请求(OpenFeign)

你前面说的“服务调用说白了就是封装了HTTP调用流程”,完全正确。

3.1 Feign干了什么?

你写:

@FeignClient(name="stock-service")publicinterfaceStockClient{@GetMapping("/stock/{productId}")StockInfogetStock(@PathVariable("productId")LongproductId);}

你直接调stockClient.getStock(123L),Feign底层帮你做了:

  1. 从注册中心拿到stock-service的IP列表。
  2. 负载均衡选一个节点(比如192.168.1.100:8080)。
  3. 拼URL:``。
  4. 发起HTTP请求(用OKHttp或HttpClient)。
  5. 把返回的JSON转成StockInfo对象。

你不需要手动拼接URL、处理HTTP连接、解析JSON,一个接口搞定。

3.2 Feign和Ribbon/LoadBalancer的关系

Feign内置了负载均衡器。你调stock-service时,它先去本地缓存里查stock-service的IP列表,再经过负载均衡选一个,然后发起HTTP请求。Feign本身也集成了服务发现能力。

四、熔断 vs 限流(你之前搞混了,这里彻底讲清)

你之前在“熔断”那块说的“1000并发来了10000请求,只处理1000个”,这其实是限流,不是熔断。面试里这两个概念经常被捆绑问,但它们的保护对象完全不同

4.1 限流(Rate Limiting)——保护“自己”

限流:保护的是服务提供方(B)。B只能处理1000个并发,来了10000个,我就只处理1000个,剩下9000个直接返回“系统繁忙”或者排队。目的:别把我自己打死了。

常用算法

  • 令牌桶(Token Bucket):每秒放N个令牌,拿到令牌的才能处理请求。允许突发流量(令牌可以积累)。
  • 漏桶(Leaky Bucket):请求像水一样流进桶里,桶以固定速率漏出去。削平流量,不允许突发。
  • 计数器:滑动窗口计数(比如Sentinel的QPS限流)。

4.2 熔断(Circuit Breaker)——保护“别人”

熔断:保护的是服务调用方(A)。B响应越来越慢(比如从50ms变成了5秒),A的线程池里所有线程都被B的慢请求卡住了,A自己的内存和CPU资源耗尽,整个A服务跟着挂掉

熔断器的逻辑(以Resilience4j为例):

  1. 关闭状态(正常):请求正常发往B。
  2. 开启状态(跳闸):错误率达到阈值(比如50%),熔断器打开。接下来N秒内,A根本不发起HTTP请求,直接返回降级结果(比如“商品服务繁忙,请稍后再试”)。给B喘息的时间。
  3. 半开状态(试探):N秒后,熔断器允许少量请求通过(比如1个),试试B好了没。如果成功,熔断器关闭;如果失败,熔断器重新开启,再等N秒。

熔断和限流的本质区别(面试关键)

  • 限流:在服务提供方(B)的门口限流——防止自己被冲垮(防“内伤”)。
  • 熔断:在服务调用方(A)内部跳闸——防止自己被慢依赖拖死(防“传染”)。

4.3 Sentinel vs Hystrix

对比SentinelHystrix(已停更)
状态活跃(阿里出品)停更(Netflix)
功能限流、熔断、热点参数限流、系统自适应保护主要就熔断和线程隔离
控制台自带Dashboard需要自己搭建
埋点方式多种适配(Web、Dubbo、gRPC)需用@HystrixCommand

现在线上新项目选Sentinel,Hystrix已经不建议用在新的生产环境了。

五、网关(Gateway):统一小区大门

你前面的理解“分发用户请求到不同的模块”基本对,但网关不只是转发。

网关做的事情

  1. 路由转发:用户请求/order/**转到订单服务,/user/**转到用户服务。
  2. 统一鉴权:所有请求先过网关,网关验证Token是否有效,放行才转发。不需要每个服务自己写鉴权拦截器。
  3. 限流:在网关层对总入口做限流(比如全系统总QPS不超过10万)。
  4. 日志/监控:记录所有请求的入参、出参、耗时(统一埋点)。

Spring Cloud Gateway

  • 基于WebFlux(响应式编程),非阻塞I/O,性能远超Zuul 1.x(同步阻塞)。
  • 配置方式:routes+filters(路由+过滤器链)。

六、分布式事务(重点中的重点)

面试官只要问微服务,分布式事务跑不掉。你已经理解了核心概念,这里把技术选型和本质彻底说透。

场景:A(订单)扣款,B(库存)扣库存。如何在分布式环境下保证一致性?

6.1 2PC/3PC(基本不用)

  • 2PC(两阶段提交):准备阶段 + 提交阶段。有一个事务协调者。所有参与者都准备好,协调者才发提交命令。问题:协调者单点故障;准备阶段锁资源时间长;性能差。
  • 3PC:加了个超时机制,但本质没解决性能问题。
  • 互联网高并发系统很少用2PC/3PC,因为锁资源和同步阻塞太影响性能。

6.2 TCC(Try-Confirm-Cancel)——代码侵入大,但强一致

你之前说“TCC侵入性大,一般不常用”有点绝对了。在资金交易、金融转账等强一致性场景,TCC是主流方案之一。

TCC三阶段

  • Try:尝试执行。比如“冻结账户里的100元”(不实际扣,只是冻结)。
  • Confirm:确认执行。Try全部成功后,执行Confirm(“把冻结的100元正式划走”)。
  • Cancel:取消回滚。任何一个Try失败,执行所有Cancel(“解冻这100元”)。

为什么侵入性大?
业务系统要实现TryConfirmCancel三个接口,每个接口都要写对应的业务逻辑(冻结、解冻、正式扣减)。而且这三个接口必须幂等(Confirm和Cancel可能被重复调用)。

适合场景:支付、转账、机票锁座。对资金数据零容忍。

6.3 事务消息(RocketMQ)——最终一致性,代码侵入小

你之前已经理解得很好了,但再来回顾一下本质:

核心三步骤

  1. 发送半消息(对消费者不可见)。
  2. 执行本地事务(A扣款),跟本地事务绑定
  3. 根据本地事务结果发Commit/Rollback(二次确认)。Commit后消息可见,B扣库存。

反查兜底:如果A发Commit时宕机,RocketMQ会定时回查A:“到底成功了没?”A查数据库告诉MQ是Commit还是Rollback。

本质:这是一种最终一致性方案,允许短暂(毫秒级)的不一致。如果B扣库存失败,不会回滚A的扣款(因为A已经成功了),而是重试 + 人工补偿。适合:下单扣库存、积分增加、日志记录。

6.4 Seata AT模式——对代码0侵入,但性能一般

Seata AT(Auto-Transaction)的原理是:

  1. 拦截SQL,记录数据修改前后的快照(before imageafter image)到undo_log表。
  2. 如果事务回滚,根据undo_log自动生成反向SQL(类似数据库的MVCC)。
  3. 基于改良版2PC,对代码零侵入(只要加@GlobalTransactional)。

缺点:全局锁机制在热点数据上有性能瓶颈,且依赖数据库的undo_log表,对数据库有一定的资源消耗。适合并发要求不高的场景。

选型总结(面试核心)

  • 强一致 + 资金类→ TCC
  • 最终一致 + 高并发(库存、积分)→ 事务消息(RocketMQ)
  • 老系统改造/非核心业务→ Seata AT(0侵入)

七、分布式ID(雪花算法)

数据库分库分表后,不能再用自增ID了(多个表自增会冲突)。需要一个全局唯一ID生成器

常见方案

  • UUID:32位16进制字符串,生成简单,但无序、太长,不适合做数据库主键(B+树插入性能差)。
  • Redis自增INCR key,但依赖Redis,网络开销大。
  • 雪花算法(Snowflake):Twitter开源,最常用

雪花算法的结构(64位Long,面试能画出结构图就赢了一半)

| 1 bit 符号位 | 41 bits 时间戳 | 10 bits 机器ID | 12 bits 序列号 |

解读

  • 1bit:最高位固定为0(保证ID为正数)。
  • 41bit:时间戳(毫秒级),能支撑约69年(2^41 / 1000 / 3600 / 24 / 365 ≈ 69年)。
  • 10bit:机器ID(5位数据中心 + 5位机器),最多支持1024个节点。
  • 12bit:序列号,同一毫秒内最多生成4096个ID(如果不满足,则自旋等待下一毫秒)。

优点:生成快、趋势递增(时间戳在前,方便数据库插入)、不依赖第三方。

八、分布式链路追踪(SkyWalking / Zipkin)

问题:用户一个请求进来,经过了网关→订单→库存→积分,如果某个环节慢了,你根本不知道慢在哪。

链路追踪:给每个请求生成一个全局唯一的Trace ID,在各个服务间传递(放在HTTP Header里)。调用链上的每个Span(跨度)都记录开始时间、结束时间、调用关系。

SkyWalking会把整个调用链画成一张图,让你一眼看到哪个环节耗时最长

在实际开发中:一般公司会要求日志里带上TraceId,方便排查问题。如果没单独部署SkyWalking,至少要把TraceId打在日志里,方便基于日志平台检索。

九、面试官真正想听什么

场景1:“你们项目怎么拆微服务的?”

加分回答:

“我们是按业务域拆的:订单域、用户域、商品域、库存域。每个域独立数据库,通过接口调用或MQ通信。拆分后遇到了分布式事务问题,下单场景我们用了RocketMQ事务消息保证最终一致性。网关用Spring Cloud Gateway做统一鉴权和路由。注册中心用Nacos,因为它同时支持服务注册和配置管理。”

场景2:“熔断和限流你们怎么做的?”

加分回答(纠正你之前的认知,然后给出实战):

“限流和熔断是两个不同的保护机制。限流我们在网关层和Sentinel配合,对总QPS做限制,超过阈值直接返回‘繁忙’(保护服务方不被冲垮)。熔断我们在调用外部第三方接口时配置了Resilience4j,设置错误率超过50%就跳闸5秒,走降级逻辑(返回兜底数据),防止外部接口慢把我们自己的服务拖死。”

场景3:“分布式事务你们用的哪种方案?”

加分回答(体现架构思考):

“分场景。核心资金流水我们用的TCC,虽然代码侵入大,但数据强一致性有保障。普通的订单扣库存用的RocketMQ事务消息,因为我们允许库存有短暂的延迟,最终一致即可,而且消息方案不会长时间锁表,性能更好。Seata AT我们也评估过,但觉得全局锁在热点商品上可能会成为性能瓶颈,就没用。”

场景4:“雪花算法生成的ID有重复过吗?”

加分回答:

“我们用的开源实现(美团Leaf),时钟回拨是个隐患。如果服务器时间被人为调回去,可能生成重复ID。我们的解决策略是:NTP时间同步做了特殊配置(缓慢调整不回调),并在代码里加了时钟回拨检测,回拨超过5ms就抛异常报警,人工介入。”

小结

核心概念大白话面试考点
注册中心存服务地址的Map本地缓存+心跳推送
负载均衡从多个节点里选一个轮询/加权等策略
Feign封装HTTP调用注解声明式接口
限流人多不接了(保护自己)令牌桶、漏桶
熔断对方病了不等了(保护调用方)状态机(关→开→半开)
网关统一小区大门路由+鉴权+限流
分布式事务跨服务保一致性TCC / 事务消息 / Seata
分布式ID雪花算法64位结构
链路追踪看请求卡在哪一环TraceID透传

记住一句话:微服务就是把大系统拆成小系统,然后解决“拆开后怎么通信、怎么治理、怎么保数据一致”的问题。所有组件都是为这三个目标服务的。

下一篇我们讲Docker & K8s——容器到底比虚拟机轻在哪?K8s到底在编排什么?把这个“集装箱调度”彻底讲透。下期见。

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

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

立即咨询