微服务面试题

发布于:2025-08-13 ⋅ 阅读:(25) ⋅ 点赞:(0)

微服务与单体架构、SOA的区别?微服务的优缺点及适用场景?

架构对比:单体 vs SOA vs 微服务
1. 单体架构(Monolithic)
  • 比喻:像一个“大箱子”,所有功能(用户管理、订单、支付等)都打包在一个应用里,共用同一个数据库。

  • 特点

    • 开发简单,部署直接(一个WAR包扔到服务器)。

    • 但代码臃肿,改一个小功能可能影响全局,扩展时需要整体扩容(浪费资源)。

2. SOA(面向服务架构)
  • 比喻:像“公司部门”,每个部门(服务)有明确职责(如财务部、人事部),通过ESB(企业服务总线)沟通。

  • 特点

    • 服务可重用(多个系统共用同一个“人事服务”)。

    • 但ESB容易成为性能瓶颈,服务粒度较粗(比如一个“订单服务”可能仍然包含很多功能)。

3. 微服务(Microservices)
  • 比喻:像“外卖平台”,每个小团队专精一件事(骑手接单、商家接单、支付结算),通过API或消息协作。

  • 特点

    • 服务彻底拆分(一个服务只做一件事),独立部署、独立数据库。

    • 轻量级通信(HTTP/RPC),不用ESB,适合快速迭代。

三者的核心区别
维度 单体架构 SOA 微服务
拆分粒度 无拆分,整体应用 按业务模块粗粒度拆分 按功能细粒度拆分
通信方式 内部方法调用 ESB(重量级) HTTP/RPC(轻量级)
数据库 共享一个数据库 可能共享或部分独立 每个服务独立数据库
部署 整体部署 部分独立部署 完全独立部署
适用场景 小型简单项目 传统企业系统集成 高并发、快速迭代系统

微服务的优点

  1. 灵活扩展:某个服务压力大(如秒杀),只扩容这个服务即可。

  2. 技术异构:不同服务可以用不同语言(订单用Java,推荐用Python)。

  3. 容错性高:一个服务挂了,不影响其他服务(比如支付挂了,用户还能浏览商品)。

  4. 快速迭代:小团队专注一个服务,发布频率更高。

微服务的缺点

  1. 复杂度高:要处理分布式事务、服务发现、链路追踪等问题。

  2. 运维成本:需要监控几十个服务,部署和排错更麻烦。

  3. 网络延迟:服务间调用多了,可能拖慢整体性能。


🎯 微服务的适用场景

  1. 大型复杂系统:比如电商平台(订单、库存、支付等模块需要独立演进)。

  2. 高并发需求:需要针对热点服务快速扩容(如双11的秒杀服务)。

  3. 团队规模大:多个小团队能并行开发不同服务。

⚠️ 不适合微服务的场景

  • 小型项目:比如一个内部管理系统,用微服务只会增加复杂度。

  • 强事务系统:比如银行核心系统,分布式事务难以保证一致性。


🌰 举个栗子

  • 单体架构:一个小餐馆,一个厨师包办切菜、炒菜、洗碗。

  • SOA:中型餐厅,有专门的切菜工、炒菜工,但由经理(ESB)协调。

  • 微服务:连锁快餐店,每家分店独立运营(专做汉堡/炸鸡),通过标准流程协作。

💡 关键总结:微服务不是银弹,选择架构时要权衡复杂度业务需求

拆分原则(DDD限界上下文、高内聚低耦合)、康威定律、分布式系统挑战(网络延迟、数据一致性)。

🧩 拆分原则:DDD限界上下文 & 高内聚低耦合

1. DDD限界上下文(Bounded Context)
  • 比喻:像公司里的不同部门

    • 财务部:只管钱,用“财务语言”(如会计分录)。

    • 技术部:只管代码,用“技术语言”(如Git提交)。

    • 如果财务部突然用技术部的“Git”来记账,就乱套了!

  • 核心思想

    • 一个微服务对应一个明确的业务边界(比如“订单服务”只管订单生命周期,不插手库存逻辑)。

    • 不同服务之间用“协议”(API)沟通,避免内部实现细节泄露。

2. 高内聚低耦合
  • 高内聚:像一台洗衣机

    • 所有洗衣相关功能(加水、漂洗、脱水)都封装在一起,不会把“脱水按钮”放到冰箱上。

  • 低耦合:像手机和充电器

    • 手机只需知道充电器有USB接口,不需要关心充电器内部是快充还是慢充。

  • 微服务中的应用

    • 订单服务内部高度自治(内聚),对外只暴露“创建订单”接口(耦合度低)。


