架构设计 - 架构师面试题库

覆盖系统设计、DDD、微服务治理、高可用方案、分布式理论,考察候选人的架构思维、权衡能力和实战经验。


一、分布式理论基础(1-15题)

1. 🔵 CAP定理是什么?在实际系统中如何应用?

答:CAP定理指出分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)三者。

核心理解:

  • C(一致性):所有节点在同一时刻看到相同的数据
  • A(可用性):每个请求都能收到非错误的响应(不保证是最新数据)
  • P(分区容错):网络分区发生时系统仍能继续运行

关键认知:

  • P是必须的(网络分区不可避免),所以实际选择是CP或AP
  • CAP不是非黑即白,而是一个连续的光谱
  • 同一个系统的不同模块可以做不同的选择

实际应用:

  • CP系统:ZooKeeper、etcd、HBase。牺牲可用性保证一致性
  • AP系统:Cassandra、DynamoDB、DNS。牺牲一致性保证可用性
  • 大多数业务系统:在正常情况下保证CA,分区时根据业务选择C或A

2. 🔴 BASE理论是什么?与ACID有什么关系?

答:BASE是对CAP中AP方向的延伸,是大规模分布式系统的实践指导。

BASE:

  • BA(Basically Available):基本可用。允许响应时间增加或功能降级
  • S(Soft State):软状态。允许系统中的数据存在中间状态
  • E(Eventually Consistent):最终一致性。经过一段时间后数据达到一致

ACID vs BASE:

维度 ACID BASE
适用场景 单机/强一致 分布式/高可用
一致性 强一致性 最终一致性
可用性 可能牺牲 优先保证
代表 传统关系数据库 NoSQL、微服务

最终一致性的实现方式:

  1. 读时修复:读取时发现不一致则修复(Cassandra的Read Repair)
  2. 写时修复:写入时检测冲突并修复
  3. 异步修复:后台定时任务对比修复(Anti-Entropy)
  4. 版本向量:通过版本号检测和解决冲突

3. 🔴 分布式事务有哪些解决方案?各自的优缺点?

答:分布式事务是分布式系统中最复杂的问题之一。

方案对比:

  1. 2PC(两阶段提交)

    • 协调者发起prepare → 参与者响应 → 协调者发起commit/rollback
    • 优点:强一致性
    • 缺点:同步阻塞、单点故障(协调者)、数据不一致风险(网络分区时)
  2. 3PC(三阶段提交)

    • 在2PC基础上增加CanCommit阶段和超时机制
    • 减少阻塞,但仍不能完全解决一致性问题
    • 实际很少使用
  3. TCC(Try-Confirm-Cancel)

    • Try:预留资源。Confirm:确认执行。Cancel:取消释放
    • 优点:性能好(无全局锁)
    • 缺点:业务侵入大,需要实现三个接口,幂等性要求高
  4. SAGA

    • 将长事务拆分为多个本地事务,每个事务有对应的补偿操作
    • 正向执行失败时,逆序执行补偿操作
    • 优点:无锁,性能好
    • 缺点:最终一致性,补偿逻辑复杂
  5. 本地消息表

    • 业务操作和消息写入在同一个本地事务中
    • 定时任务扫描消息表发送消息
    • 消费者处理消息并确认
    • 优点:简单可靠
    • 缺点:有延迟,需要定时任务
  6. 事务消息(RocketMQ)

    • 发送半消息 → 执行本地事务 → 提交/回滚消息
    • 优点:与消息队列集成,可靠性高
    • 缺点:依赖特定MQ

4. 🔴 Raft和Paxos共识算法的核心区别是什么?

答:两者都是解决分布式一致性的共识算法。

Paxos:

  • Lamport提出,理论上最优
  • 角色:Proposer、Acceptor、Learner
  • 允许多个Proposer同时提议(可能活锁)
  • 理论完备但实现复杂,工程化困难
  • 变体:Multi-Paxos、Fast Paxos、Egalitarian Paxos

Raft:

  • 为可理解性设计,等价于Multi-Paxos
  • 角色:Leader、Follower、Candidate
  • 强Leader模型:所有写入通过Leader
  • 三个子问题:Leader选举、日志复制、安全性
  • 实现相对简单,工程化友好
  • 代表:etcd、TiKV、CockroachDB

核心区别:

维度 Paxos Raft
Leader 可选(Multi-Paxos有) 必须有
日志 允许空洞 连续的
可理解性
工程实现 复杂 相对简单
性能 理论上更优 实际差异不大

5. 🔵 什么是幂等性?如何在分布式系统中保证幂等?

答:幂等性是指同一操作执行多次与执行一次的效果相同。

为什么需要幂等:

  • 网络超时重试
  • 消息队列重复消费
  • 用户重复点击
  • 服务调用重试

实现方案:

  1. 唯一ID + 去重表:每个请求携带唯一ID,处理前检查是否已处理
  2. 数据库唯一约束:利用UNIQUE KEY防止重复插入
  3. 乐观锁(版本号)UPDATE SET version=version+1 WHERE version=old_version
  4. 状态机:只允许合法的状态转换(如订单:待支付→已支付,不能重复支付)
  5. Token机制:服务端生成Token,客户端携带Token请求,Token只能使用一次
  6. Redis SETNX:利用Redis的原子操作实现分布式去重

设计原则:

  • 查询操作天然幂等
  • 删除操作天然幂等(删除不存在的资源不报错)
  • 创建操作需要去重
  • 更新操作需要版本控制

6. 🔴 一致性Hash算法的原理是什么?如何解决数据倾斜?

答:一致性Hash是分布式系统中数据分片的核心算法。

原理:

  1. 将哈希值空间组织成一个环(0到2^32-1)
  2. 将节点映射到环上(对节点IP/名称取哈希)
  3. 数据的key映射到环上,顺时针找到的第一个节点就是负责节点
  4. 节点增减时,只影响相邻节点的数据,迁移量最小

数据倾斜问题:

  • 节点少时,哈希环上的节点分布不均匀
  • 某些节点承担更多数据

解决方案——虚拟节点:

  • 每个物理节点映射多个虚拟节点到环上(如每个节点150-200个虚拟节点)
  • 虚拟节点越多,数据分布越均匀
  • 虚拟节点到物理节点有映射关系

应用场景:

  • 分布式缓存(Memcached、Redis Cluster的slot是类似思想)
  • 负载均衡(Nginx的一致性Hash)
  • 分布式存储(Cassandra的Token Ring)

7. 🔵 什么是服务雪崩?如何预防和处理?

答:服务雪崩是指一个服务的故障导致整个调用链路的级联失败。

雪崩过程:

  1. 服务A依赖服务B
  2. 服务B响应变慢或不可用
  3. 服务A的线程池被占满(等待B的响应)
  4. 服务A也变得不可用
  5. 依赖服务A的服务C也受影响
  6. 级联扩散,整个系统崩溃

预防手段:

  1. 超时控制:所有远程调用设置合理的超时时间
  2. 熔断器(Circuit Breaker):错误率超过阈值时自动熔断,快速失败
  3. 限流:控制请求速率,防止过载
  4. 隔离:线程池隔离、信号量隔离,防止故障扩散
  5. 降级:非核心功能降级,保证核心链路可用
  6. 重试策略:指数退避重试,避免重试风暴

熔断器状态机:

  • Closed(关闭):正常状态,请求正常通过
  • Open(打开):错误率超过阈值,所有请求快速失败
  • Half-Open(半开):经过一段时间后,允许少量请求通过测试

8. 🔴 限流算法有哪些?各自的特点和适用场景?

答:限流是保护系统的第一道防线。

主流算法:

  1. 固定窗口计数器

    • 固定时间窗口内计数,超过阈值拒绝
    • 问题:窗口边界的突发流量(两个窗口交界处可能有2倍流量)
  2. 滑动窗口计数器

    • 将窗口细分为多个小窗口,滑动统计
    • 解决了固定窗口的边界问题
    • 实现:Redis的ZSET(按时间戳排序)
  3. 漏桶算法(Leaky Bucket)

    • 请求进入桶中,以固定速率流出
    • 优点:输出速率恒定,平滑流量
    • 缺点:无法应对突发流量
  4. 令牌桶算法(Token Bucket)

    • 以固定速率向桶中放入令牌,请求需要获取令牌
    • 桶满时令牌溢出(不累积超过桶容量)
    • 优点:允许一定程度的突发流量(桶中有积累的令牌)
    • Guava的RateLimiter就是令牌桶实现
  5. 滑动日志

    • 记录每个请求的时间戳
    • 统计窗口内的请求数
    • 最精确但内存开销大

生产选择:

  • API网关限流:令牌桶(允许突发)
  • 消息消费限流:漏桶(平滑处理)
  • 分布式限流:Redis + Lua脚本实现滑动窗口

9. 🔵 什么是服务降级?降级策略有哪些?

答:服务降级是在系统压力过大时,暂时关闭非核心功能以保证核心功能可用。

降级策略:

  1. 功能降级:关闭非核心功能(如关闭推荐、评论、积分等)
  2. 数据降级:返回缓存数据或默认数据(如商品详情返回缓存版本)
  3. 体验降级:降低服务质量(如图片降低分辨率、搜索结果减少)
  4. 写降级:将同步写改为异步写(如日志、统计数据异步处理)
  5. 读降级:读请求走缓存,不查数据库

降级触发方式:

  • 自动降级:基于监控指标自动触发(CPU>80%、响应时间>2s)
  • 手动降级:运维人员通过开关手动触发
  • 熔断降级:熔断器打开时自动降级

降级开关设计:

  • 使用配置中心(Nacos、Apollo)管理降级开关
  • 支持动态生效,不需要重启
  • 按功能模块粒度控制
  • 有完善的监控和告警

10. 🔴 分布式锁有哪些实现方案?各自的优缺点?

答:分布式锁是分布式系统中协调并发访问的基础设施。

方案对比:

  1. Redis分布式锁

    • 实现:SET key value NX EX timeout
    • 释放:Lua脚本保证原子性(检查value再删除)
    • 优点:性能高,实现简单
    • 缺点:Redis主从切换可能丢锁
    • Redlock:多个Redis实例投票,但有争议(Martin Kleppmann的批评)
  2. ZooKeeper分布式锁

    • 实现:创建临时顺序节点,最小序号获得锁
    • 优点:可靠性高,支持可重入、公平锁
    • 缺点:性能较低(需要创建/删除节点)
  3. etcd分布式锁

    • 实现:基于Lease和Revision
    • 优点:强一致性(Raft协议),性能优于ZK
    • 缺点:生态不如ZK成熟
  4. 数据库分布式锁

    • 实现:INSERT唯一键或SELECT ... FOR UPDATE
    • 优点:不需要额外组件
    • 缺点:性能差,可能死锁

生产建议:

  • 对性能要求高、允许极端情况下锁失效:Redis
  • 对可靠性要求高:ZooKeeper或etcd
  • PostgreSQL场景:Advisory Lock(简单可靠)

11. 🔴 什么是事件驱动架构(EDA)?与请求驱动架构有什么区别?

答:事件驱动架构是现代微服务架构的重要模式。

核心概念:

  • 事件(Event):系统中发生的事实(如”订单已创建”、”支付已完成”)
  • 事件生产者:产生事件的服务
  • 事件消费者:订阅并处理事件的服务
  • 事件通道:传递事件的中间件(Kafka、RocketMQ)

与请求驱动的区别:

维度 请求驱动 事件驱动
耦合度 强耦合(调用方知道被调用方) 松耦合(生产者不知道消费者)
通信方式 同步(请求-响应) 异步(发布-订阅)
扩展性 新增消费者需要修改调用方 新增消费者只需订阅事件
可追溯性 调用链路清晰 事件流需要额外追踪
一致性 强一致性容易实现 最终一致性

事件驱动的模式:

  1. Event Notification:通知其他服务发生了什么,不携带完整数据
  2. Event-Carried State Transfer:事件携带完整数据,消费者不需要回查
  3. Event Sourcing:将所有状态变更存储为事件序列
  4. CQRS:命令和查询分离,写入产生事件,读取从物化视图查询

12. 🔴 Event Sourcing和CQRS是什么?适合什么场景?

答:这是两个经常一起使用但独立的架构模式。

Event Sourcing(事件溯源):

  • 不存储当前状态,而是存储所有状态变更事件
  • 当前状态通过重放事件序列得到
  • 类似数据库的WAL/binlog,但在应用层

优点:

  • 完整的审计日志(每个变更都有记录)
  • 可以回溯到任意时间点的状态
  • 天然支持事件驱动架构

缺点:

  • 查询当前状态需要重放事件(需要快照优化)
  • 事件schema演进复杂
  • 学习曲线陡峭

CQRS(Command Query Responsibility Segregation):

  • 将读模型和写模型分离
  • 写操作(Command)更新写模型
  • 读操作(Query)从读模型查询
  • 读写模型可以使用不同的数据库和数据结构

适用场景:

  • 读写比例悬殊(读远多于写)
  • 读写模型差异大(写入是范式化的,读取需要反范式化)
  • 需要完整审计日志的金融系统
  • 复杂业务领域(DDD中的聚合根)

不适用场景:

  • 简单的CRUD应用
  • 强一致性要求(CQRS天然是最终一致的)

13. 🔵 什么是背压(Backpressure)?在系统设计中如何应用?

答:背压是指下游处理能力不足时,向上游传递压力信号,让上游减慢发送速率。

没有背压的问题:

  • 上游生产速度 > 下游消费速度
  • 中间缓冲区不断增长,最终OOM
  • 或者大量请求超时、丢弃

背压的实现方式:

  1. 阻塞式:缓冲区满时阻塞生产者(如BlockingQueue)
  2. 丢弃式:缓冲区满时丢弃新请求或旧请求
  3. 速率控制:动态调整生产者的发送速率
  4. 响应式流:Reactive Streams规范(Publisher-Subscriber模型,Subscriber控制请求数量)

应用场景:

  • 消息队列消费:消费者拉取模式天然支持背压
  • HTTP服务:限流就是一种背压
  • 流处理:Flink的背压机制
  • 响应式编程:Project Reactor、RxJava

14. 🔴 什么是Sidecar模式?Service Mesh是如何工作的?

答:Sidecar模式是Service Mesh的基础。

Sidecar模式:

  • 在每个服务实例旁边部署一个代理进程(Sidecar)
  • 所有进出服务的流量都经过Sidecar
  • Sidecar负责服务发现、负载均衡、熔断、限流、认证、监控等
  • 业务代码不需要关心这些基础设施逻辑

Service Mesh架构:

  • 数据平面(Data Plane):由所有Sidecar代理组成,处理服务间的实际通信
  • 控制平面(Control Plane):管理和配置所有Sidecar,下发路由规则、策略等

主流实现:

  • Istio:最流行的Service Mesh,控制平面。数据平面使用Envoy
  • Linkerd:轻量级Service Mesh,Rust编写的数据平面
  • Envoy:高性能代理,通常作为Sidecar使用

优势:

  • 基础设施逻辑与业务代码解耦
  • 语言无关(任何语言的服务都能接入)
  • 统一的可观测性(指标、日志、链路追踪)
  • 统一的安全策略(mTLS、RBAC)

劣势:

  • 增加延迟(每次调用多两跳代理)
  • 资源消耗(每个Pod多一个Sidecar容器)
  • 运维复杂度增加
  • 调试困难

15. 🔵 微服务的服务发现有哪些方案?

答:服务发现是微服务架构的基础设施。

两种模式:

  1. 客户端发现:客户端从注册中心获取服务列表,自己做负载均衡

    • 代表:Eureka + Ribbon、Nacos + Dubbo
    • 优点:客户端可以实现智能路由
    • 缺点:每种语言都需要实现客户端
  2. 服务端发现:通过负载均衡器/代理转发请求

    • 代表:Kubernetes Service、Consul + Envoy
    • 优点:对客户端透明
    • 缺点:多一跳网络开销

注册中心对比:

注册中心 一致性 健康检查 特点
Eureka AP 客户端心跳 Netflix开源,已停止维护
Nacos AP/CP可切换 TCP/HTTP/MySQL 阿里开源,功能丰富
Consul CP 多种方式 HashiCorp,支持多数据中心
ZooKeeper CP 会话心跳 强一致性,不推荐做服务发现
etcd CP Lease Kubernetes的底层存储

二、DDD与领域建模(16-35题)

16. 🔵 DDD(领域驱动设计)的核心概念有哪些?

答:DDD是Eric Evans提出的软件设计方法论,核心是让代码反映业务领域。

战略设计:

  • 限界上下文(Bounded Context):明确的业务边界,每个上下文有自己的领域模型
  • 通用语言(Ubiquitous Language):团队(开发+业务)共同使用的术语
  • 上下文映射(Context Map):限界上下文之间的关系(上下游、共享内核、防腐层等)
  • 子域(Subdomain):核心域、支撑域、通用域

