第九篇:微服务与分布式——把一个大系统拆成多个小服务

发布时间:2026/7/15 22:26:39

第九篇:微服务与分布式——把一个大系统拆成多个小服务 别再背组件名了你只需要知道“拆开了之后怎么让它们好好聊天、不出乱子”写在前面微服务是Java面试里**最“唬人”**的一块。什么服务注册、服务发现、负载均衡、熔断降级、分布式事务、分布式ID……名词一大堆感觉很高大上。但微服务的本质就一句话把一个系统拆成多个小系统各自独立部署。为什么要拆因为一个系统太大100个人改一个代码仓库天天冲突一台服务器扛不住所有流量一个功能挂了整个系统都瘫了。拆开之后问题就来了——拆开的服务之间怎么通信怎么知道对方在哪对方挂了怎么办怎么保证数据一致性Spring Cloud 就是解决这些问题的“工具箱”。一、注册中心本质上还是个Map你前面的理解完全正确A调用BA怎么知道B在哪注册中心就是解决这个问题的。1.1 注册中心干了什么服务注册B启动时把自己的IP、端口、服务名上报给注册中心比如Nacos、Eureka。服务发现A要调B时从注册中心拉取B的地址列表。健康检查B定期发心跳给注册中心告诉“我还活着”。如果B挂了注册中心把它踢掉。底层还是那个Mapkey服务名valueIP列表。1.2 细节不是每次都去查注册中心面试常问如果每次调用B都去注册中心查注册中心会被高并发打爆。真实流程A启动时从注册中心拉取全量服务列表缓存到本地内存。A调用B时直接从本地缓存拿地址。A和注册中心保持一个长连接心跳注册中心发现B有变动比如B多了一个节点会主动Push推送给AA更新本地缓存。所以注册中心不是每次调用时的“中介”而是“启动时的信息同步器 变更时的通知器”。1.3 Nacos vs Eureka选型理由对比NacosEureka健康检查支持主动心跳 服务端主动探测只支持客户端心跳保护模式支持支持配置中心自带没有要配合Spring Cloud Config状态活跃开发Spring Cloud Alibaba已进入维护状态Netflix宣布停更现在国内大厂基本不用Eureka了Nacos是标配因为阿里的技术栈渗透率太高了。二、负载均衡从多个节点里选一个B部署了多个节点比如B1、B2、B3A调用B时到底调哪一个2.1 Ribbon / Spring Cloud LoadBalancer负载均衡器从本地缓存的地址列表里按照某种策略选一个出来。默认策略轮询Round Robin——轮流来第一个请求B1第二个B2第三个B3第四个又B1。其他策略随机Random随机挑一个很少用。加权响应时间响应时间短的节点多分配请求复杂但精准。最小并发数当前处理请求最少的节点优先BestAvailableRule适合长连接或处理时长不均的场景。2.2 负载均衡发生在哪客户端负载均衡Ribbon/LoadBalancer是嵌入在A服务里的A自己选一个节点发起HTTP请求。没有独立的负载均衡器机器。这和Nginx服务端负载均衡不同Nginx是独立的代理服务器请求先到NginxNginx转发到后端。Spring Cloud用的是客户端负载均衡因为在微服务架构里每个服务都是调用方也是被调用方不需要单点代理层。三、服务调用封装HTTP请求OpenFeign你前面说的“服务调用说白了就是封装了HTTP调用流程”完全正确。3.1 Feign干了什么你写FeignClient(namestock-service)publicinterfaceStockClient{GetMapping(/stock/{productId})StockInfogetStock(PathVariable(productId)LongproductId);}你直接调stockClient.getStock(123L)Feign底层帮你做了从注册中心拿到stock-service的IP列表。负载均衡选一个节点比如192.168.1.100:8080。拼URL。发起HTTP请求用OKHttp或HttpClient。把返回的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为例关闭状态正常请求正常发往B。开启状态跳闸错误率达到阈值比如50%熔断器打开。接下来N秒内A根本不发起HTTP请求直接返回降级结果比如“商品服务繁忙请稍后再试”。给B喘息的时间。半开状态试探N秒后熔断器允许少量请求通过比如1个试试B好了没。如果成功熔断器关闭如果失败熔断器重新开启再等N秒。熔断和限流的本质区别面试关键限流在服务提供方B的门口限流——防止自己被冲垮防“内伤”。熔断在服务调用方A内部跳闸——防止自己被慢依赖拖死防“传染”。4.3 Sentinel vs Hystrix对比SentinelHystrix已停更状态活跃阿里出品停更Netflix功能限流、熔断、热点参数限流、系统自适应保护主要就熔断和线程隔离控制台自带Dashboard需要自己搭建埋点方式多种适配Web、Dubbo、gRPC需用HystrixCommand现在线上新项目选SentinelHystrix已经不建议用在新的生产环境了。五、网关Gateway统一小区大门你前面的理解“分发用户请求到不同的模块”基本对但网关不只是转发。网关做的事情路由转发用户请求/order/**转到订单服务/user/**转到用户服务。统一鉴权所有请求先过网关网关验证Token是否有效放行才转发。不需要每个服务自己写鉴权拦截器。限流在网关层对总入口做限流比如全系统总QPS不超过10万。日志/监控记录所有请求的入参、出参、耗时统一埋点。Spring Cloud Gateway基于WebFlux响应式编程非阻塞I/O性能远超Zuul 1.x同步阻塞。配置方式routesfilters路由过滤器链。六、分布式事务重点中的重点面试官只要问微服务分布式事务跑不掉。你已经理解了核心概念这里把技术选型和本质彻底说透。场景A订单扣款B库存扣库存。如何在分布式环境下保证一致性6.1 2PC/3PC基本不用2PC两阶段提交准备阶段 提交阶段。有一个事务协调者。所有参与者都准备好协调者才发提交命令。问题协调者单点故障准备阶段锁资源时间长性能差。3PC加了个超时机制但本质没解决性能问题。互联网高并发系统很少用2PC/3PC因为锁资源和同步阻塞太影响性能。6.2 TCCTry-Confirm-Cancel——代码侵入大但强一致你之前说“TCC侵入性大一般不常用”有点绝对了。在资金交易、金融转账等强一致性场景TCC是主流方案之一。TCC三阶段Try尝试执行。比如“冻结账户里的100元”不实际扣只是冻结。Confirm确认执行。Try全部成功后执行Confirm“把冻结的100元正式划走”。Cancel取消回滚。任何一个Try失败执行所有Cancel“解冻这100元”。为什么侵入性大业务系统要实现Try、Confirm、Cancel三个接口每个接口都要写对应的业务逻辑冻结、解冻、正式扣减。而且这三个接口必须幂等Confirm和Cancel可能被重复调用。适合场景支付、转账、机票锁座。对资金数据零容忍。6.3 事务消息RocketMQ——最终一致性代码侵入小你之前已经理解得很好了但再来回顾一下本质核心三步骤发送半消息对消费者不可见。执行本地事务A扣款跟本地事务绑定。根据本地事务结果发Commit/Rollback二次确认。Commit后消息可见B扣库存。反查兜底如果A发Commit时宕机RocketMQ会定时回查A“到底成功了没”A查数据库告诉MQ是Commit还是Rollback。本质这是一种最终一致性方案允许短暂毫秒级的不一致。如果B扣库存失败不会回滚A的扣款因为A已经成功了而是重试 人工补偿。适合下单扣库存、积分增加、日志记录。6.4 Seata AT模式——对代码0侵入但性能一般Seata ATAuto-Transaction的原理是拦截SQL记录数据修改前后的快照before image和after image到undo_log表。如果事务回滚根据undo_log自动生成反向SQL类似数据库的MVCC。基于改良版2PC对代码零侵入只要加GlobalTransactional。缺点全局锁机制在热点数据上有性能瓶颈且依赖数据库的undo_log表对数据库有一定的资源消耗。适合并发要求不高的场景。选型总结面试核心强一致 资金类→ TCC最终一致 高并发库存、积分→ 事务消息RocketMQ老系统改造/非核心业务→ Seata AT0侵入七、分布式ID雪花算法数据库分库分表后不能再用自增ID了多个表自增会冲突。需要一个全局唯一ID生成器。常见方案UUID32位16进制字符串生成简单但无序、太长不适合做数据库主键B树插入性能差。Redis自增INCR key但依赖Redis网络开销大。雪花算法SnowflakeTwitter开源最常用。雪花算法的结构64位Long面试能画出结构图就赢了一半| 1 bit 符号位 | 41 bits 时间戳 | 10 bits 机器ID | 12 bits 序列号 |解读1bit最高位固定为0保证ID为正数。41bit时间戳毫秒级能支撑约69年2^41 / 1000 / 3600 / 24 / 365 ≈ 69年。10bit机器ID5位数据中心 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到底在编排什么把这个“集装箱调度”彻底讲透。下期见。

相关新闻