📜 康威定律(Conway's Law)

  • 原话“设计系统的组织,其产生的架构等同于组织的沟通结构。”

  • 通俗解释团队怎么坐,代码就会怎么分!

    • 例子1:如果公司有前端组、后端组、数据库组,系统大概率会拆成“前端+后端+数据库”三层架构。

    • 例子2:如果按业务分团队(订单组、支付组),系统自然会拆成“订单服务+支付服务”。

  • 微服务启示

    • 如果你想拆微服务,先调整团队结构(比如每个小团队负责一个服务)。

    • 反例:如果所有人都在一个群里沟通,最终会写出单体架构!


分布式系统挑战

1. 网络延迟
  • 比喻:像跨国快递

    • 本地调用(同一城市):今天下单,明天到货。

    • 远程调用(跨国):今天下单,下个月到货,还可能丢包(快递丢了)。

  • 问题

    • 服务A调用服务B,如果B响应慢,A可能被拖垮(必须设置超时时间!)。

    • 解决方案:

      • 用缓存(如Redis)减少远程调用。

      • 异步通信(发个消息就走,不等回复)。

2. 数据一致性
  • 比喻:像多人编辑在线文档

    • 用户A把标题改成“微服务”,用户B同时改成“分布式”,最后听谁的?

  • 问题

    • 订单服务扣了库存,但支付服务失败,要不要回滚库存?(分布式事务问题)

  • 解决方案

    • 最终一致性:先告诉用户“支付中”,后台慢慢同步数据(像支付宝的“处理中”状态)。

    • 补偿机制:支付失败后,发消息通知订单服务释放库存。


🌰 举个实际例子

假设开发一个外卖系统

  1. 限界上下文拆分

    • 订单服务:处理下单、状态跟踪。

    • 库存服务:管理餐厅菜品库存。

    • 骑手服务:分配骑手。

    • 错误示范:订单服务直接操作库存数据库(耦合过高)。

  2. 康威定律应用

    • 如果公司有“订单组”、“库存组”、“骑手组”,自然拆分成对应微服务。

  3. 分布式挑战

    • 用户下单时,订单服务调用库存服务扣减库存,但网络超时了怎么办?

      • 方案:库存服务提供“预占库存”接口,超时后自动释放。


📌 一句话总结

  • 拆分原则:按业务能力划分服务(DDD),关起门来做事(高内聚),对外简单沟通(低耦合)。

  • 康威定律:团队结构决定架构,想改架构先改团队!

  • 分布式挑战:网络不可靠(延迟/丢包),数据难同步(一致性),要有备选方案(降级/重试)。

💡 理解这些概念后,微服务设计就像“乐高积木”——每块积木(服务)独立且明确,组合起来才能搭建复杂系统!

如何按业务域拆分服务?如何避免过度拆分?

🛒 案例背景:外卖平台

假设你要把单体应用拆成微服务,核心功能包括:

  • 用户点餐

  • 餐厅管理菜品库存

  • 骑手接单配送

  • 支付结算


🔪 第一步:按业务域拆分服务(DDD思想)

1. 识别核心业务域

问一个问题:“这个功能离开其他模块还能独立运行吗?”

业务域 功能举例 能否独立?
用户域 注册、登录、浏览餐厅 ✅ 可以
订单域 下单、订单状态跟踪 ❌ 需要库存和支付
库存域 扣减库存、库存预警 ✅ 可以
配送域 分配骑手、轨迹追踪 ✅ 可以
支付域 支付、退款 ❌ 需要订单数据
2. 划定服务边界
  • 强关联功能放一起(高内聚):

    • 例子:订单的“创建订单”和“取消订单”必须在一个服务里。

  • 弱关联功能拆开(低耦合):

    • 例子:用户评价餐厅和库存管理完全无关,拆成两个服务。

3. 最终拆分方案
1. **用户服务**:账号管理、个人信息  
2. **订单服务**:下单、订单查询、取消订单  
3. **库存服务**:实时库存管理、自动补货  
4. **配送服务**:骑手调度、配送状态  
5. **支付服务**:支付、退款、对账  

🚧 如何避免过度拆分?

过度拆分的典型症状
  • 服务A调用服务B,B又调用服务C… 调用链超过3层(“死亡调⽤链”)。

  • 改一个小需求要同时发布5个服务(团队崩溃)。

  • 服务之间频繁传大量数据(网络IO成为瓶颈)。

拆分刹车原则
  1. 两个披萨原则

    • 一个服务的代码和功能应该控制在2个披萨能喂饱的团队(约6-8人)能维护的范围内。

  2. 事务边界检查

    • 如果两个操作必须在一个数据库事务中完成(比如下单和扣库存),就别拆开。

    • 反例:把“订单明细”和“订单主表”拆成两个服务 → 分布式事务噩梦!

  3. 性能敏感区保护

    • 高频调用的模块(如商品详情查询)不要拆得太细,避免多次网络请求。

  4. 团队能力匹配

    • 如果团队只有5个人,却拆了20个服务 → 运维复杂度爆炸!


🌰 回到外卖案例:合理 vs 过度拆分

合理拆分
用户服务 → 订单服务 → 库存服务  
           ↘ 支付服务  
           ↘ 配送服务  
  • 订单服务作为枢纽,协调支付、库存、配送。

过度拆分
用户服务 → 订单服务 → 订单明细服务 → 优惠券服务 → 库存服务 → 库存日志服务  
  • 问题:

    • 查一个订单要连环调用5个服务(延迟高)。

    • 优惠券和库存日志明显属于订单/库存的内部逻辑,无需独立。


🔧 拆分后检查清单

  1. 独立性:服务能否独立开发、部署、扩容?

  2. 通信成本:服务间调用是否简单(API清晰)?

  3. 团队负载:每个服务是否有明确的维护团队?

  4. 故障隔离:一个服务挂了是否不会级联崩溃?


📌 一句话总结

  • 按业务域拆分:像切蛋糕,每块包含完整功能(用户、订单、库存…)。

  • 避免过度拆分:别把蛋糕切成面包屑!遵循“事务边界”和“团队能力”。

  • 衡量标准:拆到“增加新功能时,只需要改1-2个服务”就是合理粒度。

💡 记住:微服务拆分是持续演进的,初期可以粗一点,随着业务复杂再逐步细化!

电商系统中订单、库存、支付服务的边界划分依据

🛒 电商核心流程举例

用户下单购买一台手机:

  1. 订单服务:生成订单(谁买的?买什么?收货地址?)

  2. 库存服务:检查并扣减库存(仓库里还有没有货?)

  3. 支付服务:收钱(用户付款→商家到账)


🔍 边界划分依据

1. 订单服务
  • 职责:管理订单的生命周期

    • 创建订单、查询订单、取消订单、订单状态流转(待付款→待发货→已完成)

  • 边界标志

    • 不关心库存具体怎么扣(只调用库存接口),不关心支付渠道(支付宝还是微信)。

    • 核心数据:订单ID、用户ID、商品ID、数量、总价、收货地址。

2. 库存服务
  • 职责:保障库存准确性和实时性

    • 库存查询、预占库存(下单先锁定)、释放库存(取消订单时返还)、实际扣减(支付成功后)。

  • 边界标志

    • 不关心谁下的单(只接收订单ID和商品ID),不关心支付金额。

    • 核心数据:商品ID、总库存量、已锁定库存量、实际库存量。

3. 支付服务
  • 职责:处理资金流

    • 发起支付、支付回调、退款、对账。

  • 边界标志

    • 不关心订单买了什么(只认订单ID和金额),不关心库存状态。

    • 核心数据:支付流水号、订单ID、支付金额、支付状态。


⚠️ 边界冲突的典型问题

错误示范
  • 订单服务直接操作库存数据库

    • 后果:库存逻辑变更(如增加库存预警)会影响到订单服务,耦合过高。

  • 支付服务回调时直接修改订单状态

    • 后果:支付服务需要知道订单状态机规则(如“已付款”后才能变“待发货”),违反单一职责。

正确交互方式

📌 划分边界的核心原则

  1. 数据所有权

    • 库存数据只归库存服务管,支付数据只归支付服务管,订单服务只维护订单状态。

  2. 操作封闭性

    • 扣库存的逻辑(如防止超卖)必须在库存服务内部实现,其他服务只能通过接口调用。

  3. 变更隔离

    • 如果支付渠道从支付宝换成微信,只需要改支付服务,订单服务无需变动。


🌰 实际场景验证

场景:用户取消订单
  1. 订单服务:标记订单状态为“已取消”。

  2. 库存服务:调用释放库存接口(返还预占的库存)。

  3. 支付服务:如果已支付,触发退款流程。

  • 为什么不能合并?

    • 库存服务需要处理其他系统的库存释放(如秒杀活动),不能依赖订单服务逻辑。


🧩 一句话总结

  • 订单服务:管“交易流程”(买什么+谁买的+送到哪)。

  • 库存服务:管“货”(有没有货+怎么扣库存)。

  • 支付服务:管“钱”(怎么收钱+怎么退钱)。

  • 关键:通过API交互,绝不跨服务直接操作数据库

💡 记住:如果两个服务频繁需要同步大量数据共享数据库表,说明边界划错了!

🌐 二、Spring Cloud生态深度考察

1. 核心组件解析
组件 作用 面试重点
Eureka 服务注册与发现 自我保护机制、集群部署方案、与Nacos的AP/CP模型对比
Ribbon 客户端负载均衡 7种负载策略(轮询/随机/权重)、自定义IRule实现
Feign 声明式HTTP客户端 动态代理原理、日志配置、超时控制
Hystrix 熔断降级 熔断阈值(10s内50%失败)、降级逻辑设计
Gateway API网关 路由过滤、限流(令牌桶算法)、跨域处理
2. 进阶问题
  • Nacos:动态配置管理、配置监听原理、集群CP模式配置

  • Seata:AT/XA/TCC模式区别,AT模式下的写隔离实现


📡 三、服务通信机制

RESTful设计规范(状态码使用、版本控制)、OpenFeign性能优化(连接池配置)

一、RESTful 设计规范

RESTful API 是一种基于 HTTP 协议的设计风格,强调资源(Resource)状态(State)的管理。以下是核心规范:


1. 状态码(HTTP Status Codes)

RESTful API 使用 HTTP 状态码表示请求结果,主要分为几类:

状态码 含义 适用场景
200 OK 请求成功 GET/PUT/PATCH 成功返回数据
201 Created 资源创建成功 POST 创建新资源(如新增订单)
204 No Content 成功但无返回数据 DELETE 成功删除资源
400 Bad Request 客户端请求错误 参数校验失败(如订单ID格式错误)
401 Unauthorized 未授权 用户未登录或 Token 无效
403 Forbidden 禁止访问 用户无权限(如普通用户访问管理员接口)
404 Not Found 资源不存在 请求的订单ID在数据库不存在
500 Internal Server Error 服务器错误 代码异常(如数据库连接失败)

✅ 最佳实践:

  • 不要所有接口都返回 200 + 自定义错误码(如 {code: 500, msg: "error"}),而是正确使用 HTTP 状态码。

  • GET /orders/123

    • 存在订单 → 200 OK + 订单数据

    • 不存在 → 404 Not Found

    • 参数错误 → 400 Bad Request


2. 版本控制(API Versioning)

API 迭代时,需保证兼容性,常见版本控制方式:

方式 示例 优缺点
URL 路径 https://api.com/v1/orders 直观,但 URL 会变(推荐✅)
请求头(Header) Accept: application/vnd.api.v1+json 更灵活,但不易调试
查询参数(Query) https://api.com/orders?version=1 不推荐,影响缓存

✅ 最佳实践:

  • 使用 URL 路径版本(如 /v1/orders),简单易维护。

  • 旧版本保留一段时间,等所有客户端升级后再废弃。


二、OpenFeign 性能优化(连接池配置)

OpenFeign 是 Spring Cloud 的声明式 HTTP 客户端,默认使用 JDK 的 HttpURLConnection(无连接池),性能较差。优化方案:

1. 替换为 Apache HttpClient / OKHttp

(1)引入依赖

<!-- 使用 Apache HttpClient -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
​
<!-- 或使用 OKHttp -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

(2)配置连接池(以 Apache HttpClient 为例)

# application.yml
feign:
  httpclient:
    enabled: true
    max-connections: 200       # 最大连接数(默认 200)
    max-connections-per-route: 50  # 每个路由(目标主机)的最大连接数(默认 50)
    connection-timeout: 2000   # 连接超时时间(ms)
    time-to-live: 900000       # 连接存活时间(ms,默认不限制)
2. 关键参数说明
参数 作用 推荐值
max-connections 整个连接池的最大连接数 200~500(根据并发量调整)
max-connections-per-route 单个服务的最大连接数(如订单服务调用库存服务) 50~100
connection-timeout 建立 TCP 连接的超时时间 2000ms(2秒)
time-to-live 连接空闲多久后关闭 5~15分钟(避免频繁重建连接)
3. 其他优化
  • 启用 GZIP 压缩(减少传输数据量):

    feign:
      compression:
        request:
          enabled: true
        response:
          enabled: true
  • 配置重试机制(避免网络抖动导致失败):

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(1000, 2000, 3); // 重试间隔1s,最大间隔2s,最多3次
    }

🌰 实战示例

场景:订单服务调用库存服务
@FeignClient(name = "inventory-service", url = "http://inventory:8080")
public interface InventoryClient {
    