战术设计:

  • 实体(Entity):有唯一标识的对象(如订单、用户)
  • 值对象(Value Object):无唯一标识,通过属性值判断相等(如地址、金额)
  • 聚合(Aggregate):一组相关对象的集合,有一个聚合根
  • 聚合根(Aggregate Root):聚合的入口,外部只能通过聚合根访问聚合内的对象
  • 领域服务(Domain Service):不属于任何实体的业务逻辑
  • 领域事件(Domain Event):领域中发生的业务事件
  • 仓储(Repository):聚合的持久化接口

17. 🔴 如何划分限界上下文?有什么方法论?

答:限界上下文的划分是DDD中最关键也最困难的决策。

划分方法:

  1. 事件风暴(Event Storming)

    • 召集业务专家和开发人员
    • 用便利贴列出所有领域事件(橙色)
    • 识别触发事件的命令(蓝色)
    • 识别聚合(黄色)
    • 根据聚合的关联性划分限界上下文
  2. 业务能力分解

    • 按业务能力划分(订单管理、库存管理、支付管理)
    • 每个业务能力对应一个限界上下文
  3. 数据所有权

    • 谁拥有数据,谁就是限界上下文
    • 同一个概念在不同上下文中可能有不同含义(如”商品”在商品上下文和订单上下文中的含义不同)

划分原则:

  • 高内聚低耦合
  • 一个上下文由一个团队负责
  • 上下文之间通过明确的接口通信
  • 避免上下文过大(变成大泥球)或过小(过度拆分)

18. 🔴 聚合设计的原则是什么?如何确定聚合的边界?

答:聚合设计直接影响系统的一致性和性能。

设计原则:

  1. 聚合内强一致性:聚合内的所有修改在一个事务中完成
  2. 聚合间最终一致性:跨聚合的操作通过领域事件实现最终一致
  3. 聚合尽量小:只包含必须在同一事务中修改的对象
  4. 通过ID引用其他聚合:不直接持有其他聚合的对象引用
  5. 一次事务只修改一个聚合:避免跨聚合事务

确定边界的方法:

  • 问自己:这些对象必须在同一个事务中保持一致吗?
  • 如果是,放在同一个聚合
  • 如果不是,拆分为不同的聚合

示例——电商订单:

  • 聚合根:Order
  • 聚合内:OrderItem(订单项必须和订单一起创建/修改)
  • 聚合外:Product(商品是独立的聚合,订单通过productId引用)
  • 聚合外:User(用户是独立的聚合,订单通过userId引用)

19. 🔴 DDD中的防腐层(Anti-Corruption Layer)是什么?什么时候需要?

答:防腐层是限界上下文之间的翻译层,防止外部模型污染内部模型。

使用场景:

  1. 对接遗留系统:新系统不想被老系统的数据模型污染
  2. 对接第三方服务:第三方API的模型与内部模型不一致
  3. 上下文之间模型差异大:两个上下文对同一概念的理解不同

实现方式:

  • 在上下文边界处创建一个翻译层
  • 将外部模型转换为内部模型
  • 通常包含:Adapter(适配器)、Translator(翻译器)、Facade(门面)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
// 防腐层:将第三方支付的模型转换为内部模型
public class PaymentAntiCorruptionLayer {
private ThirdPartyPaymentClient client;

public PaymentResult pay(InternalPaymentRequest request) {
// 内部模型 → 外部模型
ThirdPartyRequest externalReq = translate(request);
ThirdPartyResponse externalResp = client.pay(externalReq);
// 外部模型 → 内部模型
return translate(externalResp);
}
}

20. 🔵 贫血模型和充血模型有什么区别?你倾向于哪种?

答:这是DDD中关于领域模型设计的核心争论。

贫血模型(Anemic Domain Model):

  • 领域对象只有getter/setter,没有业务逻辑
  • 业务逻辑在Service层
  • 本质上是面向过程的编程
  • 大多数Java项目的现状

充血模型(Rich Domain Model):

  • 领域对象包含业务逻辑和行为
  • Service层只做编排和协调
  • 真正的面向对象编程
  • DDD推荐的方式

对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 贫血模型
class Order {
private BigDecimal totalAmount;
// 只有getter/setter
}
class OrderService {
public void cancel(Order order) {
if (order.getStatus() == PAID) throw new Exception("已支付不能取消");
order.setStatus(CANCELLED);
// 退款逻辑...
}
}

// 充血模型
class Order {
private BigDecimal totalAmount;
public void cancel() {
if (this.status == PAID) throw new DomainException("已支付不能取消");
this.status = CANCELLED;
this.registerEvent(new OrderCancelledEvent(this.id));
}
}

我的观点:

  • 核心域使用充血模型(业务逻辑复杂,值得投入)
  • 支撑域和通用域可以用贫血模型(简单CRUD,不需要过度设计)
  • 关键是团队能力和项目复杂度的匹配

21. 🔴 如何将DDD落地到微服务架构中?

答:DDD与微服务是天然契合的,限界上下文是微服务拆分的最佳指导。

落地步骤:

  1. 战略设计:通过事件风暴识别限界上下文
  2. 上下文映射:确定上下文之间的关系和通信方式
  3. 微服务拆分:一个限界上下文 = 一个微服务(或一组微服务)
  4. 战术设计:在每个微服务内部使用聚合、实体、值对象等
  5. 通信设计:上下文之间通过API或事件通信

代码结构(六边形架构/洋葱架构):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
order-service/
├── domain/ # 领域层(核心)
│ ├── model/ # 实体、值对象、聚合根
│ ├── event/ # 领域事件
│ ├── service/ # 领域服务
│ └── repository/ # 仓储接口
├── application/ # 应用层(编排)
│ ├── command/ # 命令处理
│ ├── query/ # 查询处理
│ └── dto/ # 数据传输对象
├── infrastructure/ # 基础设施层(实现)
│ ├── persistence/ # 仓储实现
│ ├── messaging/ # 消息发送实现
│ └── external/ # 外部服务调用
└── interfaces/ # 接口层(入口)
├── rest/ # REST API
└── consumer/ # 消息消费者

依赖规则:外层依赖内层,内层不依赖外层。领域层是最内层,不依赖任何框架。

22. 🔵 微服务拆分的原则是什么?如何避免过度拆分?

答:微服务拆分是架构设计中最容易犯错的地方。

拆分原则:

  1. 业务边界:按限界上下文拆分,不是按技术层拆分
  2. 团队边界:一个微服务由一个团队负责(康威定律)
  3. 数据边界:每个微服务拥有自己的数据库
  4. 变更频率:变更频率不同的模块拆分开
  5. 扩展需求:需要独立扩展的模块拆分开

过度拆分的信号:

  • 大量跨服务调用(一个请求需要调用5+个服务)
  • 分布式事务频繁出现
  • 服务间循环依赖
  • 一个简单功能需要修改多个服务
  • 运维成本远大于开发成本

避免过度拆分:

  • 先单体,后拆分(Monolith First)
  • 从粗粒度开始,逐步细化
  • 拆分前问自己:这个服务能独立部署和运维吗?
  • 如果两个服务总是一起修改、一起部署,考虑合并

23. 🔴 微服务之间的通信方式有哪些?如何选择?

答:通信方式的选择直接影响系统的耦合度和性能。

同步通信:

  1. REST/HTTP:最简单,语言无关。适合简单的请求-响应
  2. gRPC:高性能,强类型(Protobuf)。适合内部服务间通信
  3. GraphQL:灵活查询,减少过度获取。适合BFF层

异步通信:

  1. 消息队列:Kafka、RocketMQ。适合事件驱动、解耦
  2. 事件总线:轻量级的发布-订阅

选择原则:

  • 需要立即响应:同步(REST/gRPC)
  • 可以延迟处理:异步(消息队列)
  • 内部高性能通信:gRPC
  • 对外API:REST
  • 事件通知:消息队列
  • 数据同步:CDC + 消息队列

24. 🔴 API网关的作用是什么?如何设计一个高性能的API网关?

答:API网关是微服务架构的统一入口。

核心功能:

  • 路由转发:将请求路由到对应的后端服务
  • 认证鉴权:统一的身份验证和权限控制
  • 限流熔断:保护后端服务
  • 协议转换:HTTP → gRPC、WebSocket等
  • 请求聚合:将多个后端请求合并为一个响应(BFF模式)
  • 日志监控:统一的访问日志和指标采集
  • 灰度发布:按规则将流量路由到不同版本

主流网关:

网关 语言 特点
Kong Lua/OpenResty 插件丰富,社区活跃
APISIX Lua/OpenResty 国产,性能优秀,动态配置
Spring Cloud Gateway Java Spring生态,响应式
Envoy C++ 高性能,Service Mesh数据平面
Nginx C 稳定可靠,配置静态

高性能设计要点:

  • 异步非阻塞IO(Netty、OpenResty)
  • 连接池复用
  • 路由规则缓存
  • 插件链的高效执行
  • 避免在网关做重计算

25. 🔴 如何设计一个高可用的系统?有哪些关键策略?

答:高可用是架构设计的核心目标。

可用性指标:

  • 99.9%(三个9):年停机8.76小时
  • 99.99%(四个9):年停机52.6分钟
  • 99.999%(五个9):年停机5.26分钟

关键策略:

  1. 冗余:消除单点故障

    • 服务多实例部署
    • 数据库主从/多副本
    • 多机房/多数据中心
  2. 故障检测:快速发现问题

    • 健康检查(HTTP/TCP)
    • 心跳检测
    • 监控告警
  3. 故障转移:自动切换到健康节点

    • 负载均衡器自动摘除故障节点
    • 数据库自动主从切换
    • DNS故障转移
  4. 容错:在故障情况下继续提供服务

    • 熔断、降级、限流
    • 重试(带退避)
    • 超时控制
  5. 可恢复:快速从故障中恢复

    • 自动重启
    • 数据备份和恢复
    • 灰度发布和快速回滚

26. ⚫ 如何设计一个秒杀系统?

答:秒杀是高并发系统设计的经典面试题。

核心挑战:

  • 瞬时高并发(百万级QPS)
  • 库存一致性(不能超卖)
  • 用户体验(快速响应)

架构设计:

  1. 前端

    • 静态页面CDN缓存
    • 按钮防重复点击
    • 倒计时从服务端获取
    • 答题/验证码削峰
  2. 接入层

    • Nginx限流(令牌桶)
    • 用户级限流(同一用户1秒内只能请求1次)
    • 黑名单过滤(机器人、黄牛)
  3. 服务层

    • Redis预减库存(DECR原子操作)
    • 库存为0后直接拒绝(内存标记)
    • 请求入队(消息队列削峰)
  4. 数据层

    • 消息队列异步下单
    • 数据库乐观锁扣减库存:UPDATE SET stock=stock-1 WHERE id=? AND stock>0
    • 订单异步创建

关键技术点:

  • Redis预减库存保证不超卖
  • 消息队列削峰填谷
  • 异步处理提高吞吐量
  • 多级缓存减少数据库压力

27. ⚫ 如何设计一个Feed流系统(朋友圈/微博)?

答:Feed流是社交系统的核心,考察数据模型和读写权衡。

两种模式:

  1. 推模式(Push/Fan-out on Write)

    • 用户发布内容时,推送到所有粉丝的收件箱
    • 读取时直接从收件箱获取
    • 优点:读取快
    • 缺点:大V发布时写放大严重(百万粉丝=百万次写入)
  2. 拉模式(Pull/Fan-out on Read)

    • 用户发布内容只写入自己的发件箱
    • 读取时从所有关注人的发件箱拉取并合并
    • 优点:写入快
    • 缺点:读取慢(需要合并多个列表)
  3. 推拉结合

    • 普通用户用推模式
    • 大V用拉模式(粉丝读取时实时拉取大V的内容)
    • 活跃用户用推模式,不活跃用户用拉模式

数据存储:

  • 收件箱/发件箱:Redis Sorted Set(按时间排序)
  • 内容详情:MySQL/MongoDB
  • 关注关系:图数据库或Redis

28. 🔴 如何设计一个可扩展的权限系统?

答:权限系统是企业应用的基础设施。

权限模型演进:

  1. ACL(Access Control List):直接给用户分配权限。简单但管理困难
  2. RBAC(Role-Based Access Control):用户→角色→权限。最常用
  3. ABAC(Attribute-Based Access Control):基于属性的访问控制。最灵活

RBAC设计:

  • 用户(User):系统使用者
  • 角色(Role):权限的集合(如管理员、编辑、查看者)
  • 权限(Permission):具体的操作权限(如user:create、order:delete)
  • 用户-角色关系:多对多
  • 角色-权限关系:多对多

数据权限:

  • 不仅控制”能做什么”,还控制”能看到什么数据”
  • 实现方式:SQL拼接WHERE条件、行级安全策略(PostgreSQL RLS)

API权限设计:

  • 网关层统一鉴权
  • 使用JWT携带用户角色信息
  • 权限数据缓存到Redis(避免每次查数据库)

29. 🔴 如何设计一个全局唯一的短链接系统?

答:短链接系统考察哈希、存储和高并发设计。

核心设计:

  1. 短码生成

    • 方案1:自增ID + Base62编码(0-9a-zA-Z,62个字符)。6位可表示568亿
    • 方案2:Hash(MurmurHash)取前6位 + 冲突处理
    • 方案3:预生成短码池,使用时分配
  2. 存储

    • MySQL:短码→长URL的映射表
    • Redis:缓存热门短链接
    • 布隆过滤器:快速判断短码是否已存在
  3. 重定向

    • 301(永久重定向):浏览器缓存,减少服务器压力,但无法统计点击
    • 302(临时重定向):每次都经过服务器,可以统计点击数据
  4. 高可用

    • 读多写少,适合缓存
    • Redis缓存热门短链接
    • 数据库读写分离

30. 🔴 如何设计一个分布式配置中心?

答:配置中心是微服务架构的基础设施。

核心需求:

  • 集中管理所有服务的配置
  • 配置变更实时推送到所有实例
  • 支持多环境(dev/test/prod)
  • 支持灰度发布(部分实例先生效)
  • 配置变更审计和回滚

主流方案:

方案 特点
Nacos 阿里开源,配置+服务发现一体
Apollo 携程开源,功能最全,灰度发布
Spring Cloud Config Spring生态,基于Git
etcd 轻量级,Kubernetes原生
Consul HashiCorp,KV存储+服务发现

推送机制:

  • 长轮询(Long Polling):客户端发起请求,服务端hold住直到配置变更或超时。Nacos和Apollo使用
  • Watch机制:客户端注册Watcher,配置变更时服务端主动通知。etcd和ZooKeeper使用
  • WebSocket:全双工通信,实时推送

三、高可用与容灾(31-50题)

31. 🔴 异地多活架构如何设计?有哪些关键挑战?

答:异地多活是高可用架构的终极形态,也是最复杂的架构之一。

架构模式:

  1. 冷备:备用机房平时不提供服务,主机房故障时切换。RTO长
  2. 热备(Active-Standby):备用机房实时同步数据,但不提供服务
  3. 双活(Active-Active):两个机房都提供服务,数据双向同步
  4. 多活:多个机房都提供服务

关键挑战:

  1. 数据一致性:跨机房数据同步延迟(通常10-100ms)

    • 方案:单元化(用户数据只在一个机房写入)
  2. 流量调度:如何将用户请求路由到正确的机房

    • 方案:DNS、GSLB(全局负载均衡)、客户端路由
  3. 数据冲突:双写场景下的数据冲突

    • 方案:避免双写(单元化)、Last Write Wins、CRDT
  4. 故障切换:机房故障时如何快速切换

    • 方案:自动化切换脚本、流量调度系统

单元化架构(推荐):

  • 将用户按规则(如用户ID取模)分配到不同的单元(机房)
  • 每个单元是完整的、自包含的
  • 用户的所有数据和请求都在同一个单元内处理
  • 避免跨单元的数据同步和事务

32. 🔴 灰度发布有哪些策略?如何设计灰度发布系统?

答:灰度发布是降低发布风险的核心手段。

发布策略:

  1. 蓝绿部署:两套完整环境,切换流量
  2. 金丝雀发布:先发布少量实例,观察后逐步扩大
  3. A/B测试:按用户特征分流,对比效果
  4. 滚动发布:逐批替换旧版本实例

灰度维度:

  • 按用户ID:指定用户或用户ID取模
  • 按地域:先在某个城市灰度
  • 按流量比例:1% → 5% → 20% → 50% → 100%
  • 按设备:先灰度Android再灰度iOS
  • 按租户:SaaS场景按租户灰度

灰度发布系统设计:

  • 规则引擎:配置灰度规则(用户白名单、流量比例等)
  • 流量分发:网关层根据规则路由到不同版本
  • 监控对比:对比新旧版本的指标(错误率、延迟、业务指标)
  • 快速回滚:一键回滚到旧版本

33. 🔴 数据库主从切换的方案有哪些?如何保证切换过程中数据不丢失?

答:数据库主从切换是高可用的核心环节。

切换方式:

  1. 手动切换:DBA手动操作,适合计划内维护
  2. 半自动切换:工具辅助,人工确认后执行(如MHA)
  3. 全自动切换:自动检测故障并切换(如Orchestrator、ProxySQL)