    @PostMapping("/v1/inventory/deduct")
    ResponseEntity<Void> deductStock(@RequestBody DeductRequest request);
}
  • 状态码:库存服务返回 200(成功)或 400(库存不足)。

  • 性能优化:使用 Apache HttpClient + 连接池,避免频繁创建 TCP 连接。


📌 总结

  1. RESTful 规范

    • 状态码:正确使用 HTTP 状态码,不要滥用 200

    • 版本控制:推荐 /v1/orders 路径方式。

  2. OpenFeign 优化

    • 使用 Apache HttpClient / OKHttp 替代默认实现。

    • 调整 连接池参数(最大连接数、超时时间)。

    • 可选:GZIP 压缩、重试机制。

💡 记住:RESTful 是约定,不是法律,团队统一风格比严格遵循更重要!

Kafka vs RabbitMQ选型(吞吐量 vs 事务消息)

🚀 核心对比:Kafka vs RabbitMQ

维度 Kafka RabbitMQ
设计目标 高吞吐、分布式日志流 企业级消息队列(复杂路由、可靠投递)
吞吐量 ⭐⭐⭐⭐ 单机可达百万级/秒 ⭐⭐ 单机约几万级/秒
延迟 毫秒级(批量发送) 微秒级(实时性更高)
消息存储 持久化到磁盘(支持长期保留) 内存+磁盘(默认内存,可配置持久化)
事务消息 支持(但较复杂) 支持(更成熟,兼容AMQP协议)
消息顺序 分区内严格有序 单个队列有序(需独占消费者)
适用场景 日志采集、大数据流处理、实时分析 业务解耦、异步任务、金融级事务消息

📊 选型关键:吞吐量 vs 事务消息

1. 选 Kafka 的情况
  • 场景:需要处理海量数据,且允许少量延迟或重复。

    • 例子

      • 用户行为日志收集(如点击流分析)。

      • 物联网设备数据上报(每秒百万级传感器数据)。

    • 优势

      • 高吞吐:通过分区(Partition)批量发送提升性能。

      • 高可用:数据多副本存储,故障自动切换。

2. 选 RabbitMQ 的情况
  • 场景:需要强一致性或复杂消息路由。

    • 例子

      • 电商支付系统(要求支付消息绝对不丢失)。

      • 订单状态流转(需严格按顺序处理:创建→支付→发货)。

    • 优势

      • 事务消息:通过 AMQP协议Publisher Confirms 保证可靠性。

      • 灵活路由:支持直连(Direct)、主题(Topic)、扇出(Fanout)等交换机类型。


性能 vs 可靠性取舍

Kafka 的高吞吐秘密
  • 批量发送:攒一波消息再发(减少网络IO)。

  • 零拷贝技术:减少数据在内核和用户空间的拷贝。

  • 分区并行:多个消费者可同时读取不同分区。

RabbitMQ 的可靠性保障
  • 消息确认(ACK):消费者处理完消息后显式ACK,否则重发。

  • 死信队列(DLX):处理失败的消息自动转入死信队列。

  • 事务模式

    // RabbitMQ 事务示例(Java)
    channel.txSelect();
    try {
        channel.basicPublish(...); // 发送消息
        channel.txCommit(); // 提交事务
    } catch (Exception e) {
        channel.txRollback(); // 回滚
    }

🌰 实际案例对比

场景1:双11秒杀系统
  • 选 Kafka

    • 瞬时超高并发(每秒10万订单),允许短暂消息积压。

    • 用Kafka削峰填谷,后台慢慢处理订单。

场景2:银行转账系统
  • 选 RabbitMQ

    • 要求每笔转账消息必须成功(用事务消息+死信队列)。

    • 严格顺序:A转账给B → B收到钱 → 记录流水。


🔧 选型 checklist

  1. 数据量

    • 每天 > 1亿条消息 → Kafka。

    • 每天 < 1000万条 → RabbitMQ。

  2. 实时性

    • 要求微秒级响应 → RabbitMQ。

    • 允许毫秒级延迟 → Kafka。

  3. 可靠性

    • 允许极少量丢消息(如日志) → Kafka。

    • 金融级零丢失 → RabbitMQ。


📌 一句话总结

  • Kafka:像“货运火车”,一次拉海量数据,适合日志、大数据场景。

  • RabbitMQ:像“快递小哥”,精准投递每件包裹,适合业务解耦、金融交易。

💡 如果既要高吞吐又要事务消息? 可以组合使用:Kafka 处理流量 + RabbitMQ 处理关键事务(如支付回调)。

消息可靠性保障(生产者确认+消费者ACK+死信队列)

🍔 场景设定

假设你开了一家餐厅,顾客通过小程序下单,后厨做菜,骑手送餐。如何保证订单不丢、不重复、不卡住?

角色 对应消息队列 可能的问题
顾客下单 生产者(Producer)发消息 消息未成功发送到队列(网络闪断)
后厨接单做菜 消费者(Consumer)处理消息 后厨崩溃,订单丢失
订单异常 死信队列(DLQ) 顾客地址错误,订单无法完成

🔐 三大保障机制

1. 生产者确认(Producer Confirm)

目标:确保消息成功到达队列。 原理

  • 生产者发送消息后,MQ服务器会返回一个确认信号(类似快递签收回执)。

  • 如果MQ没收到消息(比如网络断了),生产者可以重发

配置示例(RabbitMQ)

// 开启生产者确认模式
channel.confirmSelect();
// 发送消息
channel.basicPublish(exchange, routingKey, null, message.getBytes());
// 等待服务器确认(同步方式,也可用异步回调)
if (channel.waitForConfirms()) {
    System.out.println("消息已送达MQ!");
} else {
    System.out.println("消息丢失,需要重发!");
}
2. 消费者ACK机制

目标:确保消息被成功处理。 原理

  • 消费者处理完消息后,必须显式发送ACK(告诉MQ可以删消息了)。

  • 如果消费者崩溃没发ACK,MQ会重新投递给其他消费者。

模式对比

ACK模式 行为 适用场景
自动ACK(auto) 消费者收到消息就算成功(易丢消息) 允许丢失(如日志统计)
手动ACK(manual) 处理完业务逻辑后手动确认 要求严格不丢(如支付订单)

代码示例

// 关闭自动ACK(改为手动)
channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String tag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
        try {
            System.out.println("处理消息:" + new String(body));
            // 业务逻辑完成,手动ACK
            channel.basicAck(envelope.getDeliveryTag(), false);
        } catch (Exception e) {
            // 处理失败,拒绝消息(可设置是否重新入队)
            channel.basicNack(envelope.getDeliveryTag(), false, true);
        }
    }
});
3. 死信队列(Dead Letter Queue, DLQ)

目标:处理无法正常消费的消息(比如订单地址错误)。 原理

  • 当消息被拒绝超时未处理时,自动转发到死信队列。

  • 死信队列可以单独监控,人工或自动化处理。

触发条件(RabbitMQ):

  1. 消息被消费者拒绝(basic.rejectbasic.nack)且不重新入队。

  2. 消息在队列中存活时间(TTL)过期。

  3. 队列长度超过限制。

配置示例

// 创建死信交换机和队列
channel.exchangeDeclare("dlx.exchange", "direct");
channel.queueDeclare("dlx.queue", true, false, false, null);
channel.queueBind("dlx.queue", "dlx.exchange", "dlx.routingkey");
​
// 普通队列绑定死信交换机
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dlx.exchange");
args.put("x-dead-letter-routing-key", "dlx.routingkey");
channel.queueDeclare("normal.queue", true, false, false, args);

🌟 全流程保障(外卖订餐案例)

  1. 生产者确认

    • 顾客下单 → 小程序显示“订单提交中”,直到收到MQ确认 → 显示“订单已接收”。

  2. 消费者ACK

    • 后厨做好菜 → 点击“完成”按钮(手动ACK) → MQ删除订单消息。

    • 如果后厨崩溃没点“完成”,5分钟后订单重新分配给其他厨师。

  3. 死信队列

    • 骑手发现地址错误 → 标记“配送失败”(NACK) → 订单进入死信队列 → 客服电话联系顾客。


📌 常见问题解决方案

问题 解决方式
消息重复(消费者ACK超时) 消费者业务逻辑做幂等处理(如订单ID去重)
消息积压 增加消费者数量 + 监控死信队列
顺序消费 Kafka用分区键/RabbitMQ用单队列单消费者

💡 一句话总结

  • 生产者确认:确保消息送到MQ(像快递发货单)。

  • 消费者ACK:确保消息被处理完(像顾客签收快递)。

  • 死信队列:兜底处理异常消息(像退货仓库)。

  • 最终目标消息不丢、不卡、不重复

gRPC协议优势(HTTP/2多路复用、Protobuf序列化),对比REST性能差异

🚚 核心对比:gRPC vs REST

维度 gRPC REST(HTTP/JSON)
协议层 基于 HTTP/2(二进制协议) 基于 HTTP/1.1(文本协议)
数据传输 Protobuf(二进制,体积小) JSON(文本,体积大)
多路复用 ✅ 一个连接同时处理多个请求(不排队) ❌ 多个请求需顺序处理(队头阻塞)
速度 ⭐⭐⭐⭐ 快(编码/解码效率高,延迟低) ⭐⭐ 慢(JSON解析/序列化开销大)
适用场景 微服务内部高频调用(如订单服务调库存服务) 对外暴露API(如移动端/网页调用)