MySQL主从切换方案:

  • MHA(Master High Availability):最经典方案

    • 检测主库故障 → 选择数据最新的从库 → 补齐差异binlog → 提升为新主库
    • 优点:尽量保证数据不丢失
    • 缺点:需要SSH访问所有节点
  • Orchestrator:GitHub开源

    • 自动拓扑发现和可视化
    • 支持多种故障转移策略
    • 与Raft集成实现自身高可用
  • MGR(MySQL Group Replication):MySQL官方方案

    • 基于Paxos协议的多主复制
    • 自动故障检测和切换
    • 缺点:对网络延迟敏感

数据不丢失的保障:

  • 半同步复制:主库等待至少一个从库确认收到binlog再返回
  • GTID:全局事务ID,精确追踪复制位点
  • 增强半同步(after_sync):在引擎提交前等待从库确认
  • RPO=0的代价:牺牲一定的写入性能

PostgreSQL主从切换:

  • Patroni:最流行的PG高可用方案,基于etcd/ZK/Consul
  • pg_auto_failover:Citus开源的自动故障转移
  • 流复制 + pg_rewind:旧主库重新加入集群

34. 🔴 如何设计一个高可用的消息队列集群?

答:消息队列的高可用直接影响整个系统的可靠性。

Kafka高可用设计:

  • 副本机制:每个Partition有多个副本(通常3副本)
  • ISR(In-Sync Replicas):与Leader保持同步的副本集合
  • Leader选举:ISR中的副本优先成为新Leader
  • min.insync.replicas:最少同步副本数,配合acks=all保证不丢消息
  • Unclean Leader Election:是否允许非ISR副本成为Leader(可用性vs一致性的权衡)

RocketMQ高可用设计:

  • Dledger模式:基于Raft协议的自动主从切换
  • Controller模式:5.0新增,独立的选主控制器
  • 同步双写:主从同步复制,性能降低但不丢消息

关键设计决策:

  • acks=all + min.insync.replicas=2:保证消息不丢失
  • 消费者手动提交offset:保证消息不丢失
  • 生产者重试 + 幂等:保证消息不重复
  • 消息积压处理:扩容消费者、临时Topic分流

35. 🔵 什么是混沌工程(Chaos Engineering)?如何实践?

答:混沌工程是通过主动注入故障来验证系统韧性的方法论。

核心原则:

  1. 建立稳态假设(定义系统正常行为的指标)
  2. 引入真实世界的变量(网络延迟、节点宕机、磁盘满等)
  3. 在生产环境运行实验(测试环境无法暴露真实问题)
  4. 自动化持续运行
  5. 最小化爆炸半径

常见故障注入:

  • 服务实例随机终止(Netflix Chaos Monkey)
  • 网络延迟/丢包/分区
  • CPU/内存/磁盘压力
  • 依赖服务不可用
  • 时钟偏移

工具:

  • Chaos Monkey:Netflix开源,随机终止实例
  • ChaosBlade:阿里开源,支持多种故障注入
  • Litmus:Kubernetes原生的混沌工程平台
  • Chaos Mesh:PingCAP开源,K8s环境

实践步骤:

  1. 从非核心服务开始
  2. 先在预发环境验证
  3. 逐步扩展到生产环境
  4. 建立完善的监控和告警
  5. 每次实验后复盘和改进

36. 🔴 如何设计系统的监控告警体系?

答:监控是高可用的眼睛,没有监控的系统等于裸奔。

监控层次(从下到上):

  1. 基础设施监控:CPU、内存、磁盘、网络(Prometheus + Node Exporter)
  2. 中间件监控:数据库、缓存、消息队列的指标
  3. 应用监控:JVM指标、接口RT、错误率、QPS
  4. 业务监控:订单量、支付成功率、转化率
  5. 用户体验监控:页面加载时间、首屏时间、JS错误率

监控技术栈:

  • 指标(Metrics):Prometheus + Grafana
  • 日志(Logging):ELK(Elasticsearch + Logstash + Kibana)或 Loki
  • 链路追踪(Tracing):Jaeger、SkyWalking、Zipkin
  • 三者统一:OpenTelemetry(OTEL)

告警设计原则:

  • 告警必须可操作(收到告警知道该做什么)
  • 分级:P0(立即处理)、P1(30分钟内)、P2(工作时间处理)
  • 避免告警风暴(聚合、抑制、静默)
  • 告警升级机制(5分钟未处理升级到主管)
  • 定期review告警规则,删除无效告警

关键指标(RED方法):

  • Rate:请求速率
  • Errors:错误率
  • Duration:响应时间

37. 🔴 链路追踪的原理是什么?如何在微服务中实现全链路追踪?

答:链路追踪是微服务可观测性的核心能力。

核心概念(OpenTracing/OpenTelemetry):

  • Trace:一次完整的请求链路(从入口到所有下游调用)
  • Span:链路中的一个操作(如一次RPC调用、一次数据库查询)
  • SpanContext:跨进程传递的上下文(TraceID、SpanID、采样标记)
  • Baggage:跨进程传递的业务数据

工作原理:

  1. 入口服务生成全局唯一的TraceID
  2. 每个操作创建一个Span,记录开始时间、结束时间、标签
  3. 调用下游服务时,通过HTTP Header或RPC元数据传递SpanContext
  4. 下游服务创建子Span,关联父SpanID
  5. 所有Span异步上报到收集器
  6. 收集器根据TraceID聚合所有Span,构建调用树

采样策略:

  • 全量采样:所有请求都追踪(开发/测试环境)
  • 固定比例采样:如1%的请求(生产环境)
  • 自适应采样:根据流量动态调整采样率
  • 尾部采样:先收集所有Span,根据结果决定是否保留(如只保留慢请求和错误请求)

主流方案:

  • SkyWalking:Java生态首选,字节码增强无侵入
  • Jaeger:CNCF项目,Go语言实现
  • Zipkin:Twitter开源,最早的链路追踪系统
  • OpenTelemetry:统一标准,未来趋势

38. 🔴 如何设计一个高可用的定时任务系统?

答:定时任务在分布式环境下面临单点执行、故障恢复等挑战。

核心需求:

  • 分布式环境下任务只执行一次(不重复执行)
  • 任务执行失败自动重试
  • 支持任务分片(大任务拆分到多个节点并行执行)
  • 任务执行监控和告警
  • 支持动态添加/修改/暂停任务

方案对比:

方案 特点
Quartz Java经典,数据库锁实现分布式
XXL-JOB 国产,轻量级,使用广泛
Elastic-Job 当当开源,基于ZooKeeper
PowerJob 新一代,支持MapReduce任务
Kubernetes CronJob 云原生方案

XXL-JOB架构:

  • 调度中心:负责任务调度,基于数据库锁保证单点触发
  • 执行器:注册到调度中心,接收并执行任务
  • 路由策略:第一个、最后一个、轮询、随机、一致性Hash、最不经常使用、故障转移

任务分片设计:

  • 将大任务按分片参数拆分(如按用户ID取模)
  • 每个执行器处理自己的分片
  • 分片数 = 执行器数量,自动负载均衡

39. 🔵 什么是SLA?如何制定和保障SLA?

答:SLA(Service Level Agreement)是服务提供者与消费者之间的服务质量承诺。

关键指标:

  • 可用性:99.9%、99.99%、99.999%
  • 响应时间:P50 < 100ms、P99 < 500ms、P999 < 1s
  • 吞吐量:支持10000 QPS
  • 错误率:< 0.1%

SLI/SLO/SLA的关系:

  • SLI(Service Level Indicator):服务质量指标(如请求成功率)
  • SLO(Service Level Objective):服务质量目标(如成功率 > 99.9%)
  • SLA(Service Level Agreement):服务质量协议(SLO + 违约后果)

制定原则:

  • 基于历史数据制定,不要拍脑袋
  • 留有余量(内部SLO比外部SLA更严格)
  • 不同服务不同SLA(核心服务更高)
  • 定期review和调整

保障措施:

  • 错误预算(Error Budget):允许的故障时间。用完后冻结发布
  • 容量规划:定期压测,确保容量满足SLA
  • 故障演练:定期进行故障注入和恢复演练
  • On-Call机制:7x24小时值班响应

40. 🔴 如何设计一个多租户(Multi-Tenant)系统?

答:多租户是SaaS系统的核心架构模式。

隔离模式:

  1. 独立数据库:每个租户一个数据库

    • 隔离性最好,安全性最高
    • 成本最高,运维复杂
    • 适合大客户、合规要求高的场景
  2. 共享数据库,独立Schema

    • 每个租户一个Schema
    • 隔离性较好,成本适中
    • PostgreSQL的Schema天然支持
  3. 共享数据库,共享表

    • 所有租户数据在同一张表,通过tenant_id区分
    • 成本最低,运维最简单
    • 隔离性最差,需要严格的数据过滤

数据隔离保障:

  • 所有SQL自动加上tenant_id条件(MyBatis拦截器/Hibernate Filter)
  • PostgreSQL行级安全策略(RLS)
  • API层校验tenant_id与当前用户匹配

性能隔离:

  • 限流:每个租户独立的限流配额
  • 资源配额:CPU、内存、存储的配额限制
  • 队列隔离:不同租户的请求进入不同队列

租户路由:

  • 通过域名识别租户(tenant1.app.com)
  • 通过Header传递租户ID
  • 通过JWT中的租户信息

41. 🔴 容灾演练应该怎么做?有哪些关键步骤?

答:容灾演练是验证高可用方案有效性的唯一手段。

演练类型:

  1. 桌面演练:纸上谈兵,讨论故障场景和应对方案
  2. 模拟演练:在测试环境模拟故障
  3. 真实演练:在生产环境注入真实故障
  4. 突袭演练:不提前通知,考验真实应急能力

关键步骤:

  1. 制定计划:明确演练目标、范围、时间、参与人员
  2. 风险评估:评估演练可能造成的影响,准备回滚方案
  3. 故障注入:按计划注入故障(如关闭主库、断开网络)
  4. 观察记录:记录系统表现、告警触发、自动恢复情况
  5. 人工介入:如果自动恢复失败,人工介入处理
  6. 恢复验证:确认系统完全恢复正常
  7. 复盘总结:分析问题,制定改进计划

演练场景清单:

  • 数据库主库宕机 → 自动切换到从库
  • Redis主节点故障 → Sentinel自动切换
  • 单个服务实例宕机 → 负载均衡自动摘除
  • 机房网络中断 → 流量切换到备用机房
  • 消息队列Broker宕机 → 自动Leader选举
  • 配置中心不可用 → 本地缓存兜底

42. 🔴 如何设计一个高可用的注册中心?注册中心挂了怎么办?

答:注册中心是微服务架构的心脏,它的可用性至关重要。

注册中心高可用方案:

  • 集群部署:至少3个节点(Raft/Paxos需要奇数节点)
  • 多数据中心:跨机房部署,避免单机房故障
  • 数据持久化:注册数据持久化到磁盘

注册中心挂了的应对:

  1. 客户端缓存:服务消费者本地缓存服务列表

    • Dubbo:本地文件缓存(~/.dubbo/
    • Nacos:本地快照文件
    • 注册中心挂了,已有的服务调用不受影响
  2. 直连模式:绕过注册中心,直接配置服务地址

    • 作为降级方案,在配置中心配置直连地址
  3. 多注册中心:同时注册到多个注册中心

    • Dubbo支持多注册中心配置
  4. DNS兜底:将服务地址配置到DNS

    • 注册中心不可用时,通过DNS解析服务地址

关键认知:

  • 注册中心是CP还是AP决定了故障时的行为
  • AP(Eureka/Nacos):注册中心节点间数据可能不一致,但服务可用
  • CP(ZooKeeper/Consul):注册中心不可用时,无法注册新服务

43. 🔴 如何处理分布式系统中的数据不一致问题?

答:数据不一致是分布式系统中最棘手的问题之一。

不一致的来源:

  • 网络分区导致数据同步中断
  • 服务调用失败(部分成功部分失败)
  • 缓存与数据库不一致
  • 消息丢失或重复消费
  • 并发写入冲突

解决方案:

  1. 对账系统:定时对比不同数据源的数据

    • 如:订单系统和支付系统的金额对账
    • 发现不一致后自动或人工修复
  2. 补偿机制:失败后执行补偿操作

    • SAGA模式的补偿事务
    • 定时任务重试失败的操作
  3. 最终一致性:通过消息队列保证最终一致

    • 本地消息表 + 定时发送
    • 事务消息(RocketMQ)
  4. 版本控制:通过版本号检测和解决冲突

    • 乐观锁:WHERE version = ?
    • 向量时钟:检测并发冲突
  5. CRDT(Conflict-free Replicated Data Type)

    • 无冲突复制数据类型
    • 数学上保证最终一致性
    • 适合计数器、集合等简单数据结构

实践建议:

  • 核心数据(资金):强一致性(分布式事务)
  • 非核心数据(统计):最终一致性(异步对账)
  • 缓存数据:设置过期时间 + 主动失效

44. 🔵 什么是故障隔离?有哪些隔离策略?

答:故障隔离是防止局部故障扩散为全局故障的关键手段。

隔离策略:

  1. 线程池隔离

    • 不同的下游服务使用不同的线程池
    • 某个下游服务慢不会占满所有线程
    • Hystrix的默认隔离策略
    • 缺点:线程切换开销
  2. 信号量隔离

    • 使用信号量限制并发数
    • 无线程切换开销
    • 适合快速调用(如本地缓存查询)
  3. 进程隔离

    • 不同服务部署在不同的进程/容器中
    • 微服务架构天然具备进程隔离
  4. 机房隔离

    • 不同机房独立运行
    • 单元化架构实现机房级隔离
  5. 数据隔离

    • 读写分离:读请求走从库,写请求走主库
    • 冷热分离:热数据在缓存,冷数据在磁盘
  6. 用户隔离

    • VIP用户和普通用户使用不同的服务集群
    • 防止普通用户的流量影响VIP用户

泳道隔离(Swimlane):

  • 将系统按业务维度划分为多个泳道
  • 每个泳道是完整的服务链路
  • 泳道之间互不影响
  • 适合灰度发布和故障隔离

45. 🔴 如何设计一个高可用的缓存架构?

答:缓存是系统性能的关键,缓存故障可能导致系统雪崩。

多级缓存架构:

  1. L1 - 本地缓存:Caffeine/Guava Cache,毫秒级响应
  2. L2 - 分布式缓存:Redis Cluster,亚毫秒级响应
  3. L3 - 数据库:MySQL/PostgreSQL,毫秒到秒级响应

Redis高可用方案:

  • 主从复制 + Sentinel:自动故障转移,适合中小规模
  • Redis Cluster:分片 + 副本,适合大规模
  • Proxy方案:Twemproxy、Codis,客户端透明

缓存故障应对:

  1. 缓存穿透:查询不存在的数据

    • 布隆过滤器拦截
    • 缓存空值(设置短过期时间)
  2. 缓存击穿:热点key过期

    • 互斥锁(SETNX):只允许一个请求回源
    • 逻辑过期:不设置物理过期时间,后台异步更新
  3. 缓存雪崩:大量key同时过期

    • 过期时间加随机值
    • 多级缓存兜底
    • 限流降级

缓存一致性方案:

  • Cache Aside:先更新DB,再删除缓存(最常用)
  • 延迟双删:更新DB → 删缓存 → 延迟再删一次
  • 订阅binlog:Canal监听binlog,异步更新缓存

46. 🔴 如何做容量规划?如何评估系统能承受多大的流量?

答:容量规划是保障系统稳定性的前置工作。

容量规划步骤:

  1. 业务预估:预估未来的业务量(用户数、订单量、QPS)
  2. 性能基线:通过压测获取单机性能指标
  3. 资源计算:根据业务量和单机性能计算所需资源
  4. 冗余设计:预留30-50%的冗余容量
  5. 持续验证:定期压测验证容量是否满足

压测方法:

  • 单机压测:确定单个实例的极限性能
  • 全链路压测:模拟真实流量,验证整个系统的容量
  • 影子库/影子表:压测流量写入影子库,不影响生产数据

全链路压测关键点:

  • 压测流量标记(Header中添加标记)
  • 影子库/影子表隔离压测数据
  • 中间件识别压测流量(消息队列、缓存)
  • 第三方服务Mock
  • 压测数据清理

容量计算公式:

  • 所需实例数 = 峰值QPS / 单机QPS × 安全系数(1.5-2)
  • 峰值QPS = 日均QPS × 峰值系数(通常3-5倍)
  • 日均QPS = 日请求量 / 86400

47. 🔴 什么是流量回放?如何用流量回放做回归测试?

答:流量回放是将生产流量录制下来,在测试环境重放,验证系统正确性。

核心流程:

  1. 流量录制:在生产环境录制请求和响应
  2. 流量存储:将录制的流量存储到文件或消息队列
  3. 流量回放:在测试环境重放录制的流量
  4. 结果对比:对比新旧版本的响应差异

工具:

  • GoReplay(gor):Go语言实现,轻量级HTTP流量录制回放
  • JVM-Sandbox-Repeater:阿里开源,Java应用流量录制回放
  • TCPCopy:TCP层流量复制
  • Diffy:Twitter开源,自动对比新旧版本响应

关键挑战:

  • 时间依赖:录制时和回放时的时间不同
  • 外部依赖:回放时不能调用真实的第三方服务
  • 数据依赖:回放时数据库状态可能不同
  • 幂等性:重放写请求可能产生副作用

解决方案:

  • Mock外部依赖
  • 使用影子库隔离数据
  • 时间参数替换
  • 只回放读请求(安全模式)

48. 🔴 如何设计一个高可用的日志系统?

答:日志系统是排查问题的生命线,它自身的高可用同样重要。

日志架构(ELK/EFK):

  1. 采集层:Filebeat/Fluentd/Fluent Bit 采集日志文件
  2. 缓冲层:Kafka 缓冲日志数据,削峰填谷
  3. 处理层:Logstash/Flink 解析、过滤、转换日志
  4. 存储层:Elasticsearch 存储和索引日志
  5. 展示层:Kibana/Grafana 查询和可视化

高可用设计:

  • 采集端:本地文件缓冲,采集器故障不丢日志
  • Kafka:多副本,保证日志不丢失
  • Elasticsearch:多节点集群,分片和副本
  • 冷热分离:热数据SSD,冷数据HDD,定期归档到对象存储

日志规范:

  • 统一日志格式(JSON结构化日志)
  • 必须包含:时间戳、TraceID、服务名、日志级别、消息
  • 敏感信息脱敏(手机号、身份证号)
  • 日志级别合理使用(ERROR只用于需要人工介入的错误)

日志量控制:

  • 生产环境默认INFO级别
  • 支持动态调整日志级别(不重启)
  • 采样日志:高频日志按比例采样
  • 日志保留策略:热数据7天,温数据30天,冷数据90天

49. 🔴 什么是无损发布?如何实现?

答:无损发布是指在发布过程中不丢失任何请求,用户无感知。

有损发布的问题:

  • 服务下线时,正在处理的请求被中断
  • 新服务还没准备好就接收流量
  • 注册中心感知延迟,流量仍然路由到已下线的实例

无损发布的关键步骤:

  1. 优雅下线

    • 先从注册中心注销
    • 等待一段时间(让消费者感知)
    • 拒绝新请求,处理完已有请求
    • 关闭连接,停止服务
  2. 优雅上线

    • 服务启动完成后再注册到注册中心
    • 健康检查通过后再接收流量
    • 预热(Warm Up):逐步增加流量,避免冷启动
  3. 就绪探针(Readiness Probe)

    • Kubernetes中的就绪探针
    • 只有探针通过才会接收流量
  4. 预热机制

    • JIT编译预热
    • 连接池预热
    • 缓存预热
    • Dubbo的warmup参数:启动后逐步增加权重

Spring Boot优雅停机:

1
2
3
4
5
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s

50. ⚫ 如何设计一个能支撑千万级DAU的系统架构?

答:这是一个综合性的架构设计题,考察全局视野。

流量估算:

  • 千万DAU → 峰值在线用户约100万
  • 假设每用户每分钟1次请求 → 峰值QPS约16000
  • 考虑峰值系数3倍 → 设计目标50000 QPS

架构分层:

  1. 接入层:CDN + LB(Nginx/SLB)

    • 静态资源CDN加速
    • 多机房部署,DNS负载均衡
    • Nginx集群,支持10万+并发连接
  2. 网关层:API Gateway

    • 认证鉴权、限流熔断
    • 协议转换、请求路由
    • 水平扩展,无状态
  3. 服务层:微服务集群

    • 按业务域拆分微服务
    • 服务注册发现(Nacos)
    • RPC通信(Dubbo/gRPC)
  4. 缓存层:多级缓存

    • L1本地缓存(Caffeine)
    • L2分布式缓存(Redis Cluster)
    • 缓存命中率 > 95%
  5. 数据层:分库分表 + 读写分离

    • 写库:分库分表(ShardingSphere)
    • 读库:多从库 + 读写分离
    • 搜索:Elasticsearch
    • 大数据:ClickHouse/Doris
  6. 消息层:异步解耦

    • Kafka处理高吞吐场景
    • RocketMQ处理事务消息

关键设计原则:

  • 无状态设计:服务实例可随时扩缩容
  • 异步化:能异步的都异步
  • 缓存化:能缓存的都缓存
  • 弹性伸缩:基于指标自动扩缩容

四、微服务治理(51-70题)

51. 🔴 微服务的数据一致性如何保证?

答:微服务架构下,每个服务有自己的数据库,跨服务的数据一致性是核心挑战。

方案选择矩阵:

场景 方案 一致性
资金交易 TCC/2PC 强一致
订单流程 SAGA 最终一致
通知类 本地消息表 最终一致
数据同步 CDC + MQ 最终一致

SAGA实战(订单流程):

  1. 创建订单(订单服务)
  2. 扣减库存(库存服务)
  3. 扣减余额(账户服务)
  4. 任何一步失败,逆序补偿

编排式 vs 协同式:

  • 编排式(Orchestration):有一个中心协调者,按顺序调用各服务
    • 优点:流程清晰,易于监控
    • 缺点:协调者是单点
  • 协同式(Choreography):各服务通过事件驱动,自行响应
    • 优点:去中心化,松耦合
    • 缺点:流程分散,难以追踪

52. 🔴 如何实现微服务的优雅停机和无损上下线?

答:这是微服务运维中最容易被忽视但影响巨大的问题。

下线流程(以Dubbo为例):

  1. 从注册中心注销服务
  2. 发送readonly事件通知消费者
  3. 等待消费者感知(默认等待10s)
  4. 停止接收新请求
  5. 等待已有请求处理完成(超时时间内)
  6. 关闭连接,停止JVM

上线流程:

  1. JVM启动,Spring容器初始化
  2. 连接池预热(数据库、Redis、HTTP)
  3. JIT预热(可选:回放录制的流量)
  4. 健康检查通过
  5. 注册到注册中心
  6. 逐步增加流量权重(Warm Up)

Kubernetes环境:

  • preStop Hook:Pod终止前执行脚本(注销注册中心)
  • terminationGracePeriodSeconds:优雅停机等待时间
  • Readiness Probe:就绪探针通过后才接收流量
  • Startup Probe:启动探针,避免慢启动被杀

常见问题:

  • 注册中心感知延迟:消费者缓存了旧的服务列表
  • 长连接未断开:需要主动关闭连接
  • 异步任务未完成:需要等待线程池中的任务执行完

53. 🔵 微服务的版本管理策略有哪些?

答:API版本管理是微服务长期演进的关键。

版本策略:

  1. URL版本/api/v1/users/api/v2/users

    • 最直观,客户端容易理解
    • 缺点:URL变化,缓存失效
  2. Header版本Accept: application/vnd.api.v2+json

    • URL不变,更RESTful
    • 缺点:不够直观
  3. Query参数版本/api/users?version=2

    • 简单,但不够优雅
  4. 语义化版本(SemVer):Major.Minor.Patch

    • Major:不兼容的变更
    • Minor:向后兼容的新功能
    • Patch:向后兼容的Bug修复

兼容性设计:

  • 新增字段不影响旧客户端(向后兼容)
  • 废弃字段标记为deprecated,不立即删除
  • 使用Protobuf/Avro等支持schema演进的序列化格式
  • 消费者驱动的契约测试(Pact)

多版本共存:

  • 网关层路由不同版本到不同的服务实例
  • 同一服务内部通过适配器支持多版本
  • 设置版本淘汰策略(如最多支持最近3个版本)

54. 🔴 如何设计微服务的统一异常处理和错误码体系?

答:统一的错误处理是微服务可维护性的基础。

错误码设计:

1
2
格式:[服务代码][模块代码][错误序号]
示例:ORD-PAY-001(订单服务-支付模块-第1个错误)

错误分类:

  • 客户端错误(4xx):参数错误、权限不足、资源不存在
  • 服务端错误(5xx):内部异常、依赖服务不可用
  • 业务错误:业务规则校验失败(如余额不足、库存不足)

统一响应格式:

1
2
3
4
5
6
7
{
"code": "ORD-PAY-001",
"message": "余额不足",
"detail": "当前余额: 100, 需要: 200",
"traceId": "abc123",
"timestamp": "2024-01-01T00:00:00Z"
}

异常处理原则:

  • 业务异常:返回明确的错误码和消息,不记录ERROR日志
  • 系统异常:返回通用错误信息,记录ERROR日志(含堆栈)
  • 第三方异常:包装为内部错误码,隐藏第三方细节
  • 不要吞掉异常:至少记录日志
  • 不要暴露内部细节:不返回堆栈信息给客户端

跨服务异常传播:

  • 下游服务的错误码透传给上游
  • 上游服务可以选择转换错误码或直接透传
  • TraceID贯穿整个调用链,方便排查

55. 🔴 如何实现微服务的配置热更新?

答:配置热更新是微服务运维的基本能力。

实现方式:

  1. 配置中心推送:Nacos/Apollo配置变更后推送到客户端
  2. Spring Cloud Config + Bus:配置变更通过消息总线通知所有实例
  3. Kubernetes ConfigMap + 热加载:ConfigMap变更后自动重载

Spring Cloud + Nacos实现:

  • @RefreshScope:Bean级别的配置刷新
  • @NacosValue(autoRefreshed = true):字段级别的自动刷新
  • 监听配置变更事件,执行自定义逻辑

热更新的注意事项:

  • 线程安全:配置更新时可能有线程正在使用旧值
  • 原子性:多个配置项需要同时生效
  • 回滚:配置变更后出问题需要快速回滚
  • 灰度:配置变更先在部分实例生效

不适合热更新的配置:

  • 数据库连接池大小(需要重建连接池)
  • 端口号(需要重启)
  • 日志框架配置(部分框架不支持)

56. 🔴 微服务的安全如何设计?认证和授权方案有哪些?

答:微服务安全是架构设计中不可忽视的部分。

认证方案:

  1. JWT(JSON Web Token)

    • 无状态,不需要服务端存储Session
    • 网关验证JWT签名,提取用户信息
    • 缺点:无法主动失效(需要黑名单机制)
  2. OAuth 2.0

    • 标准的授权框架
    • 支持多种授权模式(授权码、客户端凭证等)
    • 适合对外开放API
  3. mTLS(双向TLS)

    • 服务间通信的身份认证
    • Service Mesh自动管理证书
    • 零信任网络的基础

微服务安全架构:

  • 外部请求:网关统一认证(JWT/OAuth)
  • 内部通信:mTLS + 服务身份认证
  • 数据安全:传输加密(TLS)+ 存储加密(AES)
  • API安全:限流、防重放、签名验证

零信任架构(Zero Trust):

  • 不信任任何网络位置(内网也不信任)
  • 每次请求都需要认证和授权
  • 最小权限原则
  • 持续验证(不是一次认证永久有效)

57. 🔴 如何处理微服务之间的循环依赖?

答:循环依赖是微服务架构中的设计缺陷,必须消除。

循环依赖的表现:

  • 服务A调用服务B,服务B又调用服务A
  • 部署时无法确定启动顺序
  • 故障时互相影响,难以排查

解决方案:

  1. 引入中间服务:将共同依赖的逻辑抽取到新服务C

    • A → C ← B(消除循环)
  2. 事件驱动:将同步调用改为异步事件

    • A发布事件 → B订阅事件(解耦)
  3. 合并服务:如果两个服务耦合度太高,考虑合并

    • 说明拆分粒度过细
  4. 回调机制:B完成后通过回调通知A

    • 而不是B主动调用A
  5. 共享数据库(反模式,慎用)

    • 两个服务共享一个数据库
    • 违反微服务原则,但在过渡期可以接受

预防措施:

  • 服务依赖关系可视化(定期review)
  • 架构守护规则(ArchUnit等工具检测循环依赖)
  • 代码review时关注跨服务调用

58. 🔴 如何设计微服务的日志和审计系统?

答:日志和审计是微服务可观测性和合规性的基础。

日志分类:

  1. 访问日志:HTTP请求日志(URL、参数、响应码、耗时)
  2. 业务日志:业务操作日志(谁在什么时间做了什么)
  3. 错误日志:异常和错误信息
  4. 审计日志:合规审计(数据变更、权限操作)
  5. 安全日志:登录、权限变更、敏感操作

统一日志格式:

1
2
3
4
5
6
7
8
9
10
{
"timestamp": "2024-01-01T00:00:00.000Z",
"level": "INFO",
"service": "order-service",
"traceId": "abc123",
"spanId": "def456",
"userId": "user001",
"message": "订单创建成功",
"extra": {"orderId": "ORD001", "amount": 100}
}

审计日志设计:

  • 记录所有数据变更(谁、什么时间、改了什么、改前值、改后值)
  • 审计日志不可篡改(写入后不能修改或删除)
  • 独立存储(不与业务日志混在一起)
  • 支持查询和导出(合规审计需要)

实现方式:

  • AOP拦截:通过注解标记需要审计的方法
  • 数据库触发器:自动记录数据变更
  • CDC(Change Data Capture):监听binlog记录变更
  • Event Sourcing:天然具备审计能力

59. 🔴 如何实现微服务的流量染色和全链路灰度?

答:流量染色是实现全链路灰度发布的基础技术。

流量染色原理:

  1. 在网关层给请求打标签(如Header: x-gray=true)
  2. 标签在整个调用链路中传递
  3. 每个服务根据标签路由到对应版本的实例

全链路灰度架构:

  • 网关:根据规则(用户ID、IP、Header)给流量打标签
  • RPC框架:传递标签,路由到对应版本的服务实例
  • 消息队列:灰度消息路由到灰度消费者
  • 数据库:灰度流量使用影子表(可选)
  • 缓存:灰度流量使用独立的缓存key前缀

标签传递机制:

  • HTTP Header传递
  • RPC隐式参数传递(Dubbo Attachment、gRPC Metadata)
  • 线程上下文传递(ThreadLocal → 跨线程需要特殊处理)
  • 消息属性传递(Kafka Header、RocketMQ Properties)

跨线程传递方案:

  • TransmittableThreadLocal(阿里TTL)
  • 线程池包装器(自动传递上下文)
  • OpenTelemetry的Context Propagation

60. 🔴 微服务的数据库如何做Schema变更?如何保证兼容性?

答:数据库Schema变更是微服务持续交付中的高风险操作。

安全变更原则:

  1. 向后兼容:新Schema兼容旧代码
  2. 小步迭代:大变更拆分为多个小变更
  3. 先扩展后收缩:先加新列,迁移数据,再删旧列

安全的变更操作:

  • ✅ 添加新列(有默认值)
  • ✅ 添加新表
  • ✅ 添加索引(Online DDL)
  • ❌ 删除列(可能有旧代码在使用)
  • ❌ 重命名列(等同于删除+添加)
  • ❌ 修改列类型(可能数据丢失)

不兼容变更的安全步骤(以重命名列为例):

  1. 添加新列(new_name)
  2. 代码同时写入新旧两列
  3. 迁移历史数据到新列
  4. 代码只读新列
  5. 停止写入旧列
  6. 删除旧列

工具:

  • Flyway:Java生态的数据库迁移工具
  • Liquibase:支持多种数据库的迁移工具
  • gh-ost:GitHub的在线DDL工具(MySQL)
  • pt-online-schema-change:Percona的在线DDL工具

61. 🔵 什么是BFF(Backend For Frontend)模式?

答:BFF是为不同前端提供定制化后端服务的架构模式。

问题背景:

  • 不同前端(Web、App、小程序)需要不同的数据格式
  • 一个通用API很难满足所有前端的需求
  • 前端需要聚合多个微服务的数据

BFF架构:

1
2
3
Web → Web BFF → 微服务A、B、C
App → App BFF → 微服务A、B、C
小程序 → 小程序BFF → 微服务A、B、C

BFF的职责:

  • 数据聚合:调用多个微服务,组装前端需要的数据
  • 数据裁剪:只返回前端需要的字段
  • 协议转换:将内部gRPC转换为HTTP/GraphQL
  • 认证适配:不同端的认证方式不同

BFF vs API Gateway:

  • API Gateway:通用的基础设施(路由、限流、认证)
  • BFF:面向特定前端的业务逻辑(数据聚合、格式转换)
  • 两者可以共存:请求先经过Gateway,再到BFF

GraphQL作为BFF:

  • 前端自定义查询,按需获取数据
  • 减少过度获取和不足获取
  • 适合数据关系复杂的场景

62. 🔴 如何设计微服务的健康检查机制?

答:健康检查是服务治理的基础,直接影响故障发现和恢复速度。

健康检查类型:

  1. 存活检查(Liveness):服务进程是否存活

    • 失败处理:重启服务
    • 检查内容:进程是否响应、是否死锁
  2. 就绪检查(Readiness):服务是否准备好接收流量

    • 失败处理:从负载均衡中摘除
    • 检查内容:依赖的数据库、缓存是否可用
  3. 启动检查(Startup):服务是否启动完成

    • 失败处理:等待,超时后重启
    • 适用于启动慢的服务

健康检查设计原则:

  • 检查要快(超时时间短,如3秒)
  • 检查要轻(不要做重计算)
  • 区分自身健康和依赖健康
  • 避免级联不健康(A依赖B,B不健康导致A也不健康)

Spring Boot Actuator:

  • /actuator/health:综合健康状态
  • /actuator/health/liveness:存活状态
  • /actuator/health/readiness:就绪状态
  • 自定义HealthIndicator检查业务依赖

深度健康检查 vs 浅度健康检查:

  • 浅度:只检查服务进程是否存活(用于Liveness)
  • 深度:检查所有依赖是否可用(用于Readiness,但要注意级联问题)

63. 🔴 如何实现微服务的请求幂等?

答:幂等是微服务可靠性的基石,尤其在重试和消息重复消费场景下。

幂等场景:

  • 用户重复点击提交按钮
  • 网络超时后客户端重试
  • 消息队列重复投递
  • 服务调用重试(Dubbo/gRPC重试)

实现方案(按场景):

  1. 创建类操作(如创建订单):

    • 客户端生成唯一请求ID(idempotency-key)
    • 服务端用Redis SETNX检查是否已处理
    • 已处理则直接返回上次的结果
  2. 更新类操作(如扣减库存):

    • 乐观锁:UPDATE SET stock=stock-1 WHERE id=? AND version=?
    • 状态机:只允许合法的状态转换
  3. 消息消费

    • 消息ID去重(Redis或数据库唯一键)
    • 业务唯一键去重(如订单号)
  4. 支付类操作

    • 支付流水号唯一约束
    • 先查询支付状态,已支付则直接返回

幂等框架设计:

1
2
3
4
@Idempotent(key = "#request.orderId", expireSeconds = 3600)
public OrderResponse createOrder(CreateOrderRequest request) {
// 业务逻辑
}
  • 通过AOP拦截,自动实现幂等检查
  • 支持自定义key表达式
  • 支持配置过期时间

64. 🔴 微服务的超时设置有什么讲究?

答:超时设置看似简单,但设置不当会导致严重问题。

超时类型:

  • 连接超时(Connect Timeout):建立TCP连接的超时,通常1-3秒
  • 读超时(Read Timeout):等待响应的超时,根据接口特性设置
  • 写超时(Write Timeout):发送请求的超时,通常较短

超时设置原则:

  1. 链路超时递减:上游超时 > 下游超时

    • 网关超时5s > 服务A超时3s > 服务B超时1s
    • 否则上游已超时返回,下游还在处理(资源浪费)
  2. 基于P99设置:超时时间 = P99延迟 × 2

    • 太短:正常请求被超时
    • 太长:故障时线程长时间阻塞
  3. 区分读写:写操作超时可以长一些,读操作超时短一些

  4. 重试与超时的关系

    • 总超时 = 单次超时 × 重试次数
    • 总超时不能超过上游的超时

常见问题:

  • 超时设置过长 → 线程池耗尽 → 服务雪崩
  • 超时设置过短 → 正常请求失败率高
  • 没有设置超时 → 默认无限等待 → 线程泄漏
  • 重试风暴 → 下游压力倍增

65. 🔴 如何设计微服务的重试策略?

答:重试是提高系统可靠性的重要手段,但不当的重试会加剧故障。

重试策略:

  1. 固定间隔重试:每次重试间隔相同(如1秒)
  2. 指数退避重试:间隔指数增长(1s, 2s, 4s, 8s…)
  3. 指数退避 + 随机抖动:在指数退避基础上加随机值,避免重试风暴
  4. 立即重试 + 退避重试:第一次立即重试,后续退避

重试的前提条件:

  • 操作是幂等的(非幂等操作不能重试)
  • 错误是可重试的(如网络超时、503)
  • 非可重试错误:400参数错误、401未授权、404不存在

重试风暴问题:

  • 服务B故障 → 服务A重试3次 → 服务B收到3倍流量
  • 如果有多层调用:A→B→C,C故障,重试次数指数增长
  • 解决:限制重试次数、熔断器、重试预算(如最多10%的请求是重试)

重试预算(Retry Budget):

  • 统计一段时间内的重试比例
  • 如果重试比例超过阈值(如10%),停止重试
  • 防止重试风暴

66. 🔵 什么是服务网格(Service Mesh)的数据平面和控制平面?

答:Service Mesh将服务治理能力从应用代码中剥离到基础设施层。

数据平面(Data Plane):

  • 由部署在每个服务旁边的Sidecar代理组成
  • 负责实际的网络通信(请求转发、负载均衡、熔断等)
  • 代表:Envoy、Linkerd-proxy
  • 功能:服务发现、负载均衡、TLS加密、重试、超时、熔断、指标采集

控制平面(Control Plane):

  • 管理和配置所有数据平面的代理
  • 下发路由规则、安全策略、流量管理策略
  • 代表:Istio(istiod)、Linkerd(control plane)
  • 功能:配置分发、证书管理、策略管理、遥测收集

Istio架构:

  • istiod:统一的控制平面组件
    • Pilot:服务发现和流量管理
    • Citadel:证书管理和安全
    • Galley:配置验证和分发
  • Envoy Sidecar:数据平面代理
    • 自动注入到每个Pod
    • 拦截所有进出流量

无Sidecar方案(新趋势):

  • Ambient Mesh(Istio):不使用Sidecar,使用节点级代理
  • 减少资源消耗和延迟
  • 简化运维

67. 🔴 如何设计一个高效的服务间批量调用方案?

答:微服务间的批量调用是性能优化的重要课题。

问题场景:

  • 列表页需要调用用户服务获取100个用户信息
  • 逐个调用:100次RPC,延迟 = 100 × 单次延迟
  • 批量调用:1次RPC,延迟 = 1 × 单次延迟

批量调用方案:

  1. 批量API:提供批量查询接口

    1
    2
    3
    4
    // 单个查询
    User getUser(Long userId);
    // 批量查询
    Map<Long, User> batchGetUsers(List<Long> userIds);
  2. 请求合并(Request Collapsing)

    • 将短时间内的多个单次请求合并为一个批量请求
    • Hystrix的Request Collapsing
    • 自定义:使用时间窗口 + 队列收集请求
  3. DataLoader模式

    • GraphQL中的经典模式
    • 在一个事件循环中收集所有数据请求
    • 批量发送,结果分发给各个请求者
  4. 并行调用

    • CompletableFuture并行调用多个服务
    • 总延迟 = max(各服务延迟)

设计注意:

  • 批量接口要限制单次请求数量(如最多500个)
  • 批量接口要支持部分失败(返回成功和失败的结果)
  • 考虑超时:批量请求的超时要比单次请求长

68. 🔴 如何设计微服务的契约测试?

答:契约测试是保证微服务间接口兼容性的关键手段。

问题背景:

  • 服务A依赖服务B的API
  • 服务B修改了API,但没有通知服务A
  • 集成测试时才发现不兼容 → 发布延迟

契约测试(Consumer-Driven Contract Testing):

  1. 消费者定义契约:消费者定义期望的请求和响应格式
  2. 生产者验证契约:生产者运行契约测试,确保满足消费者的期望
  3. 契约共享:通过Pact Broker或Git共享契约文件

工具:

  • Pact:最流行的契约测试框架,支持多语言
  • Spring Cloud Contract:Spring生态的契约测试

Pact工作流:

  1. 消费者编写测试,生成Pact文件(JSON格式的契约)
  2. Pact文件上传到Pact Broker
  3. 生产者从Broker下载Pact文件
  4. 生产者运行Pact验证测试
  5. 验证结果上传到Broker
  6. CI/CD中检查契约是否通过

契约测试 vs 集成测试:

  • 契约测试:快速、独立运行、关注接口兼容性
  • 集成测试:慢、需要完整环境、关注端到端功能

69. 🔴 如何实现微服务的动态路由?

答:动态路由是实现灰度发布、A/B测试、流量调度的基础。

路由维度:

  • 版本路由:根据服务版本路由(v1、v2)
  • 标签路由:根据服务标签路由(gray、stable)
  • 权重路由:按权重分配流量(v1:90%, v2:10%)
  • 条件路由:根据请求参数路由(userId % 100 < 10 → v2)
  • 地域路由:就近路由到同机房的服务

Dubbo路由规则:

1
2
3
4
# 标签路由
conditions:
- "tag = gray => tag = gray" # 灰度流量路由到灰度实例
- "=> tag = stable" # 其他流量路由到稳定实例

Istio流量管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
http:
- match:
- headers:
x-user-type:
exact: "vip"
route:
- destination:
host: order-service
subset: v2
- route:
- destination:
host: order-service
subset: v1
weight: 90
- destination:
host: order-service
subset: v2
weight: 10

动态路由的实现:

  • 路由规则存储在配置中心(Nacos/etcd)
  • 规则变更实时推送到所有服务实例
  • 服务框架在负载均衡前应用路由规则

70. ⚫ 如何从单体架构演进到微服务架构?

答:这是架构师最常面对的实际问题,考察渐进式演进的能力。

演进策略(绞杀者模式 Strangler Fig Pattern):

  1. 不要大爆炸重写:风险极高,失败率极高
  2. 渐进式迁移:新功能用微服务,旧功能逐步迁移

步骤:

  1. 梳理边界:用DDD识别限界上下文
  2. 建立基础设施:注册中心、配置中心、网关、监控
  3. 抽取第一个服务:选择耦合度低、变更频繁的模块
  4. 建立防腐层:新服务与单体之间通过防腐层通信
  5. 数据迁移:将新服务的数据从单体数据库迁移出来
  6. 流量切换:逐步将流量从单体切换到新服务
  7. 重复迭代:继续抽取下一个服务

选择第一个服务的标准:

  • 业务边界清晰
  • 与其他模块耦合度低
  • 变更频率高(收益大)
  • 团队熟悉(降低风险)

数据迁移策略:

  • 双写:同时写入新旧数据库,逐步切换读
  • CDC同步:通过binlog同步数据到新数据库
  • 影子读:读请求同时发到新旧数据库,对比结果

常见陷阱:

  • 分布式单体:拆分了服务但没有拆分数据库
  • 过度拆分:一开始就拆太细
  • 忽视运维:没有准备好监控、日志、CI/CD就开始拆分

五、系统设计实战(71-100题)

71. ⚫ 如何设计一个分布式文件存储系统?

答:分布式文件存储是大规模系统的基础设施。

核心设计:

  1. 元数据管理

    • 文件名 → 文件块的映射关系
    • 集中式(HDFS NameNode)或分布式(Ceph CRUSH算法)
    • 元数据高可用:主从复制 + 自动切换
  2. 数据分片

    • 大文件切分为固定大小的块(如64MB/128MB)
    • 每个块存储在不同的节点上
    • 支持并行读写
  3. 副本策略

    • 默认3副本(不同机架、不同机房)
    • 副本放置策略:一个本地、一个同机架、一个跨机架
    • 副本一致性:链式复制或并行复制
  4. 故障恢复

    • 心跳检测节点存活
    • 节点故障后自动复制副本到其他节点
    • 数据校验(Checksum)防止静默数据损坏

方案对比:

方案 特点 适用场景
HDFS 大文件、批处理 大数据分析
Ceph 统一存储(块/文件/对象) 云平台
MinIO S3兼容、轻量级 对象存储
FastDFS 轻量级、适合小文件 图片/视频存储

72. ⚫ 如何设计一个实时搜索系统?

答:搜索系统是互联网应用的核心基础设施。

架构设计:

  1. 数据采集

    • 全量索引:定时从数据库全量构建索引
    • 增量索引:监听binlog(Canal),实时更新索引
    • 双写:业务代码同时写数据库和搜索引擎(不推荐,一致性难保证)
  2. 索引构建

    • 分词:中文分词(IK Analyzer、jieba)
    • 倒排索引:词 → 文档列表
    • 正排索引:文档 → 字段值(用于排序和聚合)
  3. 查询处理

    • 查询解析:分词、同义词扩展、纠错
    • 召回:从倒排索引中检索候选文档
    • 排序:相关性评分(BM25)+ 业务评分(销量、评分)
    • 过滤:价格范围、品类、品牌等条件过滤
  4. 性能优化

    • 索引分片:数据分布到多个节点
    • 查询缓存:热门查询结果缓存
    • 预计算:热门聚合结果预计算

Elasticsearch集群设计:

  • Master节点:集群管理(3个,不存数据)
  • Data节点:存储数据和执行查询
  • Coordinating节点:请求路由和结果聚合
  • 分片数 = 数据量 / 单分片大小(建议30-50GB/分片)

73. ⚫ 如何设计一个延迟消息系统?

答:延迟消息在订单超时取消、定时提醒等场景中广泛使用。

实现方案:

  1. RocketMQ延迟消息

    • 支持固定延迟级别(1s, 5s, 10s, 30s, 1m, …)
    • 5.0版本支持任意时间延迟
    • 实现简单,但延迟级别有限
  2. Redis实现

    • Sorted Set:score为执行时间戳
    • 定时任务轮询:ZRANGEBYSCORE key 0 now
    • 优点:精度高,支持任意延迟
    • 缺点:数据量大时内存压力
  3. 时间轮(Timing Wheel)

    • Kafka、Netty使用的延迟任务方案
    • 类似钟表,每个槽位对应一个时间刻度
    • 多层时间轮支持长延迟
    • 优点:O(1)的插入和删除
  4. 数据库轮询

    • 定时扫描数据库中到期的记录
    • 简单但性能差,适合小规模
  5. Pulsar延迟消息

    • 原生支持任意精度的延迟消息
    • 基于时间分区实现

订单超时取消的最佳实践:

  • 创建订单时发送延迟消息(如30分钟)
  • 消息到期后检查订单状态
  • 如果未支付则取消订单
  • 注意幂等:订单可能已被用户取消或已支付

74. ⚫ 如何设计一个高并发的计数系统(如点赞数、阅读数)?

答:计数系统看似简单,但在高并发下面临严峻挑战。

挑战:

  • 高并发写入(热门内容每秒数万次点赞)
  • 高并发读取(每次展示都需要读取计数)
  • 数据准确性(不能丢失计数)

架构设计:

  1. 写入路径

    • 用户点赞 → Redis INCR(原子操作,高性能)
    • 异步同步到数据库(定时批量写入)
    • 去重:Redis Set或布隆过滤器(防止重复点赞)
  2. 读取路径

    • 优先从Redis读取(毫秒级响应)
    • Redis不可用时从数据库读取
    • 本地缓存热门内容的计数
  3. 持久化

    • 定时将Redis中的计数同步到数据库
    • 使用消息队列异步写入(削峰)
    • 批量合并写入(减少数据库压力)

精确计数 vs 近似计数:

  • 精确计数:Redis INCR + 去重(适合点赞、收藏)
  • 近似计数:HyperLogLog(适合UV统计,误差约0.81%)
  • 近似计数:计数器+随机采样(适合阅读数,允许小误差)

大V问题:

  • 热门内容的计数器是热点key
  • 解决:分片计数(将一个key拆分为多个子key,读取时求和)
  • 如:like_count:{contentId}:{0-9},写入时随机选择一个子key

75. ⚫ 如何设计一个分布式ID生成系统?

答:全局唯一ID是分布式系统的基础需求。

方案对比:

  1. UUID

    • 128位,全局唯一
    • 无序,不适合做数据库主键(B+树频繁分裂)
    • 太长,存储和索引开销大
  2. 数据库自增

    • 简单可靠
    • 单点瓶颈,性能有限
    • 多库时需要设置不同步长
  3. Snowflake(雪花算法)

    • 64位:1位符号 + 41位时间戳 + 10位机器ID + 12位序列号
    • 有序、高性能(单机每秒400万+)
    • 依赖时钟,时钟回拨会导致ID重复
  4. Leaf(美团)

    • Segment模式:数据库号段,批量获取ID
    • Snowflake模式:改进的雪花算法,解决时钟回拨
    • 双Buffer:异步预加载下一个号段
  5. Tinyid(滴滴)

    • 类似Leaf Segment模式
    • 支持多DB,高可用

时钟回拨解决方案:

  • 等待:时钟追上后继续生成
  • 使用上次的时间戳 + 序列号递增
  • 备用机器ID:切换到备用的机器ID
  • NTP配置:禁止大幅度时钟调整

选择建议:

  • 简单场景:数据库自增
  • 高性能场景:Snowflake或Leaf
  • 无序可接受:UUID v7(时间有序的UUID)

76. ⚫ 如何设计一个通知系统(站内信、Push、短信、邮件)?

答:通知系统是用户触达的核心基础设施。

架构设计:

  1. 统一接入层

    • 提供统一的发送API
    • 支持多种通知渠道(站内信、Push、短信、邮件、微信)
    • 模板管理:通知内容模板化,支持变量替换
  2. 路由层

    • 根据通知类型和用户偏好选择渠道
    • 降级策略:Push失败降级为短信
    • 频率控制:防止骚扰用户(如每天最多5条营销短信)
  3. 渠道适配层

    • 每个渠道一个适配器(短信:阿里云/腾讯云、Push:APNs/FCM)
    • 多供应商:主供应商故障自动切换到备用供应商
    • 异步发送:通过消息队列解耦
  4. 存储层

    • 站内信:数据库存储(用户维度分表)
    • 发送记录:用于审计和统计
    • 已读状态:Redis BitMap(高效存储已读/未读)

关键设计:

  • 幂等发送:相同的通知不重复发送
  • 批量发送:支持百万级用户的批量通知
  • 优先级队列:紧急通知(验证码)优先于营销通知
  • 送达率监控:监控各渠道的送达率和失败率

77. ⚫ 如何设计一个电商的库存系统?

答:库存系统是电商的核心,直接影响超卖和用户体验。

库存模型:

  • 可售库存:可以售卖的数量
  • 锁定库存:已下单未支付的数量
  • 实际库存:仓库中的实际数量
  • 关系:可售库存 = 实际库存 - 锁定库存

扣减方案:

  1. 下单减库存

    • 下单时立即扣减库存
    • 优点:不会超卖
    • 缺点:恶意下单占库存(需要超时释放)
  2. 支付减库存

    • 支付成功后扣减库存
    • 优点:不会被恶意占库存
    • 缺点:可能超卖(多人同时下单)
  3. 预扣库存(推荐)

    • 下单时锁定库存(预扣)
    • 支付成功后确认扣减
    • 超时未支付则释放锁定库存

高并发扣减:

  • Redis预扣库存:DECR stock:{skuId},原子操作
  • 库存为0后内存标记,直接拒绝(不再查Redis)
  • 异步同步到数据库:消息队列 + 批量更新
  • 数据库兜底:UPDATE SET stock=stock-1 WHERE sku_id=? AND stock>0

分仓库存:

  • 多个仓库各自维护库存
  • 就近发货:根据用户地址选择最近的仓库
  • 库存调拨:仓库间库存转移

78. ⚫ 如何设计一个支付系统?

答:支付系统是金融级系统,对安全性和一致性要求极高。

核心模块:

  1. 收银台:统一的支付入口,展示可用支付方式
  2. 支付网关:路由到不同的支付渠道(微信、支付宝、银行卡)
  3. 支付核心:支付流水管理、状态机、幂等控制
  4. 渠道适配:对接各支付渠道的API
  5. 对账系统:与支付渠道对账,发现差异
  6. 清结算:资金清算和结算

支付流程:

  1. 用户选择支付方式,创建支付单
  2. 调用支付渠道API,获取支付凭证
  3. 用户完成支付(跳转/扫码)
  4. 接收支付渠道的异步通知
  5. 更新支付状态,通知业务系统

关键设计:

  • 幂等性:支付流水号唯一,防止重复支付
  • 状态机:严格的状态转换(待支付→支付中→已支付/已失败)
  • 对账:每日与支付渠道对账,发现长款/短款
  • 资金安全:所有资金操作记录流水,可追溯
  • 异步通知:支付结果通过MQ通知业务系统
  • 超时处理:支付超时自动关闭,释放库存

掉单处理:

  • 用户支付成功但系统未收到通知
  • 方案:定时查询支付渠道的支付状态(补单)

79. ⚫ 如何设计一个高性能的网关系统?

答:网关是所有流量的入口,性能直接影响整个系统。

高性能设计:

  1. 异步非阻塞

    • 基于Netty/OpenResty的事件驱动模型
    • 少量线程处理大量并发连接
    • 避免阻塞操作(同步IO、锁等待)
  2. 连接池复用

    • 与后端服务保持长连接
    • HTTP/2多路复用
    • 连接池大小根据后端服务能力配置
  3. 缓存

    • 路由规则缓存(避免每次查询配置中心)
    • 认证结果缓存(JWT验证结果短期缓存)
    • 响应缓存(GET请求的响应缓存)
  4. 插件链优化

    • 插件按需加载(不是所有请求都需要所有插件)
    • 插件执行顺序优化(先执行快速失败的插件)
    • 避免在插件中做重计算
  5. 零拷贝

    • 请求/响应体直接转发,不做不必要的序列化/反序列化
    • 使用DirectBuffer减少内存拷贝

性能指标参考:

  • Nginx:单机10万+ QPS
  • APISIX:单机数万QPS(带插件)
  • Spring Cloud Gateway:单机数千QPS
  • Envoy:单机数万QPS

80. ⚫ 如何设计一个实时数据分析平台?

答:实时数据分析是数据驱动决策的基础。

Lambda架构:

  • 批处理层:Hadoop/Spark处理全量数据,生成批视图
  • 速度层:Flink/Storm处理实时数据,生成实时视图
  • 服务层:合并批视图和实时视图,提供查询
  • 缺点:维护两套代码(批处理和流处理)

Kappa架构:

  • 只有流处理层,所有数据通过流处理
  • Kafka作为数据源(保留足够长的数据)
  • 需要重新计算时,从Kafka重放数据
  • 优点:架构简单,只维护一套代码

实时数仓架构:

  1. 数据采集:Kafka接收业务数据(binlog/日志/埋点)
  2. 实时计算:Flink进行实时ETL和聚合计算
  3. 数据存储
    • 实时指标:ClickHouse/Doris(OLAP引擎)
    • 明细数据:Hudi/Iceberg(数据湖)
  4. 数据服务:提供查询API和可视化Dashboard

技术选型:

组件 方案
消息队列 Kafka
流计算 Flink
OLAP引擎 ClickHouse/Doris/StarRocks
数据湖 Hudi/Iceberg/Delta Lake
可视化 Grafana/Superset

81. 🔴 如何设计一个高可用的任务调度平台?

答:任务调度平台是企业级应用的核心基础设施。

核心需求:

  • 支持Cron表达式和固定频率调度
  • 分布式环境下任务不重复执行
  • 任务失败自动重试和告警
  • 支持任务依赖(DAG工作流)
  • 任务执行日志和监控

架构设计:

  1. 调度器(Scheduler)

    • 集群部署,通过选举产生Leader
    • Leader负责触发任务,Follower待命
    • 基于数据库锁或分布式锁保证单点触发
  2. 执行器(Executor)

    • 注册到调度器,接收任务
    • 支持多种执行模式:单机、广播、分片
    • 执行结果回调给调度器
  3. 任务编排(DAG)

    • 支持任务依赖关系
    • 上游任务完成后触发下游任务
    • 支持条件分支和并行执行
  4. 监控告警

    • 任务执行耗时、成功率监控
    • 任务超时告警
    • 任务积压告警

方案选择:

  • 简单定时任务:XXL-JOB
  • 复杂工作流:Apache Airflow、DolphinScheduler
  • 大数据场景:Oozie、Azkaban

82. 🔴 如何设计一个数据迁移方案?

答:数据迁移是系统升级和架构演进中的高风险操作。

迁移策略:

  1. 停机迁移

    • 停止服务 → 迁移数据 → 验证 → 启动服务
    • 最简单但需要停机窗口
    • 适合数据量小、允许停机的场景
  2. 双写迁移

    • 新旧系统同时写入
    • 全量迁移历史数据
    • 切换读到新系统
    • 停止写旧系统
  3. CDC迁移

    • 全量导出历史数据到新系统
    • 同时开启CDC(binlog监听)同步增量数据
    • 数据追平后切换流量

迁移步骤:

  1. 准备阶段:评估数据量、制定迁移计划、准备回滚方案
  2. 全量迁移:导出历史数据,转换格式,导入新系统
  3. 增量同步:CDC同步增量数据
  4. 数据校验:对比新旧系统数据一致性
  5. 流量切换:灰度切换读流量到新系统
  6. 清理收尾:确认无误后停止旧系统

数据校验方法:

  • 总量校验:对比记录数
  • 抽样校验:随机抽取记录对比
  • 全量校验:逐条对比(大数据量用MapReduce)
  • 业务校验:关键业务指标对比

83. 🔴 如何设计一个多语言/国际化系统?

答:国际化(i18n)是全球化产品的基础能力。

核心设计:

  1. 文案管理

    • 所有用户可见的文案抽取为资源文件
    • 支持多语言版本(zh-CN、en-US、ja-JP)
    • 翻译管理平台:支持翻译人员在线翻译和审核
  2. 时区处理

    • 服务端统一使用UTC时间存储
    • 客户端根据用户时区转换显示
    • 数据库使用TIMESTAMP WITH TIME ZONE
  3. 货币处理

    • 使用BigDecimal存储金额(避免浮点精度问题)
    • 存储原始货币和金额
    • 汇率转换在展示层处理
  4. 日期格式

    • 不同地区日期格式不同(MM/DD/YYYY vs DD/MM/YYYY)
    • 使用ICU库处理本地化格式
  5. 排序和搜索

    • 不同语言的排序规则不同(Collation)
    • 搜索需要支持多语言分词

技术实现:

  • 前端:i18next、vue-i18n、react-intl
  • 后端:Spring MessageSource、ResourceBundle
  • 数据库:UTF-8编码,支持多语言存储

84. 🔴 如何设计一个高并发的抽奖系统?

答:抽奖系统需要兼顾公平性、性能和防作弊。

核心设计:

  1. 奖品池管理

    • 奖品库存预加载到Redis
    • 原子扣减:Redis DECR
    • 库存为0后直接返回未中奖
  2. 抽奖算法

    • 概率抽奖:根据各奖品概率随机
    • 权重抽奖:按权重分配概率
    • 保底机制:N次未中奖后必中
  3. 防作弊

    • 用户级限流(每人每天最多N次)
    • IP限流
    • 设备指纹识别
    • 行为分析(异常频率检测)
  4. 异步发奖

    • 抽奖结果立即返回
    • 发奖通过消息队列异步处理
    • 发奖失败重试 + 人工补发

架构:

  • 接入层:Nginx限流 + 用户级限流
  • 服务层:Redis预减库存 + 概率计算
  • 异步层:MQ异步发奖
  • 数据层:中奖记录持久化

关键指标:

  • 抽奖接口RT < 50ms
  • 库存准确(不超发)
  • 中奖记录不丢失

85. 🔴 如何设计一个评论系统?

答:评论系统看似简单,但在大规模场景下有很多挑战。

数据模型:

  • 评论表:id, content_id, user_id, parent_id, root_id, content, create_time
  • parent_id:直接回复的评论ID(支持嵌套回复)
  • root_id:根评论ID(方便查询整个评论树)

存储方案:

  • 热门内容评论:Redis缓存(Sorted Set按时间/热度排序)
  • 全量评论:MySQL分表(按content_id分表)
  • 评论搜索:Elasticsearch

楼中楼设计:

  • 方案1:递归查询(简单但性能差)
  • 方案2:预计算(存储完整的评论树路径)
  • 方案3:两级展示(只展示根评论和直接回复,更多回复点击展开)

高并发优化:

  • 评论计数:Redis INCR,异步同步到数据库
  • 热门评论:预计算热门评论列表,缓存到Redis
  • 分页:游标分页(避免深分页问题)

内容安全:

  • 敏感词过滤(DFA算法/AC自动机)
  • 机器审核(AI内容审核)
  • 人工审核(机器审核不确定的内容)
  • 举报机制

86. 🔴 如何设计一个数据脱敏系统?

答:数据脱敏是数据安全和合规(GDPR、个人信息保护法)的基本要求。

脱敏类型:

  1. 静态脱敏:对存储的数据进行脱敏(如测试环境数据)
  2. 动态脱敏:查询时实时脱敏(如日志中的手机号)

脱敏规则:

  • 手机号:138****1234
  • 身份证:110***********1234
  • 银行卡:6222 **** **** 1234
  • 邮箱:a***@example.com
  • 姓名:张*

实现方案:

  1. 应用层脱敏

    • 自定义注解 + AOP拦截
    • Jackson序列化时脱敏
    • 优点:灵活,可按角色控制
    • 缺点:需要修改代码
  2. 数据库层脱敏

    • 数据库视图(脱敏视图)
    • PostgreSQL的列级安全策略
    • ShardingSphere的数据脱敏功能
  3. 网关层脱敏

    • 在API网关统一脱敏
    • 根据用户角色决定是否脱敏
    • 优点:统一管理,不侵入业务代码
  4. 日志脱敏

    • Logback/Log4j2自定义Layout
    • 正则匹配敏感信息并替换

87. ⚫ 如何设计一个工作流引擎?

答:工作流引擎是企业应用中审批、流程管理的核心。

核心概念:

  • 流程定义(Process Definition):流程模板(如请假审批流程)
  • 流程实例(Process Instance):一次具体的流程执行
  • 任务(Task):流程中的一个步骤(如主管审批)
  • 网关(Gateway):流程分支和合并(排他网关、并行网关)
  • 事件(Event):流程中的事件(开始、结束、定时器、消息)

核心设计:

  1. 流程引擎

    • 解析流程定义(BPMN 2.0标准)
    • 驱动流程实例的执行
    • 管理任务的创建、分配、完成
  2. 任务分配

    • 指定人:直接指定处理人
    • 角色:分配给某个角色的所有人
    • 规则:根据业务规则动态计算处理人
    • 抢占:多人可见,先抢先得
  3. 流程控制

    • 会签:所有人都审批通过才通过
    • 或签:任一人审批通过即通过
    • 加签:审批过程中临时增加审批人
    • 退回:退回到上一步或指定步骤
    • 撤回:发起人撤回流程

主流方案:

  • Flowable:Activiti的分支,功能最全
  • Camunda:轻量级,支持BPMN和DMN
  • 自研:简单场景可以用状态机实现

88. 🔴 如何设计一个高效的文件上传系统?

答:文件上传在大文件和高并发场景下需要特殊设计。

核心功能:

  1. 分片上传

    • 大文件切分为固定大小的分片(如5MB)
    • 每个分片独立上传
    • 所有分片上传完成后合并
    • 支持并行上传多个分片
  2. 断点续传

    • 记录已上传的分片信息
    • 网络中断后只上传未完成的分片
    • 服务端记录上传进度(Redis)
  3. 秒传

    • 上传前计算文件MD5/SHA256
    • 服务端检查是否已存在相同Hash的文件
    • 已存在则直接返回成功(不需要实际上传)
  4. 存储

    • 小文件:直接存储到对象存储(MinIO/S3)
    • 大文件:分片存储,元数据记录分片信息
    • CDN加速:上传完成后分发到CDN

上传流程:

  1. 客户端计算文件Hash,请求秒传检查
  2. 如果文件已存在,秒传成功
  3. 如果不存在,请求上传凭证(预签名URL)
  4. 客户端直传到对象存储(不经过应用服务器)
  5. 上传完成后回调应用服务器

89. 🔴 如何设计一个实时消息系统(IM)?

答:IM系统是技术复杂度最高的系统之一。

核心架构:

  1. 长连接层

    • WebSocket/TCP长连接
    • 连接管理:用户ID → 连接的映射
    • 心跳保活:定时心跳检测连接存活
    • 多端同步:同一用户多个设备同时在线
  2. 消息路由

    • 单聊:查找接收者的连接,直接推送
    • 群聊:查找群成员的连接,批量推送
    • 离线消息:接收者不在线时存储到离线消息队列
  3. 消息存储

    • 消息索引:用户维度的消息列表(收件箱模型)
    • 消息内容:独立存储,通过消息ID关联
    • 读扩散 vs 写扩散(类似Feed流的推拉模型)
  4. 消息可靠性

    • 消息ACK机制:客户端确认收到消息
    • 消息重试:未ACK的消息重新推送
    • 消息去重:客户端根据消息ID去重
    • 消息顺序:单聊保证有序(序列号递增)

关键技术:

  • 长连接管理:Netty
  • 消息队列:Kafka(高吞吐)
  • 消息存储:MySQL + Redis(最近消息缓存)
  • 在线状态:Redis(用户在线状态和连接信息)

90. ⚫ 如何设计一个推荐系统?

答:推荐系统是提升用户体验和商业价值的核心系统。

架构设计(召回→粗排→精排→重排):

  1. 召回层:从海量候选中快速筛选出千级候选集

    • 协同过滤:基于用户行为的相似度
    • 内容召回:基于物品特征的相似度
    • 热门召回:热门物品兜底
    • 向量召回:Embedding相似度(ANN检索)
  2. 粗排层:将千级候选缩减到百级

    • 轻量级模型快速打分
    • 过滤不合适的候选
  3. 精排层:精确排序

    • 深度学习模型(DeepFM、DIN、DIEN)
    • 特征工程:用户特征、物品特征、上下文特征
    • 实时特征:用户最近的行为
  4. 重排层:业务规则调整

    • 多样性:避免推荐结果过于单一
    • 去重:去除已曝光/已购买的物品
    • 运营干预:置顶、降权

实时推荐架构:

  • 用户行为 → Kafka → Flink实时计算 → 更新用户特征
  • 推荐请求 → 召回 → 排序 → 返回结果
  • 特征存储:Redis(实时特征)+ HBase(离线特征)

91. 🔴 如何设计一个AB测试平台?

答:AB测试是数据驱动决策的基础设施。

核心模块:

  1. 实验管理

    • 创建实验:定义实验名称、流量比例、实验参数
    • 实验分组:对照组和实验组
    • 实验生命周期:草稿→运行→结束→归档
  2. 流量分配

    • 基于用户ID的Hash分流(保证同一用户始终在同一组)
    • 支持多层实验(正交实验:不同层的实验互不影响)
    • 流量隔离:互斥实验不能分配到同一用户
  3. 数据收集

    • 埋点数据收集(曝光、点击、转化)
    • 实时数据处理(Flink)
    • 指标计算(转化率、留存率、收入等)
  4. 统计分析

    • 假设检验(t检验、卡方检验)
    • 置信区间计算
    • 统计显著性判断(p-value < 0.05)
    • 样本量计算(实验需要多少用户才有统计意义)

分流算法:

1
2
3
bucket = hash(userId + experimentId) % 1000
if bucket < 500: 对照组
else: 实验组

多层实验(正交):

  • 每层独立分流
  • 用户在不同层可以属于不同组
  • 层间实验互不干扰

92. 🔴 如何设计一个高可用的文件导出系统?

答:大数据量的文件导出是企业应用中的常见需求。

挑战:

  • 数据量大(百万行Excel)
  • 导出耗时长(分钟级)
  • 内存压力(大文件生成时的内存占用)
  • 并发导出(多用户同时导出)

架构设计:

  1. 异步导出

    • 用户提交导出请求 → 返回任务ID
    • 后台异步生成文件
    • 生成完成后通知用户下载
  2. 分片处理

    • 大数据量分页查询(游标分页)
    • 流式写入文件(不全部加载到内存)
    • EasyExcel:阿里开源,支持百万行Excel流式写入
  3. 文件存储

    • 生成的文件上传到对象存储(MinIO/S3)
    • 返回下载链接(预签名URL,有效期限制)
  4. 并发控制

    • 限制同一用户的并发导出数
    • 导出任务队列(消息队列)
    • 执行器集群消费任务
  5. 进度反馈

    • WebSocket推送导出进度
    • 或轮询查询导出状态

93. 🔴 如何设计一个配置驱动的规则引擎?

答:规则引擎将业务规则从代码中抽离,实现业务逻辑的动态配置。

应用场景:

  • 风控规则:交易金额>10000且新用户 → 人工审核
  • 营销规则:满200减30、新用户首单优惠
  • 路由规则:根据用户属性路由到不同服务

设计方案:

  1. 简单规则引擎

    • 条件-动作模型(if-then)
    • 规则存储在数据库或配置中心
    • 支持动态添加和修改规则
  2. 表达式引擎

    • 使用表达式语言(SpEL、MVEL、Aviator)
    • 规则以表达式形式存储
    • 运行时解析和执行表达式
  3. 决策表

    • 以表格形式定义规则
    • 行是条件组合,列是动作
    • 适合条件组合较多的场景
  4. 专业规则引擎

    • Drools:Java生态最成熟的规则引擎
    • Easy Rules:轻量级规则引擎
    • LiteFlow:国产规则编排引擎

性能优化:

  • 规则编译缓存(避免每次解析)
  • Rete算法(Drools使用,高效的模式匹配)
  • 规则优先级和短路评估

94. ⚫ 如何设计一个多数据中心的数据同步方案?

答:多数据中心数据同步是异地多活架构的核心挑战。

同步模式:

  1. 主从同步:一个数据中心为主,其他为从

    • 写入只在主中心
    • 从中心只读
    • 简单但主中心是单点
  2. 双向同步:两个数据中心互相同步

    • 两个中心都可以写入
    • 需要解决数据冲突
  3. 多主同步:多个数据中心都可以写入

    • 最复杂,冲突解决最困难

数据冲突解决:

  • Last Write Wins(LWW):最后写入的胜出(需要全局时钟)
  • 业务规则:根据业务逻辑决定(如金额取较大值)
  • CRDT:无冲突复制数据类型(数学保证最终一致)
  • 人工介入:冲突记录下来,人工处理

同步工具:

  • MySQL:Canal、DTS(阿里云)
  • PostgreSQL:Logical Replication、BDR
  • Redis:Redis Enterprise的Active-Active
  • Kafka:MirrorMaker 2

单元化避免冲突:

  • 用户按规则分配到固定的数据中心
  • 用户的所有数据只在一个数据中心写入
  • 跨中心只同步只读副本
  • 从根本上避免数据冲突

95. 🔴 如何设计一个高效的批处理系统?

答:批处理是企业应用中处理大量数据的核心能力。

核心设计:

  1. 分片并行

    • 将大任务拆分为多个小任务
    • 多个Worker并行处理
    • 分片策略:按ID范围、按Hash、按时间
  2. 流式处理

    • 不要一次加载所有数据到内存
    • 使用游标/流式查询
    • 边读边处理边写
  3. 断点续跑

    • 记录处理进度(已处理到哪条记录)
    • 失败后从断点继续
    • 每个分片独立记录进度
  4. 错误处理

    • 跳过错误记录,继续处理
    • 错误记录写入错误表,后续人工处理
    • 重试策略:可重试错误自动重试

Spring Batch架构:

  • Job:一个批处理任务
  • Step:Job中的一个步骤
  • ItemReader:读取数据
  • ItemProcessor:处理数据
  • ItemWriter:写入数据
  • Chunk:分块处理(读N条→处理→写入)

性能优化:

  • 批量写入(JDBC Batch Insert)
  • 多线程Step(并行处理多个Chunk)
  • 分区Step(将数据分区到多个Worker)
  • 异步ItemProcessor(处理逻辑异步执行)

96. 🔴 如何设计一个API限流和计费系统?

答:API限流和计费是开放平台的核心能力。

限流设计:

  1. 多维度限流

    • 用户级:每个用户每秒N次请求
    • API级:每个API每秒M次请求
    • 全局级:整个平台每秒K次请求
  2. 配额管理

    • 免费配额:每月1000次调用
    • 付费配额:按套餐分配(基础版10万次/月,专业版100万次/月)
    • 超额处理:拒绝/降速/按量计费
  3. 分布式限流

    • Redis + Lua脚本实现滑动窗口
    • 令牌桶算法(支持突发流量)
    • 集群限流:所有网关节点共享计数器

计费设计:

  1. 计量:记录每次API调用
  2. 计费:根据调用次数和套餐计算费用
  3. 账单:生成月度账单
  4. 扣费:从用户余额扣除或生成应收账款

技术实现:

  • 调用记录:Kafka异步写入(不影响API性能)
  • 实时计量:Flink实时聚合调用次数
  • 配额检查:Redis存储剩余配额,原子扣减
  • 账单生成:定时任务月度汇总

97. ⚫ 如何设计一个多租户的数据隔离和资源隔离方案?

答:这是SaaS架构中最核心的设计决策。

数据隔离方案选择:

方案 隔离性 成本 运维复杂度 适用场景
独立数据库 最高 最高 大客户、合规要求
共享数据库独立Schema 中型客户
共享表+tenant_id 最低 小客户、标准化产品

混合方案(推荐):

  • 大客户:独立数据库(满足合规和性能要求)
  • 中型客户:独立Schema
  • 小客户:共享表

资源隔离:

  1. 计算隔离

    • 独立集群:大客户独享计算资源
    • 共享集群 + 资源配额:Kubernetes ResourceQuota
    • 优先级队列:VIP客户请求优先处理
  2. 存储隔离

    • 独立存储卷
    • 存储配额限制
    • 备份策略按租户定制
  3. 网络隔离

    • Kubernetes NetworkPolicy
    • 独立VPC(大客户)
    • 流量限制

噪声邻居问题:

  • 一个租户的高负载影响其他租户
  • 解决:资源配额 + 限流 + 弹性伸缩

98. ⚫ 如何设计一个全局事务协调器?

答:全局事务协调器是分布式事务的核心基础设施。

核心设计(以Seata为参考):

  1. TC(Transaction Coordinator):事务协调器

    • 维护全局事务和分支事务的状态
    • 驱动全局提交或回滚
    • 高可用:集群部署,数据持久化到数据库
  2. TM(Transaction Manager):事务管理器

    • 定义全局事务的边界
    • 开始全局事务、提交或回滚
  3. RM(Resource Manager):资源管理器

    • 管理分支事务的资源
    • 向TC注册分支事务
    • 执行分支提交或回滚

Seata的四种模式:

  1. AT模式:自动补偿,基于undo_log

    • 一阶段:执行SQL,记录undo_log
    • 二阶段提交:删除undo_log
    • 二阶段回滚:根据undo_log反向补偿
    • 优点:无侵入
    • 缺点:需要全局锁,性能有损
  2. TCC模式:手动补偿

  3. SAGA模式:长事务

  4. XA模式:数据库XA协议

高可用设计:

  • TC集群部署(3节点以上)
  • 事务日志持久化(MySQL/Redis)
  • TC故障后,新TC接管未完成的事务
  • 超时机制:事务超时自动回滚

99. ⚫ 如何设计一个可扩展的插件化架构?

答:插件化架构是实现系统可扩展性的核心模式。

核心设计:

  1. 插件接口(SPI)

    • 定义标准的插件接口
    • 插件实现接口,提供具体功能
    • Java SPI、Spring的@Component扫描
  2. 插件生命周期

    • 加载:发现并加载插件
    • 初始化:插件初始化资源
    • 启动:插件开始工作
    • 停止:插件停止工作
    • 卸载:释放资源
  3. 插件隔离

    • 类加载器隔离:每个插件使用独立的ClassLoader
    • 避免插件之间的类冲突
    • OSGi、SOFAArk、PF4J
  4. 插件通信

    • 事件总线:插件之间通过事件通信
    • 服务注册:插件注册服务,其他插件调用

应用案例:

  • IDEA插件:基于Extension Point机制
  • Dubbo SPI:增强的Java SPI,支持自适应扩展
  • Spring Boot Starter:自动配置机制
  • Webpack插件:Tapable事件机制

设计原则:

  • 开闭原则:对扩展开放,对修改关闭
  • 插件不应该影响核心系统的稳定性
  • 插件故障隔离:一个插件崩溃不影响其他插件
  • 热插拔:支持运行时加载和卸载插件

100. ⚫ 如何设计一个高性能的网络通信框架?

答:网络通信框架是中间件和分布式系统的基石。

核心设计:

  1. IO模型

    • BIO:一个连接一个线程,不适合高并发
    • NIO:多路复用(select/poll/epoll),少量线程处理大量连接
    • AIO:异步IO,操作系统完成IO后通知应用
  2. 线程模型(Reactor模式)

    • 单Reactor单线程:Redis的模型
    • 单Reactor多线程:一个线程接收连接,多个线程处理业务
    • 主从Reactor:主Reactor接收连接,从Reactor处理IO(Netty默认)
  3. 协议设计

    • 魔数:标识协议(如0xCAFEBABE)
    • 版本号:协议版本
    • 消息类型:请求/响应/心跳
    • 序列化方式:JSON/Protobuf/Hessian
    • 消息长度:解决粘包/拆包
    • 消息体:实际数据
  4. 序列化

    • JSON:可读性好,性能一般
    • Protobuf:高性能,强类型,跨语言
    • Hessian:Java生态,Dubbo默认
    • Kryo:Java高性能序列化
  5. 连接管理

    • 连接池:复用连接,减少建连开销
    • 心跳保活:定时发送心跳检测连接存活
    • 空闲检测:关闭长时间空闲的连接
    • 优雅关闭:关闭前处理完已有请求

六、架构哲学与软技能(101-120题)

101. ⚫ 什么是架构决策记录(ADR)?为什么重要?

答:ADR(Architecture Decision Record)是记录架构决策的轻量级文档。

ADR模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ADR-001: 选择Kafka作为消息队列

## 状态:已接受

## 背景
系统需要一个消息队列来解耦服务间通信,日消息量预计1亿条。

## 决策
选择Kafka作为消息队列。

## 理由
- 高吞吐量满足日亿级消息需求
- 团队有Kafka运维经验
- 支持消息回溯(业务需要)

## 备选方案
- RocketMQ:功能更丰富但团队不熟悉
- Pulsar:架构更先进但生态不够成熟

## 后果
- 需要维护Kafka集群(3 Broker + 3 ZooKeeper)
- 事务消息需要额外方案(Kafka事务消息有限制)

为什么重要:

  • 记录”为什么”而不仅仅是”是什么”
  • 新成员可以理解历史决策的背景
  • 避免重复讨论已经做过的决策
  • 决策可追溯,方便后续review

102. ⚫ 你如何评估一个技术方案的好坏?

答:技术方案评估需要多维度权衡。

评估维度:

  1. 功能满足度:能否满足当前和可预见的未来需求
  2. 性能:延迟、吞吐量、资源消耗
  3. 可靠性:故障率、恢复时间、数据安全
  4. 可扩展性:能否应对业务增长
  5. 可维护性:代码复杂度、文档、团队理解成本
  6. 成本:开发成本、运维成本、基础设施成本
  7. 团队匹配度:团队是否有能力实施和维护
  8. 风险:技术风险、时间风险、人员风险
  9. 生态成熟度:社区活跃度、文档质量、商业支持

评估方法:

  • 加权评分法:每个维度打分,按权重计算总分
  • POC验证:关键技术点做原型验证
  • 压测验证:性能相关的方案必须压测
  • 同行评审:邀请其他架构师review

常见误区:

  • 只看技术先进性,忽视团队能力
  • 过度设计,为未来不确定的需求买单
  • 跟风选型,没有结合自身场景
  • 忽视运维成本(选型时只看开发成本)

103. ⚫ 什么是技术债务?如何管理技术债务?

答:技术债务是为了短期速度而牺牲长期质量的技术决策。

技术债务类型:

  1. 有意的:明知有更好的方案,但为了赶工期选择了快速方案
  2. 无意的:当时不知道有更好的方案,后来发现了
  3. 过时的:当时是好方案,但技术演进后变成了债务

技术债务的影响:

  • 开发速度越来越慢
  • Bug越来越多
  • 新人上手越来越难
  • 系统越来越脆弱

管理策略:

  1. 可视化:将技术债务记录在看板上(与业务需求同等对待)
  2. 量化:评估每项债务的影响和修复成本
  3. 优先级:按影响/成本比排序
  4. 持续偿还:每个迭代分配20%的时间偿还技术债务
  5. 预防:代码review、架构守护、自动化测试

何时可以接受技术债务:

  • 验证商业假设(MVP阶段)
  • 紧急修复(先止血再优化)
  • 明确的偿还计划(不是无限期拖延)

何时必须偿还:

  • 影响开发效率(每次修改都很痛苦)
  • 影响系统稳定性(频繁出故障)
  • 影响安全性(存在安全漏洞)

104. ⚫ 架构师如何做技术选型?

答:技术选型是架构师最重要的决策之一。

选型流程:

  1. 明确需求:功能需求、非功能需求(性能、可用性、安全性)
  2. 候选方案:列出所有可选方案(至少3个)
  3. 评估对比:多维度对比(功能、性能、成本、团队、生态)
  4. POC验证:关键方案做原型验证
  5. 决策记录:记录决策过程和理由(ADR)
  6. 持续评估:定期review选型是否仍然合适

选型原则:

  • 合适优于先进:选择最适合当前场景的,不是最新最酷的
  • 成熟优于新颖:生产环境优先选择经过验证的技术
  • 团队优于技术:团队能驾驭的技术优于理论上更好的技术
  • 简单优于复杂:能用简单方案解决的不要用复杂方案
  • 社区优于自研:有成熟开源方案的不要自研

反模式:

  • 简历驱动开发(为了简历好看选择新技术)
  • 锤子效应(手里有锤子看什么都是钉子)
  • 从众心理(别人用什么我就用什么)
  • 过度自研(什么都想自己造轮子)

105. ⚫ 如何做架构评审?评审的关键点是什么?

答:架构评审是保证架构质量的重要机制。

评审内容:

  1. 需求理解:是否正确理解了业务需求
  2. 方案合理性:方案是否能满足需求,是否过度设计
  3. 技术选型:选型是否合理,是否有更好的选择
  4. 性能设计:是否考虑了性能瓶颈和优化方案
  5. 高可用设计:是否有单点故障,故障恢复方案
  6. 安全设计:是否考虑了安全威胁和防护措施
  7. 可扩展性:是否能应对业务增长
  8. 兼容性:是否与现有系统兼容,迁移方案
  9. 成本评估:开发成本、运维成本、基础设施成本
  10. 风险评估:技术风险、时间风险、人员风险

评审流程:

  1. 方案提交(提前发给评审人员)
  2. 方案宣讲(15-30分钟)
  3. 提问讨论(30-60分钟)
  4. 结论:通过/有条件通过/不通过
  5. 跟踪:有条件通过的需要跟踪改进

评审技巧:

  • 关注”为什么”而不是”是什么”
  • 追问边界情况和异常场景
  • 关注方案的权衡(trade-off)
  • 不要纠结于细节,关注架构层面的问题

106. ⚫ 康威定律是什么?如何影响架构设计?

答:康威定律是软件架构与组织结构关系的经典理论。

康威定律:

设计系统的组织,其产生的设计等同于组织之间的沟通结构。

简单说:系统架构 = 组织架构的映射。

实际影响:

  • 3个团队开发一个编译器 → 编译器会有3个阶段
  • 前后端分离的团队 → 系统会有前后端分离的架构
  • 按业务线划分的团队 → 系统会按业务线拆分微服务

逆康威定律(Inverse Conway Maneuver):

  • 如果想要某种架构,先调整组织结构
  • 想要微服务架构 → 先按业务域划分团队
  • 想要平台化 → 先建立平台团队

团队拓扑(Team Topologies):

  1. 流对齐团队(Stream-aligned):负责一个业务流的端到端交付
  2. 平台团队(Platform):提供内部平台和工具
  3. 赋能团队(Enabling):帮助其他团队提升能力
  4. 复杂子系统团队(Complicated Subsystem):负责复杂的技术子系统

架构师的角色:

  • 不仅设计技术架构,还要影响组织架构
  • 确保组织结构与目标架构匹配
  • 推动跨团队的技术标准和规范

107. ⚫ 什么是演进式架构?如何实践?

答:演进式架构是指能够支持增量变化的架构。

核心理念:

  • 架构不是一次性设计好的,而是持续演进的
  • 拥抱变化,而不是抵抗变化
  • 做出可逆的决策,推迟不可逆的决策

关键实践:

  1. 适应度函数(Fitness Function)

    • 定义架构的质量指标
    • 自动化验证架构是否符合预期
    • 如:模块间依赖不能有循环、API响应时间<200ms
  2. 架构守护

    • ArchUnit:Java架构规则测试
    • 依赖检查:防止不允许的依赖关系
    • API兼容性检查:防止破坏性变更
  3. 增量变更

    • 小步迭代,每次只改一小部分
    • 特性开关(Feature Toggle)控制新功能的发布
    • 蓝绿部署、金丝雀发布降低变更风险
  4. 可逆性

    • 优先选择可逆的决策
    • 不可逆的决策要更加谨慎
    • 数据库Schema变更要向后兼容

ArchUnit示例:

1
2
3
4
5
6
@Test
void domainShouldNotDependOnInfrastructure() {
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("..infrastructure..")
.check(importedClasses);
}

108. ⚫ 如何平衡系统的一致性和可用性?

答:一致性和可用性的权衡是架构设计中最核心的决策。

决策框架:

  1. 业务分析:这个数据/操作对一致性的要求有多高?

    • 资金相关:强一致性(不能多扣/少扣)
    • 库存相关:准强一致性(不能超卖,但可以少卖)
    • 统计相关:最终一致性(允许短暂不准确)
    • 展示相关:弱一致性(缓存延迟可接受)
  2. 成本分析:强一致性的代价是什么?

    • 性能下降(同步等待)
    • 可用性降低(部分节点故障时不可用)
    • 实现复杂度增加
  3. 折中方案

    • 核心路径强一致,非核心路径最终一致
    • 写入强一致,读取最终一致
    • 正常情况强一致,故障时降级为最终一致

实际案例:

  • 电商下单:库存扣减强一致(Redis原子操作),订单创建最终一致(MQ异步)
  • 银行转账:账户余额强一致(分布式事务),交易记录最终一致(异步写入)
  • 社交点赞:计数最终一致(异步聚合),去重强一致(Redis Set)

关键认知:

  • 没有银弹,只有权衡
  • 同一个系统的不同部分可以有不同的一致性级别
  • 一致性级别应该由业务需求决定,而不是技术偏好

109. ⚫ 如何做好架构的可观测性设计?

答:可观测性是理解系统内部状态的能力,是运维和排障的基础。

可观测性三支柱:

  1. 指标(Metrics):数值型的时间序列数据

    • 系统指标:CPU、内存、磁盘、网络
    • 应用指标:QPS、RT、错误率
    • 业务指标:订单量、支付成功率
    • 工具:Prometheus + Grafana
  2. 日志(Logs):离散的事件记录

    • 结构化日志(JSON格式)
    • 统一的日志格式和字段
    • 工具:ELK/EFK、Loki
  3. 链路追踪(Traces):请求在系统中的完整路径

    • 跨服务的调用链路
    • 每个Span的耗时和状态
    • 工具:Jaeger、SkyWalking

OpenTelemetry(统一标准):

  • 统一的API和SDK
  • 统一的数据格式(OTLP协议)
  • 支持Metrics、Logs、Traces
  • 厂商无关,可以对接任何后端

可观测性设计原则:

  • 从设计阶段就考虑可观测性(不是事后补充)
  • 关键业务路径必须有完整的追踪
  • 告警基于SLO,而不是基于资源指标
  • 建立Runbook:每个告警对应一个处理手册

110. ⚫ 架构师如何推动技术变革?

答:技术变革不仅是技术问题,更是组织和人的问题。

推动策略:

  1. 建立共识

    • 用数据说话(当前系统的问题、成本、风险)
    • 展示变革的收益(性能提升、成本降低、效率提高)
    • 获得管理层的支持
  2. 小步验证

    • 选择一个小项目做试点
    • 用成功案例证明方案可行
    • 逐步扩大范围
  3. 降低风险

    • 制定详细的迁移计划
    • 准备回滚方案
    • 灰度发布,逐步切换
  4. 赋能团队

    • 培训和分享
    • 编写文档和最佳实践
    • 建立内部社区
  5. 持续跟进

    • 定期review进展
    • 及时解决遇到的问题
    • 调整计划适应实际情况

常见阻力:

  • “现在的系统跑得好好的,为什么要改?”
  • “我们没有时间做这个”
  • “这个技术我们不熟悉”
  • “上次改造就失败了”

应对方式:

  • 用数据和案例说服,而不是用权威
  • 从痛点出发,解决实际问题
  • 提供培训和支持,降低学习成本
  • 承认风险,展示风险控制措施

111. ⚫ 什么是架构的简单性原则?如何在实践中应用?

答:简单性是好架构的核心特征。

KISS原则(Keep It Simple, Stupid):

  • 能用简单方案解决的不要用复杂方案
  • 复杂性是系统最大的敌人
  • 每增加一层抽象都有成本

过度设计的信号:

  • 为了”可能”的需求增加复杂度
  • 使用了团队不熟悉的技术
  • 系统中有大量未使用的抽象层
  • 新人需要很长时间才能理解系统

简单性实践:

  1. YAGNI(You Aren’t Gonna Need It):不要为未来不确定的需求设计
  2. 最少组件:能用一个组件解决的不要用两个
  3. 最少抽象:能直接实现的不要过度抽象
  4. 最少依赖:减少外部依赖,降低系统复杂度

案例:

  • 日活1000的系统不需要微服务架构
  • 数据量100万的表不需要分库分表
  • 单机能扛住的流量不需要分布式缓存
  • 简单的CRUD不需要DDD

架构师的价值不在于设计复杂的系统,而在于用最简单的方案解决复杂的问题。

112. ⚫ 如何设计系统的可测试性?

答:可测试性是软件质量的基础,也是持续交付的前提。

可测试性设计原则:

  1. 依赖注入:通过接口注入依赖,方便Mock
  2. 关注点分离:业务逻辑与基础设施分离
  3. 纯函数:相同输入总是产生相同输出,无副作用
  4. 小方法:每个方法只做一件事,容易测试

测试金字塔:

  • 单元测试(70%):测试单个类/方法,快速、稳定
  • 集成测试(20%):测试模块间的交互,如数据库、缓存
  • 端到端测试(10%):测试完整的业务流程

微服务测试策略:

  • 单元测试:测试业务逻辑
  • 组件测试:测试单个微服务(Mock外部依赖)
  • 契约测试:测试服务间的接口兼容性
  • 集成测试:测试服务间的实际交互
  • 端到端测试:测试完整的业务流程

提高可测试性的架构模式:

  • 六边形架构:核心逻辑不依赖外部,容易测试
  • CQRS:读写分离,各自独立测试
  • 事件驱动:通过事件验证行为

测试基础设施:

  • Testcontainers:用Docker容器运行测试依赖(数据库、Redis)
  • WireMock:Mock HTTP服务
  • Embedded Kafka:内嵌Kafka用于测试

113. ⚫ 如何做好系统的容量评估?

答:容量评估是保障系统稳定性的前置工作。

评估步骤:

  1. 业务量预估

    • 用户数增长预测
    • 核心业务量预测(订单量、消息量)
    • 峰值系数(日常的3-10倍)
  2. 流量模型

    • 读写比例(如读:写 = 10:1)
    • 请求大小(平均请求/响应大小)
    • 并发模型(同时在线用户数)
  3. 资源计算

    • CPU:QPS × 单请求CPU时间
    • 内存:并发连接数 × 单连接内存 + 缓存大小
    • 磁盘:数据量 × 副本数 × 增长系数
    • 网络:QPS × 请求大小 × 2(请求+响应)
  4. 压测验证

    • 单机压测:确定单实例的极限
    • 集群压测:验证集群的线性扩展能力
    • 全链路压测:验证整个系统的容量
  5. 冗余设计

    • 预留30-50%的冗余容量
    • 考虑故障场景(一个节点挂了,剩余节点能否承受)

容量计算示例:

  • 日活100万,每用户日均10次请求
  • 日请求量 = 1000万
  • 平均QPS = 1000万 / 86400 ≈ 116
  • 峰值QPS = 116 × 5 = 580
  • 单机QPS = 200(压测得出)
  • 所需实例 = 580 / 200 × 1.5(冗余)≈ 5台

114. ⚫ 如何处理遗留系统?

答:遗留系统是每个架构师都会面对的挑战。

评估遗留系统:

  1. 业务价值:系统是否仍然有业务价值?
  2. 技术状态:代码质量、技术栈、可维护性
  3. 风险:安全漏洞、性能瓶颈、单点故障
  4. 成本:维护成本、改造成本

处理策略:

  1. 维持现状:系统稳定且维护成本可接受
  2. 渐进改造:绞杀者模式,逐步替换
  3. 包装(Wrap):用API包装遗留系统,对外提供新接口
  4. 重写:完全重写(风险最高,慎用)

绞杀者模式实践:

  1. 在遗留系统前面加一个路由层(API Gateway)
  2. 新功能用新系统实现
  3. 旧功能逐步迁移到新系统
  4. 路由层逐步将流量切换到新系统
  5. 最终下线遗留系统

关键原则:

  • 不要大爆炸重写(失败率极高)
  • 先理解再改造(不理解的代码不要动)
  • 加测试再重构(没有测试的重构是冒险)
  • 保持系统始终可用(改造过程中不能停服)

115. ⚫ 架构师需要具备哪些软技能?

答:架构师不仅是技术专家,更是技术领导者。

核心软技能:

  1. 沟通能力

    • 能向非技术人员解释技术方案
    • 能倾听不同意见并整合
    • 能写清晰的技术文档
  2. 影响力

    • 不靠权威而靠专业能力影响团队
    • 能推动技术决策的落地
    • 能在组织中建立技术影响力
  3. 权衡能力

    • 在多个方案中做出合理选择
    • 理解每个决策的trade-off
    • 在理想和现实之间找到平衡
  4. 全局视野

    • 理解业务目标和技术目标的关系
    • 关注系统整体而不是局部
    • 考虑长期影响而不仅是短期效果
  5. 学习能力

    • 持续学习新技术和新理念
    • 从失败中学习
    • 保持技术敏感度
  6. 决策能力

    • 在信息不完整时做出决策
    • 区分可逆和不可逆的决策
    • 敢于承担决策的后果

116. ⚫ 什么是架构的适应度函数?如何定义?

答:适应度函数是衡量架构是否满足预期目标的自动化检查。

适应度函数类型:

  1. 原子适应度函数:检查单一维度

    • 代码覆盖率 > 80%
    • API响应时间P99 < 500ms
    • 循环依赖数 = 0
  2. 整体适应度函数:检查多个维度的组合

    • 可用性 > 99.9% AND 延迟P99 < 200ms
    • 安全扫描通过 AND 性能测试通过
  3. 触发式适应度函数:特定事件触发

    • 每次代码提交触发架构规则检查
    • 每次部署触发性能测试
  4. 持续式适应度函数:持续运行

    • 生产环境的SLO监控
    • 安全漏洞持续扫描

实现工具:

  • ArchUnit:Java架构规则测试
  • Fitness Function Runner:自定义适应度函数框架
  • SonarQube:代码质量检查
  • Prometheus + AlertManager:运行时指标监控

示例:

1
2
3
4
5
6
// 领域层不能依赖基础设施层
@ArchTest
static final ArchRule domainIndependence =
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat()
.resideInAnyPackage("..infrastructure..", "..application..");

117. ⚫ 如何设计一个高效的技术团队协作模式?

答:技术团队的协作模式直接影响交付效率和系统质量。

团队模式:

  1. 特性团队(Feature Team)

    • 跨职能团队,端到端负责一个业务特性
    • 包含前端、后端、测试、运维
    • 优点:交付速度快,沟通成本低
    • 缺点:技术深度可能不够
  2. 组件团队(Component Team)

    • 按技术组件划分(前端团队、后端团队、DBA团队)
    • 优点:技术深度好
    • 缺点:跨团队协调成本高
  3. 平台团队 + 业务团队

    • 平台团队提供基础设施和工具
    • 业务团队专注业务开发
    • 平台即产品(Platform as a Product)

内部开源模式:

  • 核心代码由负责团队维护
  • 其他团队可以提交PR
  • 代码review后合并
  • 降低跨团队协调成本

架构师在团队中的角色:

  • 技术方向的引导者(不是独裁者)
  • 技术标准的制定者和守护者
  • 团队能力的提升者
  • 跨团队协调的桥梁

118. ⚫ 如何做好系统的安全架构设计?

答:安全是架构设计中不可忽视的维度。

安全架构层次:

  1. 网络安全

    • 防火墙、WAF(Web应用防火墙)
    • DDoS防护
    • 网络隔离(VPC、安全组)
  2. 应用安全

    • 输入验证(防SQL注入、XSS)
    • 认证授权(OAuth、JWT、RBAC)
    • 加密(传输TLS、存储AES)
    • 安全编码规范
  3. 数据安全

    • 数据分类分级
    • 敏感数据加密存储
    • 数据脱敏
    • 数据备份和恢复
  4. 运维安全

    • 最小权限原则
    • 操作审计
    • 密钥管理(Vault)
    • 安全扫描(SAST/DAST)

OWASP Top 10防护:

  • SQL注入:参数化查询、ORM
  • XSS:输出编码、CSP
  • CSRF:Token验证
  • 敏感数据泄露:加密、脱敏
  • 认证失效:多因素认证、Session管理

安全左移(Shift Left Security):

  • 在开发阶段就考虑安全
  • 代码review包含安全检查
  • CI/CD集成安全扫描
  • 安全培训和意识提升

119. ⚫ 如何评估和管理架构风险?

答:风险管理是架构师的核心职责之一。

风险识别:

  1. 技术风险:新技术不成熟、性能不达标、兼容性问题
  2. 架构风险:单点故障、扩展性不足、安全漏洞
  3. 人员风险:关键人员离职、团队能力不足
  4. 进度风险:需求变更、技术难点、依赖延迟
  5. 运维风险:部署复杂、监控不足、故障恢复慢

风险评估矩阵:

概率\影响 低影响 中影响 高影响
高概率 中风险 高风险 极高风险
中概率 低风险 中风险 高风险
低概率 低风险 低风险 中风险

风险应对策略:

  1. 规避:改变方案,避免风险发生
  2. 缓解:降低风险的概率或影响
  3. 转移:将风险转移给第三方(如使用云服务)
  4. 接受:风险可控,接受并准备应急方案

实践建议:

  • 项目启动时做风险评估
  • 定期review风险清单
  • 为高风险项准备Plan B
  • 关键技术点做POC验证

120. ⚫ 你认为好的架构应该具备哪些特征?

答:这是一个开放性问题,考察架构师的架构哲学。

好架构的特征:

  1. 简单:能用简单方案解决的不要复杂化。复杂性是系统最大的敌人
  2. 演进:支持增量变化,不需要大规模重写。拥抱变化而不是抵抗变化
  3. 弹性:能够优雅地处理故障,而不是脆弱地崩溃
  4. 可观测:能够理解系统内部的状态,快速定位问题
  5. 可测试:容易编写和运行测试,保证质量
  6. 松耦合:模块之间依赖最小,可以独立变更和部署
  7. 高内聚:相关的功能聚集在一起,职责清晰
  8. 适合团队:团队能够理解、实施和维护

架构的本质:

  • 架构是关于权衡的艺术,不是追求完美的科学
  • 没有最好的架构,只有最合适的架构
  • 架构决策要考虑当前的约束(团队、时间、成本)
  • 好的架构师知道什么时候该做什么,什么时候不该做什么

我的架构原则:

  • 先让它工作,再让它正确,最后让它快
  • 推迟决策到最后责任时刻
  • 优先选择可逆的决策
  • 用最简单的方案解决问题
  • 架构服务于业务,而不是相反