🚀 gRPC 的两大核心优势

1. HTTP/2 多路复用(Multiplexing)
  • 问题:HTTP/1.1 像单车道快递,即使有10辆快递车(请求),也必须一辆一辆排队送(队头阻塞)。

  • gRPC 解决方案:HTTP/2 像多车道高速路,所有快递车可以同时跑(一个TCP连接并行处理多个请求/响应)。

    • 效果:延迟降低50%+,尤其适合高频小数据量调用(如服务间心跳检测)。

2. Protobuf 序列化
  • 问题:JSON 像用文字描述快递物品(“一台重2kg的银色MacBook”),体积大且解析慢。

  • Protobuf 解决方案:像用条形码编码快递物品0xAE3C),体积小且机器直接可读。

    // Protobuf 定义(.proto文件)
    message Order {
      int32 id = 1;
      string product = 2;
      int32 quantity = 3;
    }
    • 效果

      • 数据体积比 JSON 小3-10倍(节省带宽)。

      • 编解码速度比 JSON 快5-100倍(CPU开销低)。


性能对比实测

假设订单服务调用库存服务 1万次

指标 gRPC REST/JSON
总耗时 2秒 8秒
网络流量 10MB 50MB
CPU占用 20% 70%

📌 关键结论:gRPC 在高并发、低延迟场景下优势明显!


🌰 何时用 gRPC?何时用 REST?

用 gRPC
  • 微服务内部调用(如订单服务→库存服务)。

  • 需要高性能(如游戏服务器实时通信)。

  • 流式数据传输(如实时日志推送,gRPC支持双向流)。

用 REST
  • 对外提供API(如移动端/网页调用,JSON更通用)。

  • 需要浏览器兼容(gRPC-Web仍有限制)。

  • 快速原型开发(JSON易于调试,Postman直接测试)。


🔧 gRPC 的缺点

  1. 可读性差:Protobuf 是二进制,不能直接像 JSON 那样肉眼阅读。

  2. 生态工具少:调试工具不如 REST 丰富(需用grpcurl等专用工具)。

  3. 浏览器支持弱:需通过gRPC-Web网关转换。


📌 一句话总结

  • gRPC 快在哪里

    • 多路复用(HTTP/2)→ 减少网络等待。

    • Protobuf 编码 → 体积小、解码快。

  • REST 适合谁:需要通用性、可读性的场景。

  • 选择建议

    • 对性能敏感?选 gRPC

    • 对兼容性敏感?选 REST

💡 就像送快递——

  • 内部调货用集装箱卡车(gRPC:高效但封闭)。

  • 送货上门用普通快递(REST:灵活但慢一点)。

Hystrix vs Sentinel区别?Sentinel如何基于QPS/响应时间动态熔断?

🔌 Hystrix vs Sentinel 核心区别

维度 Hystrix(Netflix) Sentinel(阿里)
设计目标 服务容错(熔断、降级) 流量控制+熔断+系统自适应保护
熔断策略 基于错误率(默认5秒内20%失败) 支持QPS、响应时间、错误率多维触发
流量控制 简单限流(需手动扩展) 内置精准限流(直接配置QPS阈值)
实时监控 需依赖Hystrix Dashboard 自带实时监控和控制台(开箱即用)
扩展性 扩展需改代码 支持动态规则配置(无需重启)
适用场景 传统微服务容错 高并发场景下的精细化流量治理

💡 简单说:Hystrix像老式保险丝,熔断后需手动重置;Sentinel像智能电表,能实时调节电流


Sentinel 动态熔断规则详解

Sentinel 的熔断策略基于三种核心指标,以下用“电商下单”场景举例:

1. 基于 QPS(每秒请求数)熔断
  • 场景:秒杀活动时,库存服务每秒最多处理1000次查询,超过则熔断。

  • 规则配置

    FlowRule rule = new FlowRule();
    rule.setResource("queryStock"); // 资源名
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流类型
    rule.setCount(1000); // 阈值:1000次/秒
    FlowRuleManager.loadRules(Collections.singletonList(rule));
    • 效果:当 queryStock 接口的 QPS > 1000 时,新请求会被直接拒绝(走降级逻辑)。

2. 基于响应时间熔断
  • 场景:订单服务调用支付服务,若平均响应时间 > 500ms,说明支付系统压力大,触发熔断。

  • 规则配置

    DegradeRule rule = new DegradeRule();
    rule.setResource("payOrder");
    rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 按响应时间熔断
    rule.setCount(500); // 阈值:500ms
    rule.setTimeWindow(10); // 熔断时长:10秒
    DegradeRuleManager.loadRules(Collections.singletonList(rule));
    • 效果:当 payOrder 接口的平均RT > 500ms 时:

      1. 熔断开启:接下来10秒内所有请求直接降级(如返回“支付繁忙”)。

      2. 10秒后:自动尝试放行一个请求,若成功则关闭熔断。

3. 基于异常比例熔断
  • 场景:用户服务调用短信服务,若30%的请求失败(如短信接口超时),则熔断。

  • 规则配置

    DegradeRule rule = new DegradeRule();
    rule.setResource("sendSMS");
    rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); // 按异常比例
    rule.setCount(0.3); // 阈值:30%
    rule.setTimeWindow(60); // 熔断时长:60秒
    DegradeRuleManager.loadRules(Collections.singletonList(rule));

🌰 动态熔断全流程示例

问题:订单服务调用库存服务时,如何防止雪崩? 解决方案

  1. 定义规则

    • QPS > 500 或 平均RT > 300ms 或 错误率 > 25% → 熔断5秒。

  2. 触发熔断

    • 若库存服务因高并发开始变慢(RT=400ms),Sentinel 自动熔断。

  3. 降级处理

    • 熔断期间,订单服务直接读取本地缓存库存(或返回“库存计算中”)。

  4. 自动恢复

    • 5秒后,Sentinel 放行一个请求测试,若库存服务恢复(RT<300ms),则关闭熔断。

📌 为什么选 Sentinel?

  1. 更灵活:支持QPS、RT、错误率多种熔断维度。

  2. 更直观:控制台实时查看流量和熔断状态。

  3. 更智能:支持热点参数限流(如针对某个商品ID限流)。

💡 Hystrix 已停止更新,新项目建议直接用 Sentinel!

订单服务调用库存服务超时,如何设计降级策略?

🍔 场景设定

  • 订单服务:用户下单购买商品(比如一份汉堡套餐)。

  • 库存服务:检查并扣减库存(确认汉堡和薯条是否还有货)。

  • 问题:库存服务响应超时(比如网络延迟或库存服务压力大),订单服务该如何处理?


🛡️ 降级策略设计

1. 快速失败(Fail-Fast)

策略:如果库存服务超时(比如超过 500ms 未响应),订单服务立即放弃等待,直接走降级逻辑。 适用场景:高并发秒杀,宁可少卖也不能让系统雪崩。 实现代码(Spring Cloud + OpenFeign):

@FeignClient(name = "inventory-service", fallback = InventoryFallback.class)
public interface InventoryClient {
    @PostMapping("/inventory/deduct")
    ResponseEntity<Boolean> deductStock(@RequestBody DeductRequest request);
}
​
// 降级实现类
@Component
public class InventoryFallback implements InventoryClient {
    @Override
    public ResponseEntity<Boolean> deductStock(DeductRequest request) {
        // 降级逻辑:记录日志,返回"库存不足"(避免用户直接看到超时错误)
        log.warn("库存服务超时,降级返回false");
        return ResponseEntity.ok(false);
    }
}

效果:用户看到“库存不足”,实际可能是库存服务暂时不可用。


2. 默认库存(Cache Fallback)

策略:超时后,读取本地缓存的库存数据(比如Redis中记录的库存快照)。 适用场景:允许短暂数据不一致(如普通商品购买)。 实现步骤

  1. 订单服务定期从库存服务同步库存快照到Redis。

  2. 超时后,查询Redis中的库存:

    boolean hasStock = redisTemplate.opsForValue().get("stock:" + productId) > 0;
    if (hasStock) {
        // 继续创建订单(实际库存可能不准,后续需核对)
    } else {
        // 返回"库存不足"
    }

风险:可能超卖(缓存库存 > 实际库存),需事后核对(如支付前再检查一次)。


3. 异步重试 + 补偿

策略:超时后先让订单成功,后台异步重试扣库存,失败则补偿(如取消订单)。 适用场景:对用户体验要求高(如机票下单)。 实现流程

  1. 订单服务先创建订单(状态为“库存确认中”)。

  2. 发消息到MQ,让库存服务异步处理:

    // 订单服务
    orderService.createOrder(order); // 状态为PENDING
    mqProducer.send("stock-check-queue", orderId);
  3. 库存服务消费消息,扣减库存:

    • 成功 → 更新订单状态为“已确认”。

    • 失败 → 发消息通知订单服务取消订单。


4. 熔断降级(Circuit Breaker)

策略:如果库存服务连续超时,直接熔断,所有请求走降级(如返回“系统繁忙”)。 工具:使用 SentinelHystrix 配置规则:

# Sentinel规则示例
spring:
  cloud:
    sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: inventory-rules
            rule-type: degrade
# Nacos中配置的规则
[
  {
    "resource": "POST:/inventory/deduct",
    "grade": 1,             // 基于响应时间
    "count": 500,           // 阈值500ms
    "timeWindow": 10        // 熔断10秒
  }
]

效果:超时过多时,订单服务直接不走库存检查,10秒后自动恢复。


📌 策略选择建议

策略 优点 缺点 适用场景
快速失败 简单,保护系统 用户体验差(直接失败) 秒杀、高并发场景
默认库存 用户体验好 可能超卖 普通商品购买
异步重试+补偿 平衡体验和一致性 实现复杂 机票、酒店等高价值订单
熔断降级 防止雪崩 粗粒度控制 库存服务完全不可用

🌰 实际案例:电商下单

  1. 正常流程

    • 用户下单 → 订单服务同步调用库存服务扣减 → 成功则创建订单。

  2. 降级流程

    • 库存服务超时(500ms未响应)→ 订单服务查Redis库存快照 → 有货则创建订单(标记“待核对”)。

    • 后台Job每隔5分钟同步实际库存,修正错误订单。


💡 一句话总结

  • 优先保护系统:超时后快速降级,避免拖垮订单服务。

  • 平衡一致性和体验:根据业务选择策略(如秒杀用快速失败,普通订单用缓存降级)。

  • 事后补偿:通过对账、异步任务修复数据不一致。

就像餐厅忙时——

  • 如果厨师来不及做菜,直接告诉顾客“卖完了”(快速失败)。

  • 或者先接单,再悄悄换一道菜(缓存降级)😉。

负载均衡算法(通俗易懂版)

负载均衡(Load Balancing)就像餐厅的服务员分配顾客座位,目标是让所有服务员(服务器)的 workload(工作量)尽量均衡,避免有人忙死,有人闲死。

以下是常见的负载均衡算法,用餐厅比喻帮你理解:


1. 轮询(Round Robin)

🍽️ 比喻:服务员按顺序把顾客分配到每张桌子(服务器),1号桌、2号桌、3号桌……循环往复。 📌 特点

  • 简单、公平,每个服务器轮流接请求。

  • 缺点:如果某台服务器性能差(比如3号桌椅子坏了),还是会分配顾客过去,导致响应变慢。

适用场景:服务器性能相近,请求耗时均匀(如静态资源分发)。


2. 加权轮询(Weighted Round Robin)

🍽️ 比喻:有的桌子(服务器)更大(性能更好),就多分配一些顾客(请求)给它。 📌 特点

  • 给高性能服务器更高权重(比如2号桌分配2个顾客,1号桌分配1个顾客)。

  • 缺点:权重固定,无法动态调整(如果某台服务器突然变慢,还是会继续分配请求)。

适用场景:服务器性能差异较大(如有的机器CPU更强)。


3. 随机(Random)

🍽️ 比喻:闭着眼睛随便指一张桌子,指到哪张就坐哪张。 📌 特点

  • 简单,适合无状态请求。

  • 缺点:可能某个服务器运气不好,短时间内被分配大量请求。

适用场景:简单场景,服务器性能相近。


4. 最少连接(Least Connections)

🍽️ 比喻:看哪张桌子的顾客最少(服务器当前处理的请求最少),就分配新顾客过去。 📌 特点

  • 动态调整,能自动避开繁忙的服务器。

  • 缺点:需要实时统计连接数,计算开销稍大。

适用场景:长连接场景(如WebSocket、数据库连接池)。


5. 响应时间加权(Response Time Based)

🍽️ 比喻:哪张桌子的服务员(服务器)上菜最快,就多分配顾客过去。 📌 特点

  • 动态调整,优先选择响应快的服务器。

  • 缺点:需要持续监控响应时间,实现复杂。

适用场景:对延迟敏感的服务(如API网关)。


6. 一致性哈希(Consistent Hashing)

🍽️ 比喻:固定某些顾客(请求)必须坐某张桌子(服务器),比如VIP顾客永远去1号桌。 📌 特点

  • 相同请求(如相同用户ID)总是落到同一台服务器,适合缓存场景。

  • 缺点:服务器增减时,部分请求会重新分配(但影响较小)。

适用场景:缓存服务器(如Redis集群)、分布式Session。


📌 总结:如何选择?

算法 优点 缺点 适用场景
轮询 简单、公平 不考虑服务器负载 静态资源、简单服务
加权轮询 适应不同性能服务器 权重固定 服务器性能不均
随机 实现简单 可能不均匀 无状态服务
最少连接 动态均衡 计算开销稍大 长连接(如数据库、WebSocket)
响应时间加权 优先选最快的服务器 实现复杂 对延迟敏感的服务(API网关)
一致性哈希 减少缓存失效 增减服务器需调整 缓存、Session存储

🌰 实际案例

  • 电商系统

    • 用户请求 → 轮询/加权轮询(确保每台订单服务均匀处理)。

    • 购物车数据 → 一致性哈希(保证同一用户的请求落到同一台服务器)。

    • 支付接口 → 响应时间加权(选择最快的支付网关)。


💡 一句话总结

  • 轮询/随机:简单但不够智能。

  • 最少连接/响应时间:动态调整,更均衡。

  • 一致性哈希:适合缓存、固定路由场景。

就像餐厅经理分配顾客,目标是最优的用餐体验(低延迟)和员工效率(高吞吐)! 🚀

Nginx漏桶算法(突发流量控制) vs 网关令牌桶算法(匀速处理)

🚰 1. 漏桶算法(Leaky Bucket)—— Nginx常用

比喻

  • 想象一个底部有洞的水桶(漏桶),不管水(请求)以多快的速度倒进来,水都以恒定速度从洞漏出。

  • 如果水倒得太快,桶满了(超过容量),多余的水就会溢出(拒绝请求)。

特点: ✅ 严格控制速率:无论突发流量多大,出口流量始终匀速(如每秒10个请求)。 ❌ 无法应对突发:即使系统有空闲资源,也无法突然处理更多请求(因为漏速固定)。

Nginx配置示例

http {
    limit_req_zone $binary_remote_addr zone=my_limit:10m rate=10r/s; # 每秒10次请求
​
    server {
        location / {
            limit_req zone=my_limit burst=20; # 桶容量=20
            # 超过速率时,第21个请求直接返回503
        }
    }
}

适用场景

  • 需要绝对平滑的流量(如银行交易,防止突发压垮系统)。


🪙 2. 令牌桶算法(Token Bucket)—— 网关常用

比喻

  • 想象一个管理员每隔一段时间往桶里放一枚令牌(Token),请求必须拿到令牌才能处理。

  • 如果桶里有令牌,请求立即被处理;如果桶空了,请求要么等待,要么被拒绝。

  • 允许突发:如果桶里攒了10个令牌,突然来了10个请求,可以一次性处理!

特点: ✅ 允许突发流量:只要桶里有令牌,就能快速处理。 ✅ 灵活控制平均速率:通过调整令牌生成速度(如每秒5个令牌)。

Spring Cloud Gateway配置示例

spring:
  cloud:
    gateway:
      routes:
        - id: my_route
          uri: http://service
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10 # 每秒生成10个令牌
                redis-rate-limiter.burstCapacity: 20 # 桶容量=20

适用场景

  • 需要兼顾突发和平稳的场景(如秒杀系统,平时限流,秒杀时允许短时高并发)。


🔍 核心区别对比

维度 漏桶算法 令牌桶算法
流量形状 严格匀速(像直线) 允许突发(像脉冲)
突发处理 ❌ 直接拒绝 ✅ 可消耗积攒的令牌
实现复杂度 简单(只需计数) 稍复杂(需管理令牌)
典型应用 Nginx限流 网关限流(如Spring Cloud Gateway)

🌰 实际例子

  1. 漏桶

    • 地铁进站口,不管多少人排队,闸机每秒只放行1个人(匀速)。

    • 人太多时,后面的人直接劝退(拒绝请求)。

  2. 令牌桶

    • 游乐园热门项目,每隔10秒发5张快速通行证(令牌)。

    • 早上人少时,通行证能攒一堆,中午突然来了一群游客,可以一次性用掉所有通行证(突发处理)。


📌 如何选择?

  • 漏桶:需要严格限制速率(如API对外提供稳定服务)。

  • 令牌桶:希望尽量处理请求,允许短期爆发(如电商大促)。

💡 记住:

  • 漏桶是“冷漠的保安”,只按规矩放行。

  • 令牌桶是“灵活的售票员”,可以通融攒票的人!

CAP理论与BASE理论(通俗易懂版)

1. CAP理论:分布式系统的“三选二”难题

CAP理论指出,分布式系统最多只能同时满足以下三个特性中的两个:

  • C(Consistency,一致性):所有节点在同一时间的数据完全一致(强一致)。

  • A(Availability,可用性):每次请求都能得到响应(不保证数据最新)。

  • P(Partition Tolerance,分区容错性):即使网络分区(节点间通信中断),系统仍能运行。

📌 必须二选一:CP 还是 AP?

选择 特点 适用场景
CP 强一致,但可能牺牲可用性(如数据同步时拒绝请求) 银行转账、库存扣减(数据必须准确)
AP 高可用,但可能返回旧数据(如缓存和数据库短暂不一致) 社交网络、新闻推送(允许短暂不一致)

🌰 例子

  • CP系统(如ZooKeeper)

    • 如果主节点挂了,选举新主期间系统不可写(保证数据一致)。

  • AP系统(如Cassandra)

    • 即使部分节点宕机,仍能读写,但可能读到旧数据。


2. BASE理论:对CAP的妥协方案

BASE理论是CAP的延伸,强调最终一致性(Eventually Consistent),适用于AP系统:

  • BA(Basically Available,基本可用):系统即使故障,也能提供降级服务(如返回缓存数据)。

  • S(Soft State,软状态):允许系统不同节点间存在中间状态(如订单“支付中”)。

  • E(Eventually Consistent,最终一致):经过一段时间后,所有节点数据会一致。

📌 BASE vs ACID

特性 ACID(传统数据库) BASE(分布式系统)
一致性 强一致(实时) 最终一致(延迟同步)
可用性 低(事务锁导致阻塞) 高(允许降级)
适用场景 银行交易 电商库存、社交动态

🌰 例子

  • 电商库存

    • 用户下单 → 先扣缓存库存(基本可用)→ 异步同步数据库(最终一致)。

    • 可能超卖,但通过补货或订单取消补偿。

  • 朋友圈点赞

    • 你的手机显示点赞成功,朋友可能稍后才看到(软状态)。


📌 一句话总结

  • CP:像“严谨的会计”,数据必须100%准确,宁可暂停服务也要对账。

  • AP:像“灵活的快递”,先保证能送货,偶尔送错再补发。

  • BASE:AP系统的实践原则——先扛住流量,事后慢慢纠错

💡 记住

  • 钱相关的选CP(如支付系统)。

  • 用户体验优先的选AP(如微博热搜)。

  • 大部分互联网系统都是AP + BASE

分布式事务方案对比:Saga vs TCC

1. Saga 模式(最终一致性)

📌 核心思想: 把一个长事务拆分成多个本地事务,每个步骤完成后提交,如果某一步失败,则触发逆向补偿操作(回滚之前的步骤)。

🌰 例子(电商下单)

  1. 订单服务:创建订单(成功)

  2. 库存服务:扣减库存(成功)

  3. 支付服务:扣款(失败)→ 触发补偿

    • 支付服务失败后,Saga 会依次调用:

      • 库存服务补偿:增加库存

      • 订单服务补偿:取消订单

✅ 优点

  • 适合长事务(如跨多个服务的业务流程)。

  • 不需要全局锁,性能较好。

❌ 缺点

  • 补偿逻辑复杂(每个正向操作都要写对应的补偿逻辑)。

  • 不保证隔离性(可能出现“脏读”,比如库存扣减后,支付未完成时,其他事务看到库存已扣)。

📌 适用场景

  • 订单、物流等最终一致性允许的业务。

  • 不适合强一致性要求高的场景(如金融转账)。


2. TCC 模式(Try-Confirm-Cancel)

📌 核心思想: 每个事务分为三个阶段:

  1. Try:预留资源(如冻结库存、预扣款)。

  2. Confirm:确认提交(正式扣减)。

  3. Cancel:取消预留(释放资源)。

🌰 例子(电商下单)

  1. Try 阶段

    • 订单服务:生成订单(状态=“待支付”)

    • 库存服务:冻结库存(不是真正扣减)

    • 支付服务:预扣款(用户账户资金冻结)

  2. Confirm 阶段(全部Try成功)

    • 订单服务:更新订单状态=“已支付”

    • 库存服务:真正扣减库存

    • 支付服务:正式扣款

  3. Cancel 阶段(任一Try失败)

    • 订单服务:删除订单

    • 库存服务:解冻库存

    • 支付服务:解冻资金

✅ 优点

  • 强一致性(Try阶段锁定资源,避免脏读)。

  • 性能较好(相比2PC,减少阻塞时间)。

❌ 缺点

  • 代码侵入性强(每个服务都要实现Try/Confirm/Cancel接口)。

  • 业务逻辑复杂(需要处理幂等性、空回滚等问题)。

📌 适用场景

  • 金融支付、库存管理等强一致性要求高的业务。

  • 不适合简单业务(过度设计)。


📊 Saga vs TCC 对比

维度 Saga TCC
一致性 最终一致(可能读到中间状态) 强一致(Try阶段锁定资源)
性能 较高(无锁) 中等(Try阶段需预留资源)
复杂度 补偿逻辑复杂 Try/Confirm/Cancel 都要实现
适用场景 订单、物流等长事务 支付、库存等强一致性业务

🌰 实际案例选择

  1. 电商下单(Saga)

    • 创建订单 → 扣库存 → 支付 → 物流

    • 如果支付失败,补偿:释放库存 + 取消订单。

  2. 银行转账(TCC)

    • Try:A账户冻结100元,B账户预增100元。

    • Confirm:A扣100元,B加100元。

    • Cancel:A解冻100元,B取消预增。


📌 总结

  • 选 Saga:业务允许最终一致,且不想写太多补偿代码(如电商、物流)。

  • 选 TCC:要求强一致,能接受更高复杂度(如金融、库存)。

💡 一句话

  • Saga 像“先上车后补票”,出问题再退票。

  • TCC 像“先验资再交易”,资金冻结后才操作。

分布式锁 vs 唯一Token+Redis校验(通俗对比)

1. 分布式锁(Redis SETNX)

📌 核心思想: 用 Redis 的 SETNX(SET if Not eXists)命令抢锁,确保同一时间只有一个客户端能执行关键操作(如秒杀扣库存)。

🌰 例子(秒杀场景)

# 加锁(key=商品ID,value=随机值,expire=10秒避免死锁)
lock_key = "seckill:product_123"
lock_value = str(uuid.uuid4())
if redis.setnx(lock_key, lock_value, ex=10):
    try:
        # 执行业务:扣减库存
        deduct_stock()
    finally:
        # 释放锁(校验value,避免误删其他客户端的锁)
        if redis.get(lock_key) == lock_value:
            redis.delete(lock_key)
else:
    raise Exception("抢购失败,请重试!")

✅ 优点

  • 强互斥:绝对保证同一时间只有一个请求能操作资源。

  • 简单直接:适合短时高频竞争场景(如秒杀)。

❌ 缺点

  • 死锁风险:如果业务执行超时,锁自动过期前其他请求会被阻塞。

  • 锁续期问题:复杂场景需额外实现看门狗(如Redisson)。

📌 适用场景

  • 严格互斥的操作(如库存扣减、唯一订单号生成)。


2. 唯一Token+Redis校验

📌 核心思想: 预生成唯一Token(如UUID)存入Redis,操作时校验Token是否有效,避免重复提交。

🌰 例子(防止订单重复提交)

# 1. 用户进入页面时,生成Token并存入Redis(有效期5分钟)
token = str(uuid.uuid4())
redis.setex(f"order_token:{user_id}", 300, token)
​
# 2. 用户提交订单时,校验Token
def submit_order(user_id, input_token):
    saved_token = redis.get(f"order_token:{user_id}")
    if saved_token != input_token:
        raise Exception("无效请求!")
    # 执行业务:创建订单
    create_order()
    # 删除Token(确保一次性使用)
    redis.delete(f"order_token:{user_id}")

✅ 优点

  • 无阻塞:Token校验是轻量级操作,不会阻塞其他请求。

  • 防重放:Token用后即废,防止重复提交。

❌ 缺点

  • 不保证强互斥:如果两个请求同时拿到同一个Token,仍可能并发执行(需结合数据库唯一约束)。

  • 依赖存储:Redis宕机会影响功能。

📌 适用场景

  • 防表单重复提交、幂等性控制(如支付回调)。


📊 对比总结

维度 分布式锁(SETNX) 唯一Token+Redis校验
核心目标 强互斥(避免并发执行) 防重复提交(保证幂等性)
性能 较低(抢锁竞争可能阻塞) 较高(无竞争)
复杂度 需处理锁续期、死锁 实现简单
适用场景 秒杀、库存扣减 表单提交、支付回调

🌰 如何选择?

  1. 选分布式锁

    • 当操作必须绝对串行执行(如“库存从100扣到99”不能错乱)。

    • 例如:秒杀、抢优惠券。

  2. 选Token校验

    • 当操作只需避免重复提交(如“同一订单不能支付两次”)。

    • 例如:订单提交、短信防刷。

💡 一句话

  • 分布式锁是“独木桥”,一次只过一人。

  • Token是“一次性门票”,用完即作废。

Spring Cloud Config vs Nacos 配置中心对比(含动态刷新原理)


1. Spring Cloud Config(官方方案)

📌 核心特点

  • 存储:配置文件放在Git/SVN等版本库(如GitHub、Gitee)。

  • 架构

    • Config Server:从Git拉取配置。

    • Config Client:应用从Server读取配置。

🔄 动态刷新原理(需配合Spring Cloud Bus)

  1. 手动触发:调用 /actuator/refresh 接口(仅刷新单个服务)。

  2. 批量刷新(通过Spring Cloud Bus + RabbitMQ/Kafka):

    • 步骤1:修改Git配置后,调用 /actuator/bus-refresh

    • 步骤2:Bus通过消息队列通知所有服务,触发配置更新。

🌰 示例流程

✅ 优点

  • 与Git集成,适合配置版本管理。

  • 结合Bus可实现批量刷新。

❌ 缺点

  • 依赖Git:必须提交代码才能生效,不适合频繁变更。

  • 功能单一:无配置管理UI,需自行搭建。


2. Nacos(阿里开源)

📌 核心特点

  • 存储:配置存在Nacos服务端(内置数据库或MySQL)。

  • 架构

    • Nacos Server:独立的配置中心服务。

    • Nacos Client:应用直接连接Nacos读取配置。

🔄 动态刷新原理(长轮询)

  1. 客户端:启动时从Nacos拉取配置,并注册监听器。

  2. 服务端:配置变更时,立即通知监听该配置的客户端(基于长轮询,非MQ)。

  3. 客户端:收到通知后主动拉取新配置,触发Spring的 @RefreshScope 生效。

🌰 示例流程

✅ 优点

  • 开箱即用:自带UI,支持配置的增删改查。

  • 实时性强:长轮询机制,秒级生效。

  • 功能丰富:支持配置版本、灰度发布、权限管理。

❌ 缺点

  • 需额外部署:多一个Nacos Server组件。


📊 对比总结

维度 Spring Cloud Config Nacos
配置存储 Git/SVN Nacos内置存储(支持MySQL)
动态刷新 依赖Spring Cloud Bus + MQ 原生支持长轮询(无额外依赖)
易用性 需手动集成Git和Bus 自带UI,一键生效
适用场景 需严格版本管理的传统企业 互联网项目,追求实时性和便捷性

🌰 如何选择?

  1. 选Spring Cloud Config

    • 公司已有Git流程,配置需严格审计(如金融行业)。

    • 缺点:刷新麻烦,建议搭配Nacos替代。

  2. 选Nacos

    • 需要实时生效、简化运维(如微服务动态调整日志级别)。

    • 95%的Spring Cloud项目选Nacos

💡 一句话

  • Config像“手动挡汽车”,功能原始但可控。

  • Nacos像“自动挡汽车”,开箱即用,省心省力。

附:Nacos动态刷新代码示例

@RestController
@RefreshScope // 动态刷新注解
public class DemoController {
    @Value("${user.name}") 
    private String userName; // 配置变更后自动更新
}

SkyWalking 原理(探针字节码增强 + TraceID传递)


1. 核心原理:探针(Agent)如何工作?

📌 比喻: SkyWalking 的探针(Agent)像是一个隐形摄像头,在Java应用运行时偷偷记录方法调用、SQL请求、HTTP调用等信息,但不用改代码

🔧 实现技术:字节码增强(Bytecode Enhancement)

  1. 启动时注入

    • 通过Java Agent机制(-javaagent:/skywalking-agent.jar),在JVM启动时加载探针。

  2. 运行时修改类

    • 探针动态修改.class文件(如HttpClient.execute()方法),插入监控逻辑:

      // 原始方法
      public void execute() {
          // 业务逻辑
      }
      ​
      // 探针增强后的方法
      public void execute() {
          Span span = Tracing.startSpan("HTTP调用"); // 插入监控代码
          try {
              // 业务逻辑
          } finally {
              span.finish();
          }
      }
  3. 无侵入性:开发者无需修改源码,只需加一个启动参数。


2. TraceID 跨服务传递原理

📌 目标:在A服务调用B服务时,保持同一个请求的全局唯一TraceID,方便追踪完整链路。

🌰 示例流程

  1. 用户请求进入A服务

    • SkyWalking 生成唯一 TraceID: abc123,并记录到HTTP请求头:

      GET /order HTTP/1.1
      Headers:
          sw8: abc123  # SkyWalking的TraceID协议头
  2. A服务调用B服务(通过Feign/RestTemplate)

    • 探针自动将 sw8 头添加到HTTP请求中,传给B服务。

  3. B服务接收请求

    • 探针解析 sw8 头,继续使用同一个 TraceID: abc123,形成完整链路。

📌 关键点

  • 协议头名称:SkyWalking 默认使用 sw8 头(可自定义)。

  • 支持组件:HTTP/RPC调用(如Dubbo、gRPC)、MQ(如Kafka)、数据库(如MySQL)等。


🛠️ 技术细节(可选)

1. 字节码增强实现方式
  • 工具库:使用 Byte Buddy 或 ASM 动态修改字节码。

  • 增强点

    • HTTP 客户端(如HttpClient、OkHttp)

    • 数据库驱动(如JDBC)

    • RPC框架(如Dubbo、Feign)

2. TraceID 传递协议(SW8)
sw8: 1-abc123-456-789-0-0-0-0-1
  • 1:版本号

  • abc123:TraceID

  • 456:父SpanID

  • 789:当前SpanID

  • 0-0-0-0-1:其他上下文(如服务名、实例名)


🌰 实际案例

  1. 用户下单请求链路

    前端 → 网关 → 订单服务 → 库存服务 → 支付服务
    • 所有服务共享同一个 TraceID,SkyWalking 界面展示完整调用树。

  2. 异常定位

    • 发现支付服务超时 → 通过 TraceID 快速找到关联的订单和库存调用日志。


📌 总结

  1. 探针原理:通过字节码增强无侵入监控,像“X光机”透视应用内部。

  2. TraceID传递:通过HTTP头(如sw8)跨服务透传,串联完整链路。

  3. 优势

    • 无需改代码:加一个JVM参数即可接入。

    • 全栈支持:HTTP/RPC/DB/MQ全覆盖。

💡 一句话:SkyWalking 像“分布式系统的GPS”,用 TraceID 记录请求的完整路径!

ELK栈日志收集与分析(Filebeat + Kibana 极简版)


1. 核心角色分工
组件 作用 比喻
Filebeat 轻量级日志收集器,负责“搬运”日志文件 像快递员,挨家挨户收快递(日志)
Logstash 日志加工(过滤、格式化),可选组件 像分拣员,拆包裹并贴标签
Elasticsearch 存储和检索日志数据 像仓库,存所有快递记录
Kibana 可视化分析日志(图表、仪表盘) 像仓库管理员,用电脑查库存

💡 简化版ELK:可以跳过Logstash,让Filebeat直连ES(适合基础场景)。


2. Filebeat 如何收集日志?

📌 工作原理

  1. 监控日志文件

    • Filebeat 监听指定路径的日志文件(如 /var/log/app.log)。

    • 发现新日志行 → 立即读取并发送。

  2. 发送到Elasticsearch/Kibana

    • 通过YAML配置输出目标(无需写代码):

      filebeat.inputs:
        - type: filestream
          paths: ["/var/log/app/*.log"]  # 监控哪些文件
      ​
      output.elasticsearch:
        hosts: ["http://elasticsearch:9200"]  # 直接发给ES
        indices:
          - index: "app-logs-%{+yyyy.MM.dd}"  # 按日期存索引

🌰 示例场景

  • 你的Java应用生成日志到 app.log,Filebeat 会:

    1. 实时读取新日志。

    2. 将日志按日期存储到ES(如 app-logs-2023.10.01)。


3. Kibana 如何分析日志?

📌 三步搞定可视化

  1. 创建索引模式

    • 进入Kibana → Management → Stack Management → Index Patterns → 输入 app-logs-*

  2. 搜索日志

    • 进入 Discover,选择索引模式,输入关键词(如 error)过滤日志。

  3. 制作仪表盘

    • 进入 Dashboard → Create new,添加图表(如:

      • 折线图:错误日志随时间变化趋势。

      • 饼图:不同错误类型的占比。

🌰 示例仪表盘效果

[HTTP请求统计]
│
├── 请求量趋势图(折线图)
├── 状态码分布(饼图:200/404/500)
└── 慢请求TOP10(表格)

🔧 对比传统日志分析

方式 传统vi/grep ELK栈
日志存储 分散在服务器本地 集中存储,支持全文检索
检索速度 慢(需逐台登录) 快(秒级搜索所有历史日志)
可视化 自动生成图表、告警
适合场景 临时调试 长期监控、故障定位

📌 一句话总结

  1. Filebeat:像“日志搬运工”,实时监控文件并发送到ES。

  2. Kibana:像“日志透视镜”,用图表快速定位问题(如错误突增、慢请求)。

💡 小白也能懂

  • 以前查日志像“翻纸质账本”,用ELK后变成“Ctrl+F搜电子表格+自动生成报表”!

Docker多阶段构建、K8s滚动更新、Service Mesh(Istio)一句话秒懂版


1. Dockerfile多阶段构建

📌 核心思想: 像“流水线生产”,分阶段构建镜像,最终只保留需要的文件,缩小镜像体积

🌰 例子(构建Go应用)

# 阶段1:编译环境(装GCC等工具,体积大)
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
​
# 阶段2:运行环境(只留二进制文件,体积小)
FROM alpine:latest  
WORKDIR /root/
COPY --from=builder /app/myapp .  # 从builder阶段拷贝成品
CMD ["./myapp"]

✅ 效果

  • 编译阶段镜像:1GB

  • 最终镜像:仅10MB(Alpine基础镜像+二进制文件)

💡 适用场景:需要编译的语言(Go/Java/C++),避免镜像携带冗余工具。


2. Kubernetes滚动更新(Rolling Update)

📌 核心思想: 像“排队换轮胎”,逐步替换旧Pod,确保服务不中断。

🌰 流程

  1. 旧版本:v1(3个Pod)

  2. 新版本:v2

    • K8s先启动1个v2 Pod → 健康检查通过后 → 杀掉1个v1 Pod

    • 重复直到所有Pod替换为v2

配置示例

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 最多比预期多1个Pod(允许临时4个)
      maxUnavailable: 0  # 保证至少3个Pod可用

✅ 优势:零停机更新,失败可回滚。


3. Service Mesh(Istio)的作用

📌 核心思想: 像“交通指挥中心”,接管微服务间的网络通信,无需改代码即可实现:

  1. 流量管理

    • 灰度发布(20%流量走新版本)

    • 故障注入(模拟超时测试容错)

  2. 安全

    • 自动mTLS加密(服务间通讯防窃听)

  3. 监控

    • 自动生成服务拓扑图、链路追踪

🌰 典型功能

# 将80%流量给v1,20%给v2
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - myapp
  http:
    - route:
        - destination:
            host: myapp
            subset: v1
          weight: 80
        - destination:
            host: myapp
            subset: v2
          weight: 20

✅ 优势

  • 业务代码无需关心重试、熔断、监控等非功能性需求。


📊 三技术对比

技术 解决什么问题 类比
Docker多阶段构建 镜像体积过大 快递只送手机,不送包装盒和工厂工具
K8s滚动更新 无缝升级服务 逐节替换火车车厢,乘客无需下车
Istio 微服务通信治理 交通信号灯+GPS+交警统一指挥

🌰 实际应用场景

  1. 开发流程

    • 用多阶段构建生成极小镜像 → 推送到K8s集群 → Istio管理灰度发布。

  2. 故障排查

    • 通过Istio监控发现A服务调用B服务超时 → 调整熔断规则 → K8s自动回滚版本。

💡 一句话总结

  • Docker多阶段:瘦身镜像。

  • K8s滚动更新:无感升级。

  • Istio:给微服务加“智能导航”。

秒杀系统设计(Redis + 消息队列 + 限流)极简版


1. 核心挑战
  • 瞬时超高并发:1万用户抢100件商品,数据库会被压垮。

  • 超卖问题:库存扣减到负数(多人同时读到库存=1,都下单成功)。

  • 公平性:防止机器人刷单。


2. 四层防御设计

3. 关键技术实现
① 限流层(拒绝大部分请求)
  • 目标:只放行10%的请求到后端。

  • 方法

    # Nginx限流(每秒1000请求)
    limit_req_zone $binary_remote_addr zone=seckill:10m rate=1000r/s;
    • 用户点击秒杀按钮时,前端先弹验证码(防机器人)。

② Redis缓存库存(解决超卖)
  • 步骤

    1. 预热库存:秒杀开始前,将库存(如100件)存入Redis。

      SET stock:product_123 100
    2. 原子扣减:用Lua脚本保证原子性。

      -- KEYS[1]=库存key, ARGV[1]=扣减数量
      local stock = tonumber(redis.call('GET', KEYS[1]))
      if stock >= tonumber(ARGV[1]) then
          return redis.call('DECRBY', KEYS[1], ARGV[1])
      else
          return -1  -- 库存不足
      end
③ 消息队列削峰(异步下单)
  • 流程

    1. Redis扣减成功后,发送消息到RabbitMQ/Kafka:

      message = {"user_id": 123, "product_id": 456}
      mq.send("seckill_order", message)
    2. 消费者服务慢慢处理消息,创建真实订单。

④ 最终一致性
  • 补偿机制

    • 如果支付超时未完成,Redis库存自动回滚。

    • 定期对账Redis和数据库库存。


4. 为什么能抗住高并发?
环节 传统方案 秒杀方案 效果
库存检查 直接查数据库 Redis内存操作 速度快100倍
订单创建 同步插入MySQL 消息队列异步处理 数据库压力降低90%
请求过滤 全部放行 限流+验证码 拦截90%无效请求

5. 实际案例(天猫秒杀)
  1. 前端:倒计时结束前按钮置灰,结束瞬间弹验证码。

  2. 中间层:Redis扣库存,成功则返回“排队中”页面。

  3. 后端:消息队列处理订单,10分钟内支付有效。


📌 一句话总结

  • Redis:快速扣库存(内存操作+原子性)。

  • 消息队列:异步化订单(削峰填谷)。

  • 限流:保护系统(像地铁限流栏杆)。

💡 切记

  • 秒杀≠高性能,而是少干活的艺术(让90%请求提前失败)。

  • 真实场景还需:CDN静态化、库存预热、热点数据隔离。

如何定位服务雪崩?如何模拟混沌工程测试?


一、服务雪崩定位方法

📌 雪崩现象:A服务故障 → B服务因依赖A而阻塞 → 连锁反应导致整个系统崩溃。

🔍 定位步骤

  1. 查看监控告警

    • 核心指标

      • 请求量突降(服务不可用)

      • 响应时间飙升(如从50ms → 5000ms)

      • 错误率暴涨(如5xx错误超50%)

    • 工具:Prometheus + Grafana、SkyWalking、ELK。

  2. 分析调用链

    • 通过链路追踪(如SkyWalking)找到最早失败的服务

      用户请求 → 网关 → 服务A(超时) → 服务B(堆积) → 数据库(崩溃)
    • 关键点:检查服务A的依赖(数据库、Redis、外部API)。

  3. 检查资源瓶颈

    • CPU/内存topjstat(Java应用)。

    • 线程池:是否满负荷(如Tomcat线程池耗尽)。

    • 数据库:连接数、慢查询(SHOW PROCESSLIST)。

  4. 日志排查

    • 错误日志grep "ERROR|Timeout" /var/log/serviceA.log

    • 熔断日志:如Hystrix/Sentinel的熔断记录。


二、模拟混沌工程测试(以Chaos Blade为例)

📌 混沌工程核心思想:主动注入故障,验证系统容错能力。

🔧 Chaos Blade 常用场景

故障类型 命令示例 模拟场景
CPU满载 blade create cpu load --cpu-percent 80 测试服务高CPU下的降级逻辑
网络延迟 blade create network delay --time 3000 --interface eth0 模拟跨机房网络延迟
进程杀死 blade create process kill --process tomcat 测试节点宕机自动恢复
Dubbo接口熔断 blade create dubbo throwCustomException --service com.XXXService --method getStock 验证熔断策略是否生效

🚀 实施步骤

  1. 安装Chaos Blade

    # Linux一键安装
    curl -sSL https://chaosblade.oss-cn-hangzhou.aliyuncs.com/agent/github/1.7.1/chaosblade-1.7.1-linux-amd64.tar.gz | tar -xz
  2. 注入故障

    # 模拟订单服务500错误率30%
    blade create servlet method --method doPost --rest --requestpath /order/create --exception java.lang.RuntimeException --error-rate 0.3
  3. 观察系统表现

    • 监控告警是否触发?

    • 熔断降级是否生效?

    • 日志是否有错误处理记录?

  4. 生成测试报告

    blade destroy UID  # 停止实验
    blade status UID   # 查看实验详情

三、预防雪崩的关键设计
  1. 熔断降级

    • 配置Sentinel规则(QPS > 1000时熔断)。

  2. 限流保护

    • Nginx层限流(limit_req)。

  3. 异步解耦

    • 用消息队列(如RocketMQ)削峰。

  4. 资源隔离

    • 线程池隔离(不同服务用不同线程池)。


📌 一句话总结

  • 定位雪崩:监控 → 链路 → 资源 → 日志,找到“多米诺骨牌第一张”。

  • 混沌测试:用Chaos Blade模拟故障,像“消防演习”一样验证系统韧性。

  • 核心原则假设故障一定会发生,提前设计防御

💡 真实案例

  • 某电商在大促前,通过Chaos Blade随机杀死30%的Pod,验证K8s自愈能力和服务降级是否生效。


💡 面试准备建议

  • 技术深度:针对注册中心、负载均衡、分布式事务等核心点,准备1-2个线上问题解决案例(如Nacos集群故障恢复)。

  • 架构思维:练习画系统架构图(如电商微服务调用链路),标注关键技术选型依据。

  • 延伸学习:关注Service Mesh、Serverless等新趋势,对比Spring Cloud生态差异10。


网站公告

今日签到

点亮在社区的每一天
去签到