架构设计 - 架构师面试题库
覆盖系统设计、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、微服务 |
最终一致性的实现方式:
- 读时修复:读取时发现不一致则修复(Cassandra的Read Repair)
- 写时修复:写入时检测冲突并修复
- 异步修复:后台定时任务对比修复(Anti-Entropy)
- 版本向量:通过版本号检测和解决冲突
3. 🔴 分布式事务有哪些解决方案?各自的优缺点?
答:分布式事务是分布式系统中最复杂的问题之一。
方案对比:
2PC(两阶段提交):
- 协调者发起prepare → 参与者响应 → 协调者发起commit/rollback
- 优点:强一致性
- 缺点:同步阻塞、单点故障(协调者)、数据不一致风险(网络分区时)
3PC(三阶段提交):
- 在2PC基础上增加CanCommit阶段和超时机制
- 减少阻塞,但仍不能完全解决一致性问题
- 实际很少使用
TCC(Try-Confirm-Cancel):
- Try:预留资源。Confirm:确认执行。Cancel:取消释放
- 优点:性能好(无全局锁)
- 缺点:业务侵入大,需要实现三个接口,幂等性要求高
SAGA:
- 将长事务拆分为多个本地事务,每个事务有对应的补偿操作
- 正向执行失败时,逆序执行补偿操作
- 优点:无锁,性能好
- 缺点:最终一致性,补偿逻辑复杂
本地消息表:
- 业务操作和消息写入在同一个本地事务中
- 定时任务扫描消息表发送消息
- 消费者处理消息并确认
- 优点:简单可靠
- 缺点:有延迟,需要定时任务
事务消息(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. 🔵 什么是幂等性?如何在分布式系统中保证幂等?
答:幂等性是指同一操作执行多次与执行一次的效果相同。
为什么需要幂等:
- 网络超时重试
- 消息队列重复消费
- 用户重复点击
- 服务调用重试
实现方案:
- 唯一ID + 去重表:每个请求携带唯一ID,处理前检查是否已处理
- 数据库唯一约束:利用UNIQUE KEY防止重复插入
- 乐观锁(版本号):
UPDATE SET version=version+1 WHERE version=old_version - 状态机:只允许合法的状态转换(如订单:待支付→已支付,不能重复支付)
- Token机制:服务端生成Token,客户端携带Token请求,Token只能使用一次
- Redis SETNX:利用Redis的原子操作实现分布式去重
设计原则:
- 查询操作天然幂等
- 删除操作天然幂等(删除不存在的资源不报错)
- 创建操作需要去重
- 更新操作需要版本控制
6. 🔴 一致性Hash算法的原理是什么?如何解决数据倾斜?
答:一致性Hash是分布式系统中数据分片的核心算法。
原理:
- 将哈希值空间组织成一个环(0到2^32-1)
- 将节点映射到环上(对节点IP/名称取哈希)
- 数据的key映射到环上,顺时针找到的第一个节点就是负责节点
- 节点增减时,只影响相邻节点的数据,迁移量最小
数据倾斜问题:
- 节点少时,哈希环上的节点分布不均匀
- 某些节点承担更多数据
解决方案——虚拟节点:
- 每个物理节点映射多个虚拟节点到环上(如每个节点150-200个虚拟节点)
- 虚拟节点越多,数据分布越均匀
- 虚拟节点到物理节点有映射关系
应用场景:
- 分布式缓存(Memcached、Redis Cluster的slot是类似思想)
- 负载均衡(Nginx的一致性Hash)
- 分布式存储(Cassandra的Token Ring)
7. 🔵 什么是服务雪崩?如何预防和处理?
答:服务雪崩是指一个服务的故障导致整个调用链路的级联失败。
雪崩过程:
- 服务A依赖服务B
- 服务B响应变慢或不可用
- 服务A的线程池被占满(等待B的响应)
- 服务A也变得不可用
- 依赖服务A的服务C也受影响
- 级联扩散,整个系统崩溃
预防手段:
- 超时控制:所有远程调用设置合理的超时时间
- 熔断器(Circuit Breaker):错误率超过阈值时自动熔断,快速失败
- 限流:控制请求速率,防止过载
- 隔离:线程池隔离、信号量隔离,防止故障扩散
- 降级:非核心功能降级,保证核心链路可用
- 重试策略:指数退避重试,避免重试风暴
熔断器状态机:
- Closed(关闭):正常状态,请求正常通过
- Open(打开):错误率超过阈值,所有请求快速失败
- Half-Open(半开):经过一段时间后,允许少量请求通过测试
8. 🔴 限流算法有哪些?各自的特点和适用场景?
答:限流是保护系统的第一道防线。
主流算法:
固定窗口计数器:
- 固定时间窗口内计数,超过阈值拒绝
- 问题:窗口边界的突发流量(两个窗口交界处可能有2倍流量)
滑动窗口计数器:
- 将窗口细分为多个小窗口,滑动统计
- 解决了固定窗口的边界问题
- 实现:Redis的ZSET(按时间戳排序)
漏桶算法(Leaky Bucket):
- 请求进入桶中,以固定速率流出
- 优点:输出速率恒定,平滑流量
- 缺点:无法应对突发流量
令牌桶算法(Token Bucket):
- 以固定速率向桶中放入令牌,请求需要获取令牌
- 桶满时令牌溢出(不累积超过桶容量)
- 优点:允许一定程度的突发流量(桶中有积累的令牌)
- Guava的RateLimiter就是令牌桶实现
滑动日志:
- 记录每个请求的时间戳
- 统计窗口内的请求数
- 最精确但内存开销大
生产选择:
- API网关限流:令牌桶(允许突发)
- 消息消费限流:漏桶(平滑处理)
- 分布式限流:Redis + Lua脚本实现滑动窗口
9. 🔵 什么是服务降级?降级策略有哪些?
答:服务降级是在系统压力过大时,暂时关闭非核心功能以保证核心功能可用。
降级策略:
- 功能降级:关闭非核心功能(如关闭推荐、评论、积分等)
- 数据降级:返回缓存数据或默认数据(如商品详情返回缓存版本)
- 体验降级:降低服务质量(如图片降低分辨率、搜索结果减少)
- 写降级:将同步写改为异步写(如日志、统计数据异步处理)
- 读降级:读请求走缓存,不查数据库
降级触发方式:
- 自动降级:基于监控指标自动触发(CPU>80%、响应时间>2s)
- 手动降级:运维人员通过开关手动触发
- 熔断降级:熔断器打开时自动降级
降级开关设计:
- 使用配置中心(Nacos、Apollo)管理降级开关
- 支持动态生效,不需要重启
- 按功能模块粒度控制
- 有完善的监控和告警
10. 🔴 分布式锁有哪些实现方案?各自的优缺点?
答:分布式锁是分布式系统中协调并发访问的基础设施。
方案对比:
Redis分布式锁:
- 实现:
SET key value NX EX timeout - 释放:Lua脚本保证原子性(检查value再删除)
- 优点:性能高,实现简单
- 缺点:Redis主从切换可能丢锁
- Redlock:多个Redis实例投票,但有争议(Martin Kleppmann的批评)
- 实现:
ZooKeeper分布式锁:
- 实现:创建临时顺序节点,最小序号获得锁
- 优点:可靠性高,支持可重入、公平锁
- 缺点:性能较低(需要创建/删除节点)
etcd分布式锁:
- 实现:基于Lease和Revision
- 优点:强一致性(Raft协议),性能优于ZK
- 缺点:生态不如ZK成熟
数据库分布式锁:
- 实现:
INSERT唯一键或SELECT ... FOR UPDATE - 优点:不需要额外组件
- 缺点:性能差,可能死锁
- 实现:
生产建议:
- 对性能要求高、允许极端情况下锁失效:Redis
- 对可靠性要求高:ZooKeeper或etcd
- PostgreSQL场景:Advisory Lock(简单可靠)
11. 🔴 什么是事件驱动架构(EDA)?与请求驱动架构有什么区别?
答:事件驱动架构是现代微服务架构的重要模式。
核心概念:
- 事件(Event):系统中发生的事实(如”订单已创建”、”支付已完成”)
- 事件生产者:产生事件的服务
- 事件消费者:订阅并处理事件的服务
- 事件通道:传递事件的中间件(Kafka、RocketMQ)
与请求驱动的区别:
| 维度 | 请求驱动 | 事件驱动 |
|---|---|---|
| 耦合度 | 强耦合(调用方知道被调用方) | 松耦合(生产者不知道消费者) |
| 通信方式 | 同步(请求-响应) | 异步(发布-订阅) |
| 扩展性 | 新增消费者需要修改调用方 | 新增消费者只需订阅事件 |
| 可追溯性 | 调用链路清晰 | 事件流需要额外追踪 |
| 一致性 | 强一致性容易实现 | 最终一致性 |
事件驱动的模式:
- Event Notification:通知其他服务发生了什么,不携带完整数据
- Event-Carried State Transfer:事件携带完整数据,消费者不需要回查
- Event Sourcing:将所有状态变更存储为事件序列
- CQRS:命令和查询分离,写入产生事件,读取从物化视图查询
12. 🔴 Event Sourcing和CQRS是什么?适合什么场景?
答:这是两个经常一起使用但独立的架构模式。
Event Sourcing(事件溯源):
- 不存储当前状态,而是存储所有状态变更事件
- 当前状态通过重放事件序列得到
- 类似数据库的WAL/binlog,但在应用层
优点:
- 完整的审计日志(每个变更都有记录)
- 可以回溯到任意时间点的状态
- 天然支持事件驱动架构
缺点:
- 查询当前状态需要重放事件(需要快照优化)
- 事件schema演进复杂
- 学习曲线陡峭
CQRS(Command Query Responsibility Segregation):
- 将读模型和写模型分离
- 写操作(Command)更新写模型
- 读操作(Query)从读模型查询
- 读写模型可以使用不同的数据库和数据结构
适用场景:
- 读写比例悬殊(读远多于写)
- 读写模型差异大(写入是范式化的,读取需要反范式化)
- 需要完整审计日志的金融系统
- 复杂业务领域(DDD中的聚合根)
不适用场景:
- 简单的CRUD应用
- 强一致性要求(CQRS天然是最终一致的)
13. 🔵 什么是背压(Backpressure)?在系统设计中如何应用?
答:背压是指下游处理能力不足时,向上游传递压力信号,让上游减慢发送速率。
没有背压的问题:
- 上游生产速度 > 下游消费速度
- 中间缓冲区不断增长,最终OOM
- 或者大量请求超时、丢弃
背压的实现方式:
- 阻塞式:缓冲区满时阻塞生产者(如BlockingQueue)
- 丢弃式:缓冲区满时丢弃新请求或旧请求
- 速率控制:动态调整生产者的发送速率
- 响应式流: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. 🔵 微服务的服务发现有哪些方案?
答:服务发现是微服务架构的基础设施。
两种模式:
客户端发现:客户端从注册中心获取服务列表,自己做负载均衡
- 代表:Eureka + Ribbon、Nacos + Dubbo
- 优点:客户端可以实现智能路由
- 缺点:每种语言都需要实现客户端
服务端发现:通过负载均衡器/代理转发请求
- 代表: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中最关键也最困难的决策。
划分方法:
事件风暴(Event Storming):
- 召集业务专家和开发人员
- 用便利贴列出所有领域事件(橙色)
- 识别触发事件的命令(蓝色)
- 识别聚合(黄色)
- 根据聚合的关联性划分限界上下文
业务能力分解:
- 按业务能力划分(订单管理、库存管理、支付管理)
- 每个业务能力对应一个限界上下文
数据所有权:
- 谁拥有数据,谁就是限界上下文
- 同一个概念在不同上下文中可能有不同含义(如”商品”在商品上下文和订单上下文中的含义不同)
划分原则:
- 高内聚低耦合
- 一个上下文由一个团队负责
- 上下文之间通过明确的接口通信
- 避免上下文过大(变成大泥球)或过小(过度拆分)
18. 🔴 聚合设计的原则是什么?如何确定聚合的边界?
答:聚合设计直接影响系统的一致性和性能。
设计原则:
- 聚合内强一致性:聚合内的所有修改在一个事务中完成
- 聚合间最终一致性:跨聚合的操作通过领域事件实现最终一致
- 聚合尽量小:只包含必须在同一事务中修改的对象
- 通过ID引用其他聚合:不直接持有其他聚合的对象引用
- 一次事务只修改一个聚合:避免跨聚合事务
确定边界的方法:
- 问自己:这些对象必须在同一个事务中保持一致吗?
- 如果是,放在同一个聚合
- 如果不是,拆分为不同的聚合
示例——电商订单:
- 聚合根:Order
- 聚合内:OrderItem(订单项必须和订单一起创建/修改)
- 聚合外:Product(商品是独立的聚合,订单通过productId引用)
- 聚合外:User(用户是独立的聚合,订单通过userId引用)
19. 🔴 DDD中的防腐层(Anti-Corruption Layer)是什么?什么时候需要?
答:防腐层是限界上下文之间的翻译层,防止外部模型污染内部模型。
使用场景:
- 对接遗留系统:新系统不想被老系统的数据模型污染
- 对接第三方服务:第三方API的模型与内部模型不一致
- 上下文之间模型差异大:两个上下文对同一概念的理解不同
实现方式:
- 在上下文边界处创建一个翻译层
- 将外部模型转换为内部模型
- 通常包含:Adapter(适配器)、Translator(翻译器)、Facade(门面)
示例:
1 | // 防腐层:将第三方支付的模型转换为内部模型 |
20. 🔵 贫血模型和充血模型有什么区别?你倾向于哪种?
答:这是DDD中关于领域模型设计的核心争论。
贫血模型(Anemic Domain Model):
- 领域对象只有getter/setter,没有业务逻辑
- 业务逻辑在Service层
- 本质上是面向过程的编程
- 大多数Java项目的现状
充血模型(Rich Domain Model):
- 领域对象包含业务逻辑和行为
- Service层只做编排和协调
- 真正的面向对象编程
- DDD推荐的方式
对比:
1 | // 贫血模型 |
我的观点:
- 核心域使用充血模型(业务逻辑复杂,值得投入)
- 支撑域和通用域可以用贫血模型(简单CRUD,不需要过度设计)
- 关键是团队能力和项目复杂度的匹配
21. 🔴 如何将DDD落地到微服务架构中?
答:DDD与微服务是天然契合的,限界上下文是微服务拆分的最佳指导。
落地步骤:
- 战略设计:通过事件风暴识别限界上下文
- 上下文映射:确定上下文之间的关系和通信方式
- 微服务拆分:一个限界上下文 = 一个微服务(或一组微服务)
- 战术设计:在每个微服务内部使用聚合、实体、值对象等
- 通信设计:上下文之间通过API或事件通信
代码结构(六边形架构/洋葱架构):
1 | order-service/ |
依赖规则:外层依赖内层,内层不依赖外层。领域层是最内层,不依赖任何框架。
22. 🔵 微服务拆分的原则是什么?如何避免过度拆分?
答:微服务拆分是架构设计中最容易犯错的地方。
拆分原则:
- 业务边界:按限界上下文拆分,不是按技术层拆分
- 团队边界:一个微服务由一个团队负责(康威定律)
- 数据边界:每个微服务拥有自己的数据库
- 变更频率:变更频率不同的模块拆分开
- 扩展需求:需要独立扩展的模块拆分开
过度拆分的信号:
- 大量跨服务调用(一个请求需要调用5+个服务)
- 分布式事务频繁出现
- 服务间循环依赖
- 一个简单功能需要修改多个服务
- 运维成本远大于开发成本
避免过度拆分:
- 先单体,后拆分(Monolith First)
- 从粗粒度开始,逐步细化
- 拆分前问自己:这个服务能独立部署和运维吗?
- 如果两个服务总是一起修改、一起部署,考虑合并
23. 🔴 微服务之间的通信方式有哪些?如何选择?
答:通信方式的选择直接影响系统的耦合度和性能。
同步通信:
- REST/HTTP:最简单,语言无关。适合简单的请求-响应
- gRPC:高性能,强类型(Protobuf)。适合内部服务间通信
- GraphQL:灵活查询,减少过度获取。适合BFF层
异步通信:
- 消息队列:Kafka、RocketMQ。适合事件驱动、解耦
- 事件总线:轻量级的发布-订阅
选择原则:
- 需要立即响应:同步(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分钟
关键策略:
冗余:消除单点故障
- 服务多实例部署
- 数据库主从/多副本
- 多机房/多数据中心
故障检测:快速发现问题
- 健康检查(HTTP/TCP)
- 心跳检测
- 监控告警
故障转移:自动切换到健康节点
- 负载均衡器自动摘除故障节点
- 数据库自动主从切换
- DNS故障转移
容错:在故障情况下继续提供服务
- 熔断、降级、限流
- 重试(带退避)
- 超时控制
可恢复:快速从故障中恢复
- 自动重启
- 数据备份和恢复
- 灰度发布和快速回滚
26. ⚫ 如何设计一个秒杀系统?
答:秒杀是高并发系统设计的经典面试题。
核心挑战:
- 瞬时高并发(百万级QPS)
- 库存一致性(不能超卖)
- 用户体验(快速响应)
架构设计:
前端:
- 静态页面CDN缓存
- 按钮防重复点击
- 倒计时从服务端获取
- 答题/验证码削峰
接入层:
- Nginx限流(令牌桶)
- 用户级限流(同一用户1秒内只能请求1次)
- 黑名单过滤(机器人、黄牛)
服务层:
- Redis预减库存(DECR原子操作)
- 库存为0后直接拒绝(内存标记)
- 请求入队(消息队列削峰)
数据层:
- 消息队列异步下单
- 数据库乐观锁扣减库存:
UPDATE SET stock=stock-1 WHERE id=? AND stock>0 - 订单异步创建
关键技术点:
- Redis预减库存保证不超卖
- 消息队列削峰填谷
- 异步处理提高吞吐量
- 多级缓存减少数据库压力
27. ⚫ 如何设计一个Feed流系统(朋友圈/微博)?
答:Feed流是社交系统的核心,考察数据模型和读写权衡。
两种模式:
推模式(Push/Fan-out on Write):
- 用户发布内容时,推送到所有粉丝的收件箱
- 读取时直接从收件箱获取
- 优点:读取快
- 缺点:大V发布时写放大严重(百万粉丝=百万次写入)
拉模式(Pull/Fan-out on Read):
- 用户发布内容只写入自己的发件箱
- 读取时从所有关注人的发件箱拉取并合并
- 优点:写入快
- 缺点:读取慢(需要合并多个列表)
推拉结合:
- 普通用户用推模式
- 大V用拉模式(粉丝读取时实时拉取大V的内容)
- 活跃用户用推模式,不活跃用户用拉模式
数据存储:
- 收件箱/发件箱:Redis Sorted Set(按时间排序)
- 内容详情:MySQL/MongoDB
- 关注关系:图数据库或Redis
28. 🔴 如何设计一个可扩展的权限系统?
答:权限系统是企业应用的基础设施。
权限模型演进:
- ACL(Access Control List):直接给用户分配权限。简单但管理困难
- RBAC(Role-Based Access Control):用户→角色→权限。最常用
- ABAC(Attribute-Based Access Control):基于属性的访问控制。最灵活
RBAC设计:
- 用户(User):系统使用者
- 角色(Role):权限的集合(如管理员、编辑、查看者)
- 权限(Permission):具体的操作权限(如user:create、order:delete)
- 用户-角色关系:多对多
- 角色-权限关系:多对多
数据权限:
- 不仅控制”能做什么”,还控制”能看到什么数据”
- 实现方式:SQL拼接WHERE条件、行级安全策略(PostgreSQL RLS)
API权限设计:
- 网关层统一鉴权
- 使用JWT携带用户角色信息
- 权限数据缓存到Redis(避免每次查数据库)
29. 🔴 如何设计一个全局唯一的短链接系统?
答:短链接系统考察哈希、存储和高并发设计。
核心设计:
短码生成:
- 方案1:自增ID + Base62编码(0-9a-zA-Z,62个字符)。6位可表示568亿
- 方案2:Hash(MurmurHash)取前6位 + 冲突处理
- 方案3:预生成短码池,使用时分配
存储:
- MySQL:短码→长URL的映射表
- Redis:缓存热门短链接
- 布隆过滤器:快速判断短码是否已存在
重定向:
- 301(永久重定向):浏览器缓存,减少服务器压力,但无法统计点击
- 302(临时重定向):每次都经过服务器,可以统计点击数据
高可用:
- 读多写少,适合缓存
- 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. 🔴 异地多活架构如何设计?有哪些关键挑战?
答:异地多活是高可用架构的终极形态,也是最复杂的架构之一。
架构模式:
- 冷备:备用机房平时不提供服务,主机房故障时切换。RTO长
- 热备(Active-Standby):备用机房实时同步数据,但不提供服务
- 双活(Active-Active):两个机房都提供服务,数据双向同步
- 多活:多个机房都提供服务
关键挑战:
数据一致性:跨机房数据同步延迟(通常10-100ms)
- 方案:单元化(用户数据只在一个机房写入)
流量调度:如何将用户请求路由到正确的机房
- 方案:DNS、GSLB(全局负载均衡)、客户端路由
数据冲突:双写场景下的数据冲突
- 方案:避免双写(单元化)、Last Write Wins、CRDT
故障切换:机房故障时如何快速切换
- 方案:自动化切换脚本、流量调度系统
单元化架构(推荐):
- 将用户按规则(如用户ID取模)分配到不同的单元(机房)
- 每个单元是完整的、自包含的
- 用户的所有数据和请求都在同一个单元内处理
- 避免跨单元的数据同步和事务
32. 🔴 灰度发布有哪些策略?如何设计灰度发布系统?
答:灰度发布是降低发布风险的核心手段。
发布策略:
- 蓝绿部署:两套完整环境,切换流量
- 金丝雀发布:先发布少量实例,观察后逐步扩大
- A/B测试:按用户特征分流,对比效果
- 滚动发布:逐批替换旧版本实例
灰度维度:
- 按用户ID:指定用户或用户ID取模
- 按地域:先在某个城市灰度
- 按流量比例:1% → 5% → 20% → 50% → 100%
- 按设备:先灰度Android再灰度iOS
- 按租户:SaaS场景按租户灰度
灰度发布系统设计:
- 规则引擎:配置灰度规则(用户白名单、流量比例等)
- 流量分发:网关层根据规则路由到不同版本
- 监控对比:对比新旧版本的指标(错误率、延迟、业务指标)
- 快速回滚:一键回滚到旧版本
33. 🔴 数据库主从切换的方案有哪些?如何保证切换过程中数据不丢失?
答:数据库主从切换是高可用的核心环节。
切换方式:
- 手动切换:DBA手动操作,适合计划内维护
- 半自动切换:工具辅助,人工确认后执行(如MHA)
- 全自动切换:自动检测故障并切换(如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)?如何实践?
答:混沌工程是通过主动注入故障来验证系统韧性的方法论。
核心原则:
- 建立稳态假设(定义系统正常行为的指标)
- 引入真实世界的变量(网络延迟、节点宕机、磁盘满等)
- 在生产环境运行实验(测试环境无法暴露真实问题)
- 自动化持续运行
- 最小化爆炸半径
常见故障注入:
- 服务实例随机终止(Netflix Chaos Monkey)
- 网络延迟/丢包/分区
- CPU/内存/磁盘压力
- 依赖服务不可用
- 时钟偏移
工具:
- Chaos Monkey:Netflix开源,随机终止实例
- ChaosBlade:阿里开源,支持多种故障注入
- Litmus:Kubernetes原生的混沌工程平台
- Chaos Mesh:PingCAP开源,K8s环境
实践步骤:
- 从非核心服务开始
- 先在预发环境验证
- 逐步扩展到生产环境
- 建立完善的监控和告警
- 每次实验后复盘和改进
36. 🔴 如何设计系统的监控告警体系?
答:监控是高可用的眼睛,没有监控的系统等于裸奔。
监控层次(从下到上):
- 基础设施监控:CPU、内存、磁盘、网络(Prometheus + Node Exporter)
- 中间件监控:数据库、缓存、消息队列的指标
- 应用监控:JVM指标、接口RT、错误率、QPS
- 业务监控:订单量、支付成功率、转化率
- 用户体验监控:页面加载时间、首屏时间、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:跨进程传递的业务数据
工作原理:
- 入口服务生成全局唯一的TraceID
- 每个操作创建一个Span,记录开始时间、结束时间、标签
- 调用下游服务时,通过HTTP Header或RPC元数据传递SpanContext
- 下游服务创建子Span,关联父SpanID
- 所有Span异步上报到收集器
- 收集器根据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系统的核心架构模式。
隔离模式:
独立数据库:每个租户一个数据库
- 隔离性最好,安全性最高
- 成本最高,运维复杂
- 适合大客户、合规要求高的场景
共享数据库,独立Schema:
- 每个租户一个Schema
- 隔离性较好,成本适中
- PostgreSQL的Schema天然支持
共享数据库,共享表:
- 所有租户数据在同一张表,通过tenant_id区分
- 成本最低,运维最简单
- 隔离性最差,需要严格的数据过滤
数据隔离保障:
- 所有SQL自动加上tenant_id条件(MyBatis拦截器/Hibernate Filter)
- PostgreSQL行级安全策略(RLS)
- API层校验tenant_id与当前用户匹配
性能隔离:
- 限流:每个租户独立的限流配额
- 资源配额:CPU、内存、存储的配额限制
- 队列隔离:不同租户的请求进入不同队列
租户路由:
- 通过域名识别租户(tenant1.app.com)
- 通过Header传递租户ID
- 通过JWT中的租户信息
41. 🔴 容灾演练应该怎么做?有哪些关键步骤?
答:容灾演练是验证高可用方案有效性的唯一手段。
演练类型:
- 桌面演练:纸上谈兵,讨论故障场景和应对方案
- 模拟演练:在测试环境模拟故障
- 真实演练:在生产环境注入真实故障
- 突袭演练:不提前通知,考验真实应急能力
关键步骤:
- 制定计划:明确演练目标、范围、时间、参与人员
- 风险评估:评估演练可能造成的影响,准备回滚方案
- 故障注入:按计划注入故障(如关闭主库、断开网络)
- 观察记录:记录系统表现、告警触发、自动恢复情况
- 人工介入:如果自动恢复失败,人工介入处理
- 恢复验证:确认系统完全恢复正常
- 复盘总结:分析问题,制定改进计划
演练场景清单:
- 数据库主库宕机 → 自动切换到从库
- Redis主节点故障 → Sentinel自动切换
- 单个服务实例宕机 → 负载均衡自动摘除
- 机房网络中断 → 流量切换到备用机房
- 消息队列Broker宕机 → 自动Leader选举
- 配置中心不可用 → 本地缓存兜底
42. 🔴 如何设计一个高可用的注册中心?注册中心挂了怎么办?
答:注册中心是微服务架构的心脏,它的可用性至关重要。
注册中心高可用方案:
- 集群部署:至少3个节点(Raft/Paxos需要奇数节点)
- 多数据中心:跨机房部署,避免单机房故障
- 数据持久化:注册数据持久化到磁盘
注册中心挂了的应对:
客户端缓存:服务消费者本地缓存服务列表
- Dubbo:本地文件缓存(
~/.dubbo/) - Nacos:本地快照文件
- 注册中心挂了,已有的服务调用不受影响
- Dubbo:本地文件缓存(
直连模式:绕过注册中心,直接配置服务地址
- 作为降级方案,在配置中心配置直连地址
多注册中心:同时注册到多个注册中心
- Dubbo支持多注册中心配置
DNS兜底:将服务地址配置到DNS
- 注册中心不可用时,通过DNS解析服务地址
关键认知:
- 注册中心是CP还是AP决定了故障时的行为
- AP(Eureka/Nacos):注册中心节点间数据可能不一致,但服务可用
- CP(ZooKeeper/Consul):注册中心不可用时,无法注册新服务
43. 🔴 如何处理分布式系统中的数据不一致问题?
答:数据不一致是分布式系统中最棘手的问题之一。
不一致的来源:
- 网络分区导致数据同步中断
- 服务调用失败(部分成功部分失败)
- 缓存与数据库不一致
- 消息丢失或重复消费
- 并发写入冲突
解决方案:
对账系统:定时对比不同数据源的数据
- 如:订单系统和支付系统的金额对账
- 发现不一致后自动或人工修复
补偿机制:失败后执行补偿操作
- SAGA模式的补偿事务
- 定时任务重试失败的操作
最终一致性:通过消息队列保证最终一致
- 本地消息表 + 定时发送
- 事务消息(RocketMQ)
版本控制:通过版本号检测和解决冲突
- 乐观锁:
WHERE version = ? - 向量时钟:检测并发冲突
- 乐观锁:
CRDT(Conflict-free Replicated Data Type):
- 无冲突复制数据类型
- 数学上保证最终一致性
- 适合计数器、集合等简单数据结构
实践建议:
- 核心数据(资金):强一致性(分布式事务)
- 非核心数据(统计):最终一致性(异步对账)
- 缓存数据:设置过期时间 + 主动失效
44. 🔵 什么是故障隔离?有哪些隔离策略?
答:故障隔离是防止局部故障扩散为全局故障的关键手段。
隔离策略:
线程池隔离:
- 不同的下游服务使用不同的线程池
- 某个下游服务慢不会占满所有线程
- Hystrix的默认隔离策略
- 缺点:线程切换开销
信号量隔离:
- 使用信号量限制并发数
- 无线程切换开销
- 适合快速调用(如本地缓存查询)
进程隔离:
- 不同服务部署在不同的进程/容器中
- 微服务架构天然具备进程隔离
机房隔离:
- 不同机房独立运行
- 单元化架构实现机房级隔离
数据隔离:
- 读写分离:读请求走从库,写请求走主库
- 冷热分离:热数据在缓存,冷数据在磁盘
用户隔离:
- VIP用户和普通用户使用不同的服务集群
- 防止普通用户的流量影响VIP用户
泳道隔离(Swimlane):
- 将系统按业务维度划分为多个泳道
- 每个泳道是完整的服务链路
- 泳道之间互不影响
- 适合灰度发布和故障隔离
45. 🔴 如何设计一个高可用的缓存架构?
答:缓存是系统性能的关键,缓存故障可能导致系统雪崩。
多级缓存架构:
- L1 - 本地缓存:Caffeine/Guava Cache,毫秒级响应
- L2 - 分布式缓存:Redis Cluster,亚毫秒级响应
- L3 - 数据库:MySQL/PostgreSQL,毫秒到秒级响应
Redis高可用方案:
- 主从复制 + Sentinel:自动故障转移,适合中小规模
- Redis Cluster:分片 + 副本,适合大规模
- Proxy方案:Twemproxy、Codis,客户端透明
缓存故障应对:
缓存穿透:查询不存在的数据
- 布隆过滤器拦截
- 缓存空值(设置短过期时间)
缓存击穿:热点key过期
- 互斥锁(SETNX):只允许一个请求回源
- 逻辑过期:不设置物理过期时间,后台异步更新
缓存雪崩:大量key同时过期
- 过期时间加随机值
- 多级缓存兜底
- 限流降级
缓存一致性方案:
- Cache Aside:先更新DB,再删除缓存(最常用)
- 延迟双删:更新DB → 删缓存 → 延迟再删一次
- 订阅binlog:Canal监听binlog,异步更新缓存
46. 🔴 如何做容量规划?如何评估系统能承受多大的流量?
答:容量规划是保障系统稳定性的前置工作。
容量规划步骤:
- 业务预估:预估未来的业务量(用户数、订单量、QPS)
- 性能基线:通过压测获取单机性能指标
- 资源计算:根据业务量和单机性能计算所需资源
- 冗余设计:预留30-50%的冗余容量
- 持续验证:定期压测验证容量是否满足
压测方法:
- 单机压测:确定单个实例的极限性能
- 全链路压测:模拟真实流量,验证整个系统的容量
- 影子库/影子表:压测流量写入影子库,不影响生产数据
全链路压测关键点:
- 压测流量标记(Header中添加标记)
- 影子库/影子表隔离压测数据
- 中间件识别压测流量(消息队列、缓存)
- 第三方服务Mock
- 压测数据清理
容量计算公式:
- 所需实例数 = 峰值QPS / 单机QPS × 安全系数(1.5-2)
- 峰值QPS = 日均QPS × 峰值系数(通常3-5倍)
- 日均QPS = 日请求量 / 86400
47. 🔴 什么是流量回放?如何用流量回放做回归测试?
答:流量回放是将生产流量录制下来,在测试环境重放,验证系统正确性。
核心流程:
- 流量录制:在生产环境录制请求和响应
- 流量存储:将录制的流量存储到文件或消息队列
- 流量回放:在测试环境重放录制的流量
- 结果对比:对比新旧版本的响应差异
工具:
- GoReplay(gor):Go语言实现,轻量级HTTP流量录制回放
- JVM-Sandbox-Repeater:阿里开源,Java应用流量录制回放
- TCPCopy:TCP层流量复制
- Diffy:Twitter开源,自动对比新旧版本响应
关键挑战:
- 时间依赖:录制时和回放时的时间不同
- 外部依赖:回放时不能调用真实的第三方服务
- 数据依赖:回放时数据库状态可能不同
- 幂等性:重放写请求可能产生副作用
解决方案:
- Mock外部依赖
- 使用影子库隔离数据
- 时间参数替换
- 只回放读请求(安全模式)
48. 🔴 如何设计一个高可用的日志系统?
答:日志系统是排查问题的生命线,它自身的高可用同样重要。
日志架构(ELK/EFK):
- 采集层:Filebeat/Fluentd/Fluent Bit 采集日志文件
- 缓冲层:Kafka 缓冲日志数据,削峰填谷
- 处理层:Logstash/Flink 解析、过滤、转换日志
- 存储层:Elasticsearch 存储和索引日志
- 展示层:Kibana/Grafana 查询和可视化
高可用设计:
- 采集端:本地文件缓冲,采集器故障不丢日志
- Kafka:多副本,保证日志不丢失
- Elasticsearch:多节点集群,分片和副本
- 冷热分离:热数据SSD,冷数据HDD,定期归档到对象存储
日志规范:
- 统一日志格式(JSON结构化日志)
- 必须包含:时间戳、TraceID、服务名、日志级别、消息
- 敏感信息脱敏(手机号、身份证号)
- 日志级别合理使用(ERROR只用于需要人工介入的错误)
日志量控制:
- 生产环境默认INFO级别
- 支持动态调整日志级别(不重启)
- 采样日志:高频日志按比例采样
- 日志保留策略:热数据7天,温数据30天,冷数据90天
49. 🔴 什么是无损发布?如何实现?
答:无损发布是指在发布过程中不丢失任何请求,用户无感知。
有损发布的问题:
- 服务下线时,正在处理的请求被中断
- 新服务还没准备好就接收流量
- 注册中心感知延迟,流量仍然路由到已下线的实例
无损发布的关键步骤:
优雅下线:
- 先从注册中心注销
- 等待一段时间(让消费者感知)
- 拒绝新请求,处理完已有请求
- 关闭连接,停止服务
优雅上线:
- 服务启动完成后再注册到注册中心
- 健康检查通过后再接收流量
- 预热(Warm Up):逐步增加流量,避免冷启动
就绪探针(Readiness Probe):
- Kubernetes中的就绪探针
- 只有探针通过才会接收流量
预热机制:
- JIT编译预热
- 连接池预热
- 缓存预热
- Dubbo的warmup参数:启动后逐步增加权重
Spring Boot优雅停机:
1 | server: |
50. ⚫ 如何设计一个能支撑千万级DAU的系统架构?
答:这是一个综合性的架构设计题,考察全局视野。
流量估算:
- 千万DAU → 峰值在线用户约100万
- 假设每用户每分钟1次请求 → 峰值QPS约16000
- 考虑峰值系数3倍 → 设计目标50000 QPS
架构分层:
接入层:CDN + LB(Nginx/SLB)
- 静态资源CDN加速
- 多机房部署,DNS负载均衡
- Nginx集群,支持10万+并发连接
网关层:API Gateway
- 认证鉴权、限流熔断
- 协议转换、请求路由
- 水平扩展,无状态
服务层:微服务集群
- 按业务域拆分微服务
- 服务注册发现(Nacos)
- RPC通信(Dubbo/gRPC)
缓存层:多级缓存
- L1本地缓存(Caffeine)
- L2分布式缓存(Redis Cluster)
- 缓存命中率 > 95%
数据层:分库分表 + 读写分离
- 写库:分库分表(ShardingSphere)
- 读库:多从库 + 读写分离
- 搜索:Elasticsearch
- 大数据:ClickHouse/Doris
消息层:异步解耦
- Kafka处理高吞吐场景
- RocketMQ处理事务消息
关键设计原则:
- 无状态设计:服务实例可随时扩缩容
- 异步化:能异步的都异步
- 缓存化:能缓存的都缓存
- 弹性伸缩:基于指标自动扩缩容
四、微服务治理(51-70题)
51. 🔴 微服务的数据一致性如何保证?
答:微服务架构下,每个服务有自己的数据库,跨服务的数据一致性是核心挑战。
方案选择矩阵:
| 场景 | 方案 | 一致性 |
|---|---|---|
| 资金交易 | TCC/2PC | 强一致 |
| 订单流程 | SAGA | 最终一致 |
| 通知类 | 本地消息表 | 最终一致 |
| 数据同步 | CDC + MQ | 最终一致 |
SAGA实战(订单流程):
- 创建订单(订单服务)
- 扣减库存(库存服务)
- 扣减余额(账户服务)
- 任何一步失败,逆序补偿
编排式 vs 协同式:
- 编排式(Orchestration):有一个中心协调者,按顺序调用各服务
- 优点:流程清晰,易于监控
- 缺点:协调者是单点
- 协同式(Choreography):各服务通过事件驱动,自行响应
- 优点:去中心化,松耦合
- 缺点:流程分散,难以追踪
52. 🔴 如何实现微服务的优雅停机和无损上下线?
答:这是微服务运维中最容易被忽视但影响巨大的问题。
下线流程(以Dubbo为例):
- 从注册中心注销服务
- 发送readonly事件通知消费者
- 等待消费者感知(默认等待10s)
- 停止接收新请求
- 等待已有请求处理完成(超时时间内)
- 关闭连接,停止JVM
上线流程:
- JVM启动,Spring容器初始化
- 连接池预热(数据库、Redis、HTTP)
- JIT预热(可选:回放录制的流量)
- 健康检查通过
- 注册到注册中心
- 逐步增加流量权重(Warm Up)
Kubernetes环境:
- preStop Hook:Pod终止前执行脚本(注销注册中心)
- terminationGracePeriodSeconds:优雅停机等待时间
- Readiness Probe:就绪探针通过后才接收流量
- Startup Probe:启动探针,避免慢启动被杀
常见问题:
- 注册中心感知延迟:消费者缓存了旧的服务列表
- 长连接未断开:需要主动关闭连接
- 异步任务未完成:需要等待线程池中的任务执行完
53. 🔵 微服务的版本管理策略有哪些?
答:API版本管理是微服务长期演进的关键。
版本策略:
URL版本:
/api/v1/users、/api/v2/users- 最直观,客户端容易理解
- 缺点:URL变化,缓存失效
Header版本:
Accept: application/vnd.api.v2+json- URL不变,更RESTful
- 缺点:不够直观
Query参数版本:
/api/users?version=2- 简单,但不够优雅
语义化版本(SemVer):Major.Minor.Patch
- Major:不兼容的变更
- Minor:向后兼容的新功能
- Patch:向后兼容的Bug修复
兼容性设计:
- 新增字段不影响旧客户端(向后兼容)
- 废弃字段标记为deprecated,不立即删除
- 使用Protobuf/Avro等支持schema演进的序列化格式
- 消费者驱动的契约测试(Pact)
多版本共存:
- 网关层路由不同版本到不同的服务实例
- 同一服务内部通过适配器支持多版本
- 设置版本淘汰策略(如最多支持最近3个版本)
54. 🔴 如何设计微服务的统一异常处理和错误码体系?
答:统一的错误处理是微服务可维护性的基础。
错误码设计:
1 | 格式:[服务代码][模块代码][错误序号] |
错误分类:
- 客户端错误(4xx):参数错误、权限不足、资源不存在
- 服务端错误(5xx):内部异常、依赖服务不可用
- 业务错误:业务规则校验失败(如余额不足、库存不足)
统一响应格式:
1 | { |
异常处理原则:
- 业务异常:返回明确的错误码和消息,不记录ERROR日志
- 系统异常:返回通用错误信息,记录ERROR日志(含堆栈)
- 第三方异常:包装为内部错误码,隐藏第三方细节
- 不要吞掉异常:至少记录日志
- 不要暴露内部细节:不返回堆栈信息给客户端
跨服务异常传播:
- 下游服务的错误码透传给上游
- 上游服务可以选择转换错误码或直接透传
- TraceID贯穿整个调用链,方便排查
55. 🔴 如何实现微服务的配置热更新?
答:配置热更新是微服务运维的基本能力。
实现方式:
- 配置中心推送:Nacos/Apollo配置变更后推送到客户端
- Spring Cloud Config + Bus:配置变更通过消息总线通知所有实例
- Kubernetes ConfigMap + 热加载:ConfigMap变更后自动重载
Spring Cloud + Nacos实现:
@RefreshScope:Bean级别的配置刷新@NacosValue(autoRefreshed = true):字段级别的自动刷新- 监听配置变更事件,执行自定义逻辑
热更新的注意事项:
- 线程安全:配置更新时可能有线程正在使用旧值
- 原子性:多个配置项需要同时生效
- 回滚:配置变更后出问题需要快速回滚
- 灰度:配置变更先在部分实例生效
不适合热更新的配置:
- 数据库连接池大小(需要重建连接池)
- 端口号(需要重启)
- 日志框架配置(部分框架不支持)
56. 🔴 微服务的安全如何设计?认证和授权方案有哪些?
答:微服务安全是架构设计中不可忽视的部分。
认证方案:
JWT(JSON Web Token):
- 无状态,不需要服务端存储Session
- 网关验证JWT签名,提取用户信息
- 缺点:无法主动失效(需要黑名单机制)
OAuth 2.0:
- 标准的授权框架
- 支持多种授权模式(授权码、客户端凭证等)
- 适合对外开放API
mTLS(双向TLS):
- 服务间通信的身份认证
- Service Mesh自动管理证书
- 零信任网络的基础
微服务安全架构:
- 外部请求:网关统一认证(JWT/OAuth)
- 内部通信:mTLS + 服务身份认证
- 数据安全:传输加密(TLS)+ 存储加密(AES)
- API安全:限流、防重放、签名验证
零信任架构(Zero Trust):
- 不信任任何网络位置(内网也不信任)
- 每次请求都需要认证和授权
- 最小权限原则
- 持续验证(不是一次认证永久有效)
57. 🔴 如何处理微服务之间的循环依赖?
答:循环依赖是微服务架构中的设计缺陷,必须消除。
循环依赖的表现:
- 服务A调用服务B,服务B又调用服务A
- 部署时无法确定启动顺序
- 故障时互相影响,难以排查
解决方案:
引入中间服务:将共同依赖的逻辑抽取到新服务C
- A → C ← B(消除循环)
事件驱动:将同步调用改为异步事件
- A发布事件 → B订阅事件(解耦)
合并服务:如果两个服务耦合度太高,考虑合并
- 说明拆分粒度过细
回调机制:B完成后通过回调通知A
- 而不是B主动调用A
共享数据库(反模式,慎用):
- 两个服务共享一个数据库
- 违反微服务原则,但在过渡期可以接受
预防措施:
- 服务依赖关系可视化(定期review)
- 架构守护规则(ArchUnit等工具检测循环依赖)
- 代码review时关注跨服务调用
58. 🔴 如何设计微服务的日志和审计系统?
答:日志和审计是微服务可观测性和合规性的基础。
日志分类:
- 访问日志:HTTP请求日志(URL、参数、响应码、耗时)
- 业务日志:业务操作日志(谁在什么时间做了什么)
- 错误日志:异常和错误信息
- 审计日志:合规审计(数据变更、权限操作)
- 安全日志:登录、权限变更、敏感操作
统一日志格式:
1 | { |
审计日志设计:
- 记录所有数据变更(谁、什么时间、改了什么、改前值、改后值)
- 审计日志不可篡改(写入后不能修改或删除)
- 独立存储(不与业务日志混在一起)
- 支持查询和导出(合规审计需要)
实现方式:
- AOP拦截:通过注解标记需要审计的方法
- 数据库触发器:自动记录数据变更
- CDC(Change Data Capture):监听binlog记录变更
- Event Sourcing:天然具备审计能力
59. 🔴 如何实现微服务的流量染色和全链路灰度?
答:流量染色是实现全链路灰度发布的基础技术。
流量染色原理:
- 在网关层给请求打标签(如Header: x-gray=true)
- 标签在整个调用链路中传递
- 每个服务根据标签路由到对应版本的实例
全链路灰度架构:
- 网关:根据规则(用户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变更是微服务持续交付中的高风险操作。
安全变更原则:
- 向后兼容:新Schema兼容旧代码
- 小步迭代:大变更拆分为多个小变更
- 先扩展后收缩:先加新列,迁移数据,再删旧列
安全的变更操作:
- ✅ 添加新列(有默认值)
- ✅ 添加新表
- ✅ 添加索引(Online DDL)
- ❌ 删除列(可能有旧代码在使用)
- ❌ 重命名列(等同于删除+添加)
- ❌ 修改列类型(可能数据丢失)
不兼容变更的安全步骤(以重命名列为例):
- 添加新列(new_name)
- 代码同时写入新旧两列
- 迁移历史数据到新列
- 代码只读新列
- 停止写入旧列
- 删除旧列
工具:
- 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 | Web → Web BFF → 微服务A、B、C |
BFF的职责:
- 数据聚合:调用多个微服务,组装前端需要的数据
- 数据裁剪:只返回前端需要的字段
- 协议转换:将内部gRPC转换为HTTP/GraphQL
- 认证适配:不同端的认证方式不同
BFF vs API Gateway:
- API Gateway:通用的基础设施(路由、限流、认证)
- BFF:面向特定前端的业务逻辑(数据聚合、格式转换)
- 两者可以共存:请求先经过Gateway,再到BFF
GraphQL作为BFF:
- 前端自定义查询,按需获取数据
- 减少过度获取和不足获取
- 适合数据关系复杂的场景
62. 🔴 如何设计微服务的健康检查机制?
答:健康检查是服务治理的基础,直接影响故障发现和恢复速度。
健康检查类型:
存活检查(Liveness):服务进程是否存活
- 失败处理:重启服务
- 检查内容:进程是否响应、是否死锁
就绪检查(Readiness):服务是否准备好接收流量
- 失败处理:从负载均衡中摘除
- 检查内容:依赖的数据库、缓存是否可用
启动检查(Startup):服务是否启动完成
- 失败处理:等待,超时后重启
- 适用于启动慢的服务
健康检查设计原则:
- 检查要快(超时时间短,如3秒)
- 检查要轻(不要做重计算)
- 区分自身健康和依赖健康
- 避免级联不健康(A依赖B,B不健康导致A也不健康)
Spring Boot Actuator:
/actuator/health:综合健康状态/actuator/health/liveness:存活状态/actuator/health/readiness:就绪状态- 自定义HealthIndicator检查业务依赖
深度健康检查 vs 浅度健康检查:
- 浅度:只检查服务进程是否存活(用于Liveness)
- 深度:检查所有依赖是否可用(用于Readiness,但要注意级联问题)
63. 🔴 如何实现微服务的请求幂等?
答:幂等是微服务可靠性的基石,尤其在重试和消息重复消费场景下。
幂等场景:
- 用户重复点击提交按钮
- 网络超时后客户端重试
- 消息队列重复投递
- 服务调用重试(Dubbo/gRPC重试)
实现方案(按场景):
创建类操作(如创建订单):
- 客户端生成唯一请求ID(idempotency-key)
- 服务端用Redis SETNX检查是否已处理
- 已处理则直接返回上次的结果
更新类操作(如扣减库存):
- 乐观锁:
UPDATE SET stock=stock-1 WHERE id=? AND version=? - 状态机:只允许合法的状态转换
- 乐观锁:
消息消费:
- 消息ID去重(Redis或数据库唯一键)
- 业务唯一键去重(如订单号)
支付类操作:
- 支付流水号唯一约束
- 先查询支付状态,已支付则直接返回
幂等框架设计:
1 |
|
- 通过AOP拦截,自动实现幂等检查
- 支持自定义key表达式
- 支持配置过期时间
64. 🔴 微服务的超时设置有什么讲究?
答:超时设置看似简单,但设置不当会导致严重问题。
超时类型:
- 连接超时(Connect Timeout):建立TCP连接的超时,通常1-3秒
- 读超时(Read Timeout):等待响应的超时,根据接口特性设置
- 写超时(Write Timeout):发送请求的超时,通常较短
超时设置原则:
链路超时递减:上游超时 > 下游超时
- 网关超时5s > 服务A超时3s > 服务B超时1s
- 否则上游已超时返回,下游还在处理(资源浪费)
基于P99设置:超时时间 = P99延迟 × 2
- 太短:正常请求被超时
- 太长:故障时线程长时间阻塞
区分读写:写操作超时可以长一些,读操作超时短一些
重试与超时的关系:
- 总超时 = 单次超时 × 重试次数
- 总超时不能超过上游的超时
常见问题:
- 超时设置过长 → 线程池耗尽 → 服务雪崩
- 超时设置过短 → 正常请求失败率高
- 没有设置超时 → 默认无限等待 → 线程泄漏
- 重试风暴 → 下游压力倍增
65. 🔴 如何设计微服务的重试策略?
答:重试是提高系统可靠性的重要手段,但不当的重试会加剧故障。
重试策略:
- 固定间隔重试:每次重试间隔相同(如1秒)
- 指数退避重试:间隔指数增长(1s, 2s, 4s, 8s…)
- 指数退避 + 随机抖动:在指数退避基础上加随机值,避免重试风暴
- 立即重试 + 退避重试:第一次立即重试,后续退避
重试的前提条件:
- 操作是幂等的(非幂等操作不能重试)
- 错误是可重试的(如网络超时、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 × 单次延迟
批量调用方案:
批量API:提供批量查询接口
1
2
3
4// 单个查询
User getUser(Long userId);
// 批量查询
Map<Long, User> batchGetUsers(List<Long> userIds);请求合并(Request Collapsing):
- 将短时间内的多个单次请求合并为一个批量请求
- Hystrix的Request Collapsing
- 自定义:使用时间窗口 + 队列收集请求
DataLoader模式:
- GraphQL中的经典模式
- 在一个事件循环中收集所有数据请求
- 批量发送,结果分发给各个请求者
并行调用:
- CompletableFuture并行调用多个服务
- 总延迟 = max(各服务延迟)
设计注意:
- 批量接口要限制单次请求数量(如最多500个)
- 批量接口要支持部分失败(返回成功和失败的结果)
- 考虑超时:批量请求的超时要比单次请求长
68. 🔴 如何设计微服务的契约测试?
答:契约测试是保证微服务间接口兼容性的关键手段。
问题背景:
- 服务A依赖服务B的API
- 服务B修改了API,但没有通知服务A
- 集成测试时才发现不兼容 → 发布延迟
契约测试(Consumer-Driven Contract Testing):
- 消费者定义契约:消费者定义期望的请求和响应格式
- 生产者验证契约:生产者运行契约测试,确保满足消费者的期望
- 契约共享:通过Pact Broker或Git共享契约文件
工具:
- Pact:最流行的契约测试框架,支持多语言
- Spring Cloud Contract:Spring生态的契约测试
Pact工作流:
- 消费者编写测试,生成Pact文件(JSON格式的契约)
- Pact文件上传到Pact Broker
- 生产者从Broker下载Pact文件
- 生产者运行Pact验证测试
- 验证结果上传到Broker
- CI/CD中检查契约是否通过
契约测试 vs 集成测试:
- 契约测试:快速、独立运行、关注接口兼容性
- 集成测试:慢、需要完整环境、关注端到端功能
69. 🔴 如何实现微服务的动态路由?
答:动态路由是实现灰度发布、A/B测试、流量调度的基础。
路由维度:
- 版本路由:根据服务版本路由(v1、v2)
- 标签路由:根据服务标签路由(gray、stable)
- 权重路由:按权重分配流量(v1:90%, v2:10%)
- 条件路由:根据请求参数路由(userId % 100 < 10 → v2)
- 地域路由:就近路由到同机房的服务
Dubbo路由规则:
1 | # 标签路由 |
Istio流量管理:
1 | apiVersion: networking.istio.io/v1alpha3 |
动态路由的实现:
- 路由规则存储在配置中心(Nacos/etcd)
- 规则变更实时推送到所有服务实例
- 服务框架在负载均衡前应用路由规则
70. ⚫ 如何从单体架构演进到微服务架构?
答:这是架构师最常面对的实际问题,考察渐进式演进的能力。
演进策略(绞杀者模式 Strangler Fig Pattern):
- 不要大爆炸重写:风险极高,失败率极高
- 渐进式迁移:新功能用微服务,旧功能逐步迁移
步骤:
- 梳理边界:用DDD识别限界上下文
- 建立基础设施:注册中心、配置中心、网关、监控
- 抽取第一个服务:选择耦合度低、变更频繁的模块
- 建立防腐层:新服务与单体之间通过防腐层通信
- 数据迁移:将新服务的数据从单体数据库迁移出来
- 流量切换:逐步将流量从单体切换到新服务
- 重复迭代:继续抽取下一个服务
选择第一个服务的标准:
- 业务边界清晰
- 与其他模块耦合度低
- 变更频率高(收益大)
- 团队熟悉(降低风险)
数据迁移策略:
- 双写:同时写入新旧数据库,逐步切换读
- CDC同步:通过binlog同步数据到新数据库
- 影子读:读请求同时发到新旧数据库,对比结果
常见陷阱:
- 分布式单体:拆分了服务但没有拆分数据库
- 过度拆分:一开始就拆太细
- 忽视运维:没有准备好监控、日志、CI/CD就开始拆分
五、系统设计实战(71-100题)
71. ⚫ 如何设计一个分布式文件存储系统?
答:分布式文件存储是大规模系统的基础设施。
核心设计:
元数据管理:
- 文件名 → 文件块的映射关系
- 集中式(HDFS NameNode)或分布式(Ceph CRUSH算法)
- 元数据高可用:主从复制 + 自动切换
数据分片:
- 大文件切分为固定大小的块(如64MB/128MB)
- 每个块存储在不同的节点上
- 支持并行读写
副本策略:
- 默认3副本(不同机架、不同机房)
- 副本放置策略:一个本地、一个同机架、一个跨机架
- 副本一致性:链式复制或并行复制
故障恢复:
- 心跳检测节点存活
- 节点故障后自动复制副本到其他节点
- 数据校验(Checksum)防止静默数据损坏
方案对比:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| HDFS | 大文件、批处理 | 大数据分析 |
| Ceph | 统一存储(块/文件/对象) | 云平台 |
| MinIO | S3兼容、轻量级 | 对象存储 |
| FastDFS | 轻量级、适合小文件 | 图片/视频存储 |
72. ⚫ 如何设计一个实时搜索系统?
答:搜索系统是互联网应用的核心基础设施。
架构设计:
数据采集:
- 全量索引:定时从数据库全量构建索引
- 增量索引:监听binlog(Canal),实时更新索引
- 双写:业务代码同时写数据库和搜索引擎(不推荐,一致性难保证)
索引构建:
- 分词:中文分词(IK Analyzer、jieba)
- 倒排索引:词 → 文档列表
- 正排索引:文档 → 字段值(用于排序和聚合)
查询处理:
- 查询解析:分词、同义词扩展、纠错
- 召回:从倒排索引中检索候选文档
- 排序:相关性评分(BM25)+ 业务评分(销量、评分)
- 过滤:价格范围、品类、品牌等条件过滤
性能优化:
- 索引分片:数据分布到多个节点
- 查询缓存:热门查询结果缓存
- 预计算:热门聚合结果预计算
Elasticsearch集群设计:
- Master节点:集群管理(3个,不存数据)
- Data节点:存储数据和执行查询
- Coordinating节点:请求路由和结果聚合
- 分片数 = 数据量 / 单分片大小(建议30-50GB/分片)
73. ⚫ 如何设计一个延迟消息系统?
答:延迟消息在订单超时取消、定时提醒等场景中广泛使用。
实现方案:
RocketMQ延迟消息:
- 支持固定延迟级别(1s, 5s, 10s, 30s, 1m, …)
- 5.0版本支持任意时间延迟
- 实现简单,但延迟级别有限
Redis实现:
- Sorted Set:score为执行时间戳
- 定时任务轮询:
ZRANGEBYSCORE key 0 now - 优点:精度高,支持任意延迟
- 缺点:数据量大时内存压力
时间轮(Timing Wheel):
- Kafka、Netty使用的延迟任务方案
- 类似钟表,每个槽位对应一个时间刻度
- 多层时间轮支持长延迟
- 优点:O(1)的插入和删除
数据库轮询:
- 定时扫描数据库中到期的记录
- 简单但性能差,适合小规模
Pulsar延迟消息:
- 原生支持任意精度的延迟消息
- 基于时间分区实现
订单超时取消的最佳实践:
- 创建订单时发送延迟消息(如30分钟)
- 消息到期后检查订单状态
- 如果未支付则取消订单
- 注意幂等:订单可能已被用户取消或已支付
74. ⚫ 如何设计一个高并发的计数系统(如点赞数、阅读数)?
答:计数系统看似简单,但在高并发下面临严峻挑战。
挑战:
- 高并发写入(热门内容每秒数万次点赞)
- 高并发读取(每次展示都需要读取计数)
- 数据准确性(不能丢失计数)
架构设计:
写入路径:
- 用户点赞 → Redis INCR(原子操作,高性能)
- 异步同步到数据库(定时批量写入)
- 去重:Redis Set或布隆过滤器(防止重复点赞)
读取路径:
- 优先从Redis读取(毫秒级响应)
- Redis不可用时从数据库读取
- 本地缓存热门内容的计数
持久化:
- 定时将Redis中的计数同步到数据库
- 使用消息队列异步写入(削峰)
- 批量合并写入(减少数据库压力)
精确计数 vs 近似计数:
- 精确计数:Redis INCR + 去重(适合点赞、收藏)
- 近似计数:HyperLogLog(适合UV统计,误差约0.81%)
- 近似计数:计数器+随机采样(适合阅读数,允许小误差)
大V问题:
- 热门内容的计数器是热点key
- 解决:分片计数(将一个key拆分为多个子key,读取时求和)
- 如:
like_count:{contentId}:{0-9},写入时随机选择一个子key
75. ⚫ 如何设计一个分布式ID生成系统?
答:全局唯一ID是分布式系统的基础需求。
方案对比:
UUID:
- 128位,全局唯一
- 无序,不适合做数据库主键(B+树频繁分裂)
- 太长,存储和索引开销大
数据库自增:
- 简单可靠
- 单点瓶颈,性能有限
- 多库时需要设置不同步长
Snowflake(雪花算法):
- 64位:1位符号 + 41位时间戳 + 10位机器ID + 12位序列号
- 有序、高性能(单机每秒400万+)
- 依赖时钟,时钟回拨会导致ID重复
Leaf(美团):
- Segment模式:数据库号段,批量获取ID
- Snowflake模式:改进的雪花算法,解决时钟回拨
- 双Buffer:异步预加载下一个号段
Tinyid(滴滴):
- 类似Leaf Segment模式
- 支持多DB,高可用
时钟回拨解决方案:
- 等待:时钟追上后继续生成
- 使用上次的时间戳 + 序列号递增
- 备用机器ID:切换到备用的机器ID
- NTP配置:禁止大幅度时钟调整
选择建议:
- 简单场景:数据库自增
- 高性能场景:Snowflake或Leaf
- 无序可接受:UUID v7(时间有序的UUID)
76. ⚫ 如何设计一个通知系统(站内信、Push、短信、邮件)?
答:通知系统是用户触达的核心基础设施。
架构设计:
统一接入层:
- 提供统一的发送API
- 支持多种通知渠道(站内信、Push、短信、邮件、微信)
- 模板管理:通知内容模板化,支持变量替换
路由层:
- 根据通知类型和用户偏好选择渠道
- 降级策略:Push失败降级为短信
- 频率控制:防止骚扰用户(如每天最多5条营销短信)
渠道适配层:
- 每个渠道一个适配器(短信:阿里云/腾讯云、Push:APNs/FCM)
- 多供应商:主供应商故障自动切换到备用供应商
- 异步发送:通过消息队列解耦
存储层:
- 站内信:数据库存储(用户维度分表)
- 发送记录:用于审计和统计
- 已读状态:Redis BitMap(高效存储已读/未读)
关键设计:
- 幂等发送:相同的通知不重复发送
- 批量发送:支持百万级用户的批量通知
- 优先级队列:紧急通知(验证码)优先于营销通知
- 送达率监控:监控各渠道的送达率和失败率
77. ⚫ 如何设计一个电商的库存系统?
答:库存系统是电商的核心,直接影响超卖和用户体验。
库存模型:
- 可售库存:可以售卖的数量
- 锁定库存:已下单未支付的数量
- 实际库存:仓库中的实际数量
- 关系:可售库存 = 实际库存 - 锁定库存
扣减方案:
下单减库存:
- 下单时立即扣减库存
- 优点:不会超卖
- 缺点:恶意下单占库存(需要超时释放)
支付减库存:
- 支付成功后扣减库存
- 优点:不会被恶意占库存
- 缺点:可能超卖(多人同时下单)
预扣库存(推荐):
- 下单时锁定库存(预扣)
- 支付成功后确认扣减
- 超时未支付则释放锁定库存
高并发扣减:
- Redis预扣库存:
DECR stock:{skuId},原子操作 - 库存为0后内存标记,直接拒绝(不再查Redis)
- 异步同步到数据库:消息队列 + 批量更新
- 数据库兜底:
UPDATE SET stock=stock-1 WHERE sku_id=? AND stock>0
分仓库存:
- 多个仓库各自维护库存
- 就近发货:根据用户地址选择最近的仓库
- 库存调拨:仓库间库存转移
78. ⚫ 如何设计一个支付系统?
答:支付系统是金融级系统,对安全性和一致性要求极高。
核心模块:
- 收银台:统一的支付入口,展示可用支付方式
- 支付网关:路由到不同的支付渠道(微信、支付宝、银行卡)
- 支付核心:支付流水管理、状态机、幂等控制
- 渠道适配:对接各支付渠道的API
- 对账系统:与支付渠道对账,发现差异
- 清结算:资金清算和结算
支付流程:
- 用户选择支付方式,创建支付单
- 调用支付渠道API,获取支付凭证
- 用户完成支付(跳转/扫码)
- 接收支付渠道的异步通知
- 更新支付状态,通知业务系统
关键设计:
- 幂等性:支付流水号唯一,防止重复支付
- 状态机:严格的状态转换(待支付→支付中→已支付/已失败)
- 对账:每日与支付渠道对账,发现长款/短款
- 资金安全:所有资金操作记录流水,可追溯
- 异步通知:支付结果通过MQ通知业务系统
- 超时处理:支付超时自动关闭,释放库存
掉单处理:
- 用户支付成功但系统未收到通知
- 方案:定时查询支付渠道的支付状态(补单)
79. ⚫ 如何设计一个高性能的网关系统?
答:网关是所有流量的入口,性能直接影响整个系统。
高性能设计:
异步非阻塞:
- 基于Netty/OpenResty的事件驱动模型
- 少量线程处理大量并发连接
- 避免阻塞操作(同步IO、锁等待)
连接池复用:
- 与后端服务保持长连接
- HTTP/2多路复用
- 连接池大小根据后端服务能力配置
缓存:
- 路由规则缓存(避免每次查询配置中心)
- 认证结果缓存(JWT验证结果短期缓存)
- 响应缓存(GET请求的响应缓存)
插件链优化:
- 插件按需加载(不是所有请求都需要所有插件)
- 插件执行顺序优化(先执行快速失败的插件)
- 避免在插件中做重计算
零拷贝:
- 请求/响应体直接转发,不做不必要的序列化/反序列化
- 使用DirectBuffer减少内存拷贝
性能指标参考:
- Nginx:单机10万+ QPS
- APISIX:单机数万QPS(带插件)
- Spring Cloud Gateway:单机数千QPS
- Envoy:单机数万QPS
80. ⚫ 如何设计一个实时数据分析平台?
答:实时数据分析是数据驱动决策的基础。
Lambda架构:
- 批处理层:Hadoop/Spark处理全量数据,生成批视图
- 速度层:Flink/Storm处理实时数据,生成实时视图
- 服务层:合并批视图和实时视图,提供查询
- 缺点:维护两套代码(批处理和流处理)
Kappa架构:
- 只有流处理层,所有数据通过流处理
- Kafka作为数据源(保留足够长的数据)
- 需要重新计算时,从Kafka重放数据
- 优点:架构简单,只维护一套代码
实时数仓架构:
- 数据采集:Kafka接收业务数据(binlog/日志/埋点)
- 实时计算:Flink进行实时ETL和聚合计算
- 数据存储:
- 实时指标:ClickHouse/Doris(OLAP引擎)
- 明细数据:Hudi/Iceberg(数据湖)
- 数据服务:提供查询API和可视化Dashboard
技术选型:
| 组件 | 方案 |
|---|---|
| 消息队列 | Kafka |
| 流计算 | Flink |
| OLAP引擎 | ClickHouse/Doris/StarRocks |
| 数据湖 | Hudi/Iceberg/Delta Lake |
| 可视化 | Grafana/Superset |
81. 🔴 如何设计一个高可用的任务调度平台?
答:任务调度平台是企业级应用的核心基础设施。
核心需求:
- 支持Cron表达式和固定频率调度
- 分布式环境下任务不重复执行
- 任务失败自动重试和告警
- 支持任务依赖(DAG工作流)
- 任务执行日志和监控
架构设计:
调度器(Scheduler):
- 集群部署,通过选举产生Leader
- Leader负责触发任务,Follower待命
- 基于数据库锁或分布式锁保证单点触发
执行器(Executor):
- 注册到调度器,接收任务
- 支持多种执行模式:单机、广播、分片
- 执行结果回调给调度器
任务编排(DAG):
- 支持任务依赖关系
- 上游任务完成后触发下游任务
- 支持条件分支和并行执行
监控告警:
- 任务执行耗时、成功率监控
- 任务超时告警
- 任务积压告警
方案选择:
- 简单定时任务:XXL-JOB
- 复杂工作流:Apache Airflow、DolphinScheduler
- 大数据场景:Oozie、Azkaban
82. 🔴 如何设计一个数据迁移方案?
答:数据迁移是系统升级和架构演进中的高风险操作。
迁移策略:
停机迁移:
- 停止服务 → 迁移数据 → 验证 → 启动服务
- 最简单但需要停机窗口
- 适合数据量小、允许停机的场景
双写迁移:
- 新旧系统同时写入
- 全量迁移历史数据
- 切换读到新系统
- 停止写旧系统
CDC迁移:
- 全量导出历史数据到新系统
- 同时开启CDC(binlog监听)同步增量数据
- 数据追平后切换流量
迁移步骤:
- 准备阶段:评估数据量、制定迁移计划、准备回滚方案
- 全量迁移:导出历史数据,转换格式,导入新系统
- 增量同步:CDC同步增量数据
- 数据校验:对比新旧系统数据一致性
- 流量切换:灰度切换读流量到新系统
- 清理收尾:确认无误后停止旧系统
数据校验方法:
- 总量校验:对比记录数
- 抽样校验:随机抽取记录对比
- 全量校验:逐条对比(大数据量用MapReduce)
- 业务校验:关键业务指标对比
83. 🔴 如何设计一个多语言/国际化系统?
答:国际化(i18n)是全球化产品的基础能力。
核心设计:
文案管理:
- 所有用户可见的文案抽取为资源文件
- 支持多语言版本(zh-CN、en-US、ja-JP)
- 翻译管理平台:支持翻译人员在线翻译和审核
时区处理:
- 服务端统一使用UTC时间存储
- 客户端根据用户时区转换显示
- 数据库使用TIMESTAMP WITH TIME ZONE
货币处理:
- 使用BigDecimal存储金额(避免浮点精度问题)
- 存储原始货币和金额
- 汇率转换在展示层处理
日期格式:
- 不同地区日期格式不同(MM/DD/YYYY vs DD/MM/YYYY)
- 使用ICU库处理本地化格式
排序和搜索:
- 不同语言的排序规则不同(Collation)
- 搜索需要支持多语言分词
技术实现:
- 前端:i18next、vue-i18n、react-intl
- 后端:Spring MessageSource、ResourceBundle
- 数据库:UTF-8编码,支持多语言存储
84. 🔴 如何设计一个高并发的抽奖系统?
答:抽奖系统需要兼顾公平性、性能和防作弊。
核心设计:
奖品池管理:
- 奖品库存预加载到Redis
- 原子扣减:Redis DECR
- 库存为0后直接返回未中奖
抽奖算法:
- 概率抽奖:根据各奖品概率随机
- 权重抽奖:按权重分配概率
- 保底机制:N次未中奖后必中
防作弊:
- 用户级限流(每人每天最多N次)
- IP限流
- 设备指纹识别
- 行为分析(异常频率检测)
异步发奖:
- 抽奖结果立即返回
- 发奖通过消息队列异步处理
- 发奖失败重试 + 人工补发
架构:
- 接入层: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、个人信息保护法)的基本要求。
脱敏类型:
- 静态脱敏:对存储的数据进行脱敏(如测试环境数据)
- 动态脱敏:查询时实时脱敏(如日志中的手机号)
脱敏规则:
- 手机号:138****1234
- 身份证:110***********1234
- 银行卡:6222 **** **** 1234
- 邮箱:a***@example.com
- 姓名:张*
实现方案:
应用层脱敏:
- 自定义注解 + AOP拦截
- Jackson序列化时脱敏
- 优点:灵活,可按角色控制
- 缺点:需要修改代码
数据库层脱敏:
- 数据库视图(脱敏视图)
- PostgreSQL的列级安全策略
- ShardingSphere的数据脱敏功能
网关层脱敏:
- 在API网关统一脱敏
- 根据用户角色决定是否脱敏
- 优点:统一管理,不侵入业务代码
日志脱敏:
- Logback/Log4j2自定义Layout
- 正则匹配敏感信息并替换
87. ⚫ 如何设计一个工作流引擎?
答:工作流引擎是企业应用中审批、流程管理的核心。
核心概念:
- 流程定义(Process Definition):流程模板(如请假审批流程)
- 流程实例(Process Instance):一次具体的流程执行
- 任务(Task):流程中的一个步骤(如主管审批)
- 网关(Gateway):流程分支和合并(排他网关、并行网关)
- 事件(Event):流程中的事件(开始、结束、定时器、消息)
核心设计:
流程引擎:
- 解析流程定义(BPMN 2.0标准)
- 驱动流程实例的执行
- 管理任务的创建、分配、完成
任务分配:
- 指定人:直接指定处理人
- 角色:分配给某个角色的所有人
- 规则:根据业务规则动态计算处理人
- 抢占:多人可见,先抢先得
流程控制:
- 会签:所有人都审批通过才通过
- 或签:任一人审批通过即通过
- 加签:审批过程中临时增加审批人
- 退回:退回到上一步或指定步骤
- 撤回:发起人撤回流程
主流方案:
- Flowable:Activiti的分支,功能最全
- Camunda:轻量级,支持BPMN和DMN
- 自研:简单场景可以用状态机实现
88. 🔴 如何设计一个高效的文件上传系统?
答:文件上传在大文件和高并发场景下需要特殊设计。
核心功能:
分片上传:
- 大文件切分为固定大小的分片(如5MB)
- 每个分片独立上传
- 所有分片上传完成后合并
- 支持并行上传多个分片
断点续传:
- 记录已上传的分片信息
- 网络中断后只上传未完成的分片
- 服务端记录上传进度(Redis)
秒传:
- 上传前计算文件MD5/SHA256
- 服务端检查是否已存在相同Hash的文件
- 已存在则直接返回成功(不需要实际上传)
存储:
- 小文件:直接存储到对象存储(MinIO/S3)
- 大文件:分片存储,元数据记录分片信息
- CDN加速:上传完成后分发到CDN
上传流程:
- 客户端计算文件Hash,请求秒传检查
- 如果文件已存在,秒传成功
- 如果不存在,请求上传凭证(预签名URL)
- 客户端直传到对象存储(不经过应用服务器)
- 上传完成后回调应用服务器
89. 🔴 如何设计一个实时消息系统(IM)?
答:IM系统是技术复杂度最高的系统之一。
核心架构:
长连接层:
- WebSocket/TCP长连接
- 连接管理:用户ID → 连接的映射
- 心跳保活:定时心跳检测连接存活
- 多端同步:同一用户多个设备同时在线
消息路由:
- 单聊:查找接收者的连接,直接推送
- 群聊:查找群成员的连接,批量推送
- 离线消息:接收者不在线时存储到离线消息队列
消息存储:
- 消息索引:用户维度的消息列表(收件箱模型)
- 消息内容:独立存储,通过消息ID关联
- 读扩散 vs 写扩散(类似Feed流的推拉模型)
消息可靠性:
- 消息ACK机制:客户端确认收到消息
- 消息重试:未ACK的消息重新推送
- 消息去重:客户端根据消息ID去重
- 消息顺序:单聊保证有序(序列号递增)
关键技术:
- 长连接管理:Netty
- 消息队列:Kafka(高吞吐)
- 消息存储:MySQL + Redis(最近消息缓存)
- 在线状态:Redis(用户在线状态和连接信息)
90. ⚫ 如何设计一个推荐系统?
答:推荐系统是提升用户体验和商业价值的核心系统。
架构设计(召回→粗排→精排→重排):
召回层:从海量候选中快速筛选出千级候选集
- 协同过滤:基于用户行为的相似度
- 内容召回:基于物品特征的相似度
- 热门召回:热门物品兜底
- 向量召回:Embedding相似度(ANN检索)
粗排层:将千级候选缩减到百级
- 轻量级模型快速打分
- 过滤不合适的候选
精排层:精确排序
- 深度学习模型(DeepFM、DIN、DIEN)
- 特征工程:用户特征、物品特征、上下文特征
- 实时特征:用户最近的行为
重排层:业务规则调整
- 多样性:避免推荐结果过于单一
- 去重:去除已曝光/已购买的物品
- 运营干预:置顶、降权
实时推荐架构:
- 用户行为 → Kafka → Flink实时计算 → 更新用户特征
- 推荐请求 → 召回 → 排序 → 返回结果
- 特征存储:Redis(实时特征)+ HBase(离线特征)
91. 🔴 如何设计一个AB测试平台?
答:AB测试是数据驱动决策的基础设施。
核心模块:
实验管理:
- 创建实验:定义实验名称、流量比例、实验参数
- 实验分组:对照组和实验组
- 实验生命周期:草稿→运行→结束→归档
流量分配:
- 基于用户ID的Hash分流(保证同一用户始终在同一组)
- 支持多层实验(正交实验:不同层的实验互不影响)
- 流量隔离:互斥实验不能分配到同一用户
数据收集:
- 埋点数据收集(曝光、点击、转化)
- 实时数据处理(Flink)
- 指标计算(转化率、留存率、收入等)
统计分析:
- 假设检验(t检验、卡方检验)
- 置信区间计算
- 统计显著性判断(p-value < 0.05)
- 样本量计算(实验需要多少用户才有统计意义)
分流算法:
1 | bucket = hash(userId + experimentId) % 1000 |
多层实验(正交):
- 每层独立分流
- 用户在不同层可以属于不同组
- 层间实验互不干扰
92. 🔴 如何设计一个高可用的文件导出系统?
答:大数据量的文件导出是企业应用中的常见需求。
挑战:
- 数据量大(百万行Excel)
- 导出耗时长(分钟级)
- 内存压力(大文件生成时的内存占用)
- 并发导出(多用户同时导出)
架构设计:
异步导出:
- 用户提交导出请求 → 返回任务ID
- 后台异步生成文件
- 生成完成后通知用户下载
分片处理:
- 大数据量分页查询(游标分页)
- 流式写入文件(不全部加载到内存)
- EasyExcel:阿里开源,支持百万行Excel流式写入
文件存储:
- 生成的文件上传到对象存储(MinIO/S3)
- 返回下载链接(预签名URL,有效期限制)
并发控制:
- 限制同一用户的并发导出数
- 导出任务队列(消息队列)
- 执行器集群消费任务
进度反馈:
- WebSocket推送导出进度
- 或轮询查询导出状态
93. 🔴 如何设计一个配置驱动的规则引擎?
答:规则引擎将业务规则从代码中抽离,实现业务逻辑的动态配置。
应用场景:
- 风控规则:交易金额>10000且新用户 → 人工审核
- 营销规则:满200减30、新用户首单优惠
- 路由规则:根据用户属性路由到不同服务
设计方案:
简单规则引擎:
- 条件-动作模型(if-then)
- 规则存储在数据库或配置中心
- 支持动态添加和修改规则
表达式引擎:
- 使用表达式语言(SpEL、MVEL、Aviator)
- 规则以表达式形式存储
- 运行时解析和执行表达式
决策表:
- 以表格形式定义规则
- 行是条件组合,列是动作
- 适合条件组合较多的场景
专业规则引擎:
- Drools:Java生态最成熟的规则引擎
- Easy Rules:轻量级规则引擎
- LiteFlow:国产规则编排引擎
性能优化:
- 规则编译缓存(避免每次解析)
- Rete算法(Drools使用,高效的模式匹配)
- 规则优先级和短路评估
94. ⚫ 如何设计一个多数据中心的数据同步方案?
答:多数据中心数据同步是异地多活架构的核心挑战。
同步模式:
主从同步:一个数据中心为主,其他为从
- 写入只在主中心
- 从中心只读
- 简单但主中心是单点
双向同步:两个数据中心互相同步
- 两个中心都可以写入
- 需要解决数据冲突
多主同步:多个数据中心都可以写入
- 最复杂,冲突解决最困难
数据冲突解决:
- Last Write Wins(LWW):最后写入的胜出(需要全局时钟)
- 业务规则:根据业务逻辑决定(如金额取较大值)
- CRDT:无冲突复制数据类型(数学保证最终一致)
- 人工介入:冲突记录下来,人工处理
同步工具:
- MySQL:Canal、DTS(阿里云)
- PostgreSQL:Logical Replication、BDR
- Redis:Redis Enterprise的Active-Active
- Kafka:MirrorMaker 2
单元化避免冲突:
- 用户按规则分配到固定的数据中心
- 用户的所有数据只在一个数据中心写入
- 跨中心只同步只读副本
- 从根本上避免数据冲突
95. 🔴 如何设计一个高效的批处理系统?
答:批处理是企业应用中处理大量数据的核心能力。
核心设计:
分片并行:
- 将大任务拆分为多个小任务
- 多个Worker并行处理
- 分片策略:按ID范围、按Hash、按时间
流式处理:
- 不要一次加载所有数据到内存
- 使用游标/流式查询
- 边读边处理边写
断点续跑:
- 记录处理进度(已处理到哪条记录)
- 失败后从断点继续
- 每个分片独立记录进度
错误处理:
- 跳过错误记录,继续处理
- 错误记录写入错误表,后续人工处理
- 重试策略:可重试错误自动重试
Spring Batch架构:
- Job:一个批处理任务
- Step:Job中的一个步骤
- ItemReader:读取数据
- ItemProcessor:处理数据
- ItemWriter:写入数据
- Chunk:分块处理(读N条→处理→写入)
性能优化:
- 批量写入(JDBC Batch Insert)
- 多线程Step(并行处理多个Chunk)
- 分区Step(将数据分区到多个Worker)
- 异步ItemProcessor(处理逻辑异步执行)
96. 🔴 如何设计一个API限流和计费系统?
答:API限流和计费是开放平台的核心能力。
限流设计:
多维度限流:
- 用户级:每个用户每秒N次请求
- API级:每个API每秒M次请求
- 全局级:整个平台每秒K次请求
配额管理:
- 免费配额:每月1000次调用
- 付费配额:按套餐分配(基础版10万次/月,专业版100万次/月)
- 超额处理:拒绝/降速/按量计费
分布式限流:
- Redis + Lua脚本实现滑动窗口
- 令牌桶算法(支持突发流量)
- 集群限流:所有网关节点共享计数器
计费设计:
- 计量:记录每次API调用
- 计费:根据调用次数和套餐计算费用
- 账单:生成月度账单
- 扣费:从用户余额扣除或生成应收账款
技术实现:
- 调用记录:Kafka异步写入(不影响API性能)
- 实时计量:Flink实时聚合调用次数
- 配额检查:Redis存储剩余配额,原子扣减
- 账单生成:定时任务月度汇总
97. ⚫ 如何设计一个多租户的数据隔离和资源隔离方案?
答:这是SaaS架构中最核心的设计决策。
数据隔离方案选择:
| 方案 | 隔离性 | 成本 | 运维复杂度 | 适用场景 |
|---|---|---|---|---|
| 独立数据库 | 最高 | 最高 | 高 | 大客户、合规要求 |
| 共享数据库独立Schema | 高 | 中 | 中 | 中型客户 |
| 共享表+tenant_id | 低 | 最低 | 低 | 小客户、标准化产品 |
混合方案(推荐):
- 大客户:独立数据库(满足合规和性能要求)
- 中型客户:独立Schema
- 小客户:共享表
资源隔离:
计算隔离:
- 独立集群:大客户独享计算资源
- 共享集群 + 资源配额:Kubernetes ResourceQuota
- 优先级队列:VIP客户请求优先处理
存储隔离:
- 独立存储卷
- 存储配额限制
- 备份策略按租户定制
网络隔离:
- Kubernetes NetworkPolicy
- 独立VPC(大客户)
- 流量限制
噪声邻居问题:
- 一个租户的高负载影响其他租户
- 解决:资源配额 + 限流 + 弹性伸缩
98. ⚫ 如何设计一个全局事务协调器?
答:全局事务协调器是分布式事务的核心基础设施。
核心设计(以Seata为参考):
TC(Transaction Coordinator):事务协调器
- 维护全局事务和分支事务的状态
- 驱动全局提交或回滚
- 高可用:集群部署,数据持久化到数据库
TM(Transaction Manager):事务管理器
- 定义全局事务的边界
- 开始全局事务、提交或回滚
RM(Resource Manager):资源管理器
- 管理分支事务的资源
- 向TC注册分支事务
- 执行分支提交或回滚
Seata的四种模式:
AT模式:自动补偿,基于undo_log
- 一阶段:执行SQL,记录undo_log
- 二阶段提交:删除undo_log
- 二阶段回滚:根据undo_log反向补偿
- 优点:无侵入
- 缺点:需要全局锁,性能有损
TCC模式:手动补偿
SAGA模式:长事务
XA模式:数据库XA协议
高可用设计:
- TC集群部署(3节点以上)
- 事务日志持久化(MySQL/Redis)
- TC故障后,新TC接管未完成的事务
- 超时机制:事务超时自动回滚
99. ⚫ 如何设计一个可扩展的插件化架构?
答:插件化架构是实现系统可扩展性的核心模式。
核心设计:
插件接口(SPI):
- 定义标准的插件接口
- 插件实现接口,提供具体功能
- Java SPI、Spring的@Component扫描
插件生命周期:
- 加载:发现并加载插件
- 初始化:插件初始化资源
- 启动:插件开始工作
- 停止:插件停止工作
- 卸载:释放资源
插件隔离:
- 类加载器隔离:每个插件使用独立的ClassLoader
- 避免插件之间的类冲突
- OSGi、SOFAArk、PF4J
插件通信:
- 事件总线:插件之间通过事件通信
- 服务注册:插件注册服务,其他插件调用
应用案例:
- IDEA插件:基于Extension Point机制
- Dubbo SPI:增强的Java SPI,支持自适应扩展
- Spring Boot Starter:自动配置机制
- Webpack插件:Tapable事件机制
设计原则:
- 开闭原则:对扩展开放,对修改关闭
- 插件不应该影响核心系统的稳定性
- 插件故障隔离:一个插件崩溃不影响其他插件
- 热插拔:支持运行时加载和卸载插件
100. ⚫ 如何设计一个高性能的网络通信框架?
答:网络通信框架是中间件和分布式系统的基石。
核心设计:
IO模型:
- BIO:一个连接一个线程,不适合高并发
- NIO:多路复用(select/poll/epoll),少量线程处理大量连接
- AIO:异步IO,操作系统完成IO后通知应用
线程模型(Reactor模式):
- 单Reactor单线程:Redis的模型
- 单Reactor多线程:一个线程接收连接,多个线程处理业务
- 主从Reactor:主Reactor接收连接,从Reactor处理IO(Netty默认)
协议设计:
- 魔数:标识协议(如0xCAFEBABE)
- 版本号:协议版本
- 消息类型:请求/响应/心跳
- 序列化方式:JSON/Protobuf/Hessian
- 消息长度:解决粘包/拆包
- 消息体:实际数据
序列化:
- JSON:可读性好,性能一般
- Protobuf:高性能,强类型,跨语言
- Hessian:Java生态,Dubbo默认
- Kryo:Java高性能序列化
连接管理:
- 连接池:复用连接,减少建连开销
- 心跳保活:定时发送心跳检测连接存活
- 空闲检测:关闭长时间空闲的连接
- 优雅关闭:关闭前处理完已有请求
六、架构哲学与软技能(101-120题)
101. ⚫ 什么是架构决策记录(ADR)?为什么重要?
答:ADR(Architecture Decision Record)是记录架构决策的轻量级文档。
ADR模板:
1 | # ADR-001: 选择Kafka作为消息队列 |
为什么重要:
- 记录”为什么”而不仅仅是”是什么”
- 新成员可以理解历史决策的背景
- 避免重复讨论已经做过的决策
- 决策可追溯,方便后续review
102. ⚫ 你如何评估一个技术方案的好坏?
答:技术方案评估需要多维度权衡。
评估维度:
- 功能满足度:能否满足当前和可预见的未来需求
- 性能:延迟、吞吐量、资源消耗
- 可靠性:故障率、恢复时间、数据安全
- 可扩展性:能否应对业务增长
- 可维护性:代码复杂度、文档、团队理解成本
- 成本:开发成本、运维成本、基础设施成本
- 团队匹配度:团队是否有能力实施和维护
- 风险:技术风险、时间风险、人员风险
- 生态成熟度:社区活跃度、文档质量、商业支持
评估方法:
- 加权评分法:每个维度打分,按权重计算总分
- POC验证:关键技术点做原型验证
- 压测验证:性能相关的方案必须压测
- 同行评审:邀请其他架构师review
常见误区:
- 只看技术先进性,忽视团队能力
- 过度设计,为未来不确定的需求买单
- 跟风选型,没有结合自身场景
- 忽视运维成本(选型时只看开发成本)
103. ⚫ 什么是技术债务?如何管理技术债务?
答:技术债务是为了短期速度而牺牲长期质量的技术决策。
技术债务类型:
- 有意的:明知有更好的方案,但为了赶工期选择了快速方案
- 无意的:当时不知道有更好的方案,后来发现了
- 过时的:当时是好方案,但技术演进后变成了债务
技术债务的影响:
- 开发速度越来越慢
- Bug越来越多
- 新人上手越来越难
- 系统越来越脆弱
管理策略:
- 可视化:将技术债务记录在看板上(与业务需求同等对待)
- 量化:评估每项债务的影响和修复成本
- 优先级:按影响/成本比排序
- 持续偿还:每个迭代分配20%的时间偿还技术债务
- 预防:代码review、架构守护、自动化测试
何时可以接受技术债务:
- 验证商业假设(MVP阶段)
- 紧急修复(先止血再优化)
- 明确的偿还计划(不是无限期拖延)
何时必须偿还:
- 影响开发效率(每次修改都很痛苦)
- 影响系统稳定性(频繁出故障)
- 影响安全性(存在安全漏洞)
104. ⚫ 架构师如何做技术选型?
答:技术选型是架构师最重要的决策之一。
选型流程:
- 明确需求:功能需求、非功能需求(性能、可用性、安全性)
- 候选方案:列出所有可选方案(至少3个)
- 评估对比:多维度对比(功能、性能、成本、团队、生态)
- POC验证:关键方案做原型验证
- 决策记录:记录决策过程和理由(ADR)
- 持续评估:定期review选型是否仍然合适
选型原则:
- 合适优于先进:选择最适合当前场景的,不是最新最酷的
- 成熟优于新颖:生产环境优先选择经过验证的技术
- 团队优于技术:团队能驾驭的技术优于理论上更好的技术
- 简单优于复杂:能用简单方案解决的不要用复杂方案
- 社区优于自研:有成熟开源方案的不要自研
反模式:
- 简历驱动开发(为了简历好看选择新技术)
- 锤子效应(手里有锤子看什么都是钉子)
- 从众心理(别人用什么我就用什么)
- 过度自研(什么都想自己造轮子)
105. ⚫ 如何做架构评审?评审的关键点是什么?
答:架构评审是保证架构质量的重要机制。
评审内容:
- 需求理解:是否正确理解了业务需求
- 方案合理性:方案是否能满足需求,是否过度设计
- 技术选型:选型是否合理,是否有更好的选择
- 性能设计:是否考虑了性能瓶颈和优化方案
- 高可用设计:是否有单点故障,故障恢复方案
- 安全设计:是否考虑了安全威胁和防护措施
- 可扩展性:是否能应对业务增长
- 兼容性:是否与现有系统兼容,迁移方案
- 成本评估:开发成本、运维成本、基础设施成本
- 风险评估:技术风险、时间风险、人员风险
评审流程:
- 方案提交(提前发给评审人员)
- 方案宣讲(15-30分钟)
- 提问讨论(30-60分钟)
- 结论:通过/有条件通过/不通过
- 跟踪:有条件通过的需要跟踪改进
评审技巧:
- 关注”为什么”而不是”是什么”
- 追问边界情况和异常场景
- 关注方案的权衡(trade-off)
- 不要纠结于细节,关注架构层面的问题
106. ⚫ 康威定律是什么?如何影响架构设计?
答:康威定律是软件架构与组织结构关系的经典理论。
康威定律:
设计系统的组织,其产生的设计等同于组织之间的沟通结构。
简单说:系统架构 = 组织架构的映射。
实际影响:
- 3个团队开发一个编译器 → 编译器会有3个阶段
- 前后端分离的团队 → 系统会有前后端分离的架构
- 按业务线划分的团队 → 系统会按业务线拆分微服务
逆康威定律(Inverse Conway Maneuver):
- 如果想要某种架构,先调整组织结构
- 想要微服务架构 → 先按业务域划分团队
- 想要平台化 → 先建立平台团队
团队拓扑(Team Topologies):
- 流对齐团队(Stream-aligned):负责一个业务流的端到端交付
- 平台团队(Platform):提供内部平台和工具
- 赋能团队(Enabling):帮助其他团队提升能力
- 复杂子系统团队(Complicated Subsystem):负责复杂的技术子系统
架构师的角色:
- 不仅设计技术架构,还要影响组织架构
- 确保组织结构与目标架构匹配
- 推动跨团队的技术标准和规范
107. ⚫ 什么是演进式架构?如何实践?
答:演进式架构是指能够支持增量变化的架构。
核心理念:
- 架构不是一次性设计好的,而是持续演进的
- 拥抱变化,而不是抵抗变化
- 做出可逆的决策,推迟不可逆的决策
关键实践:
适应度函数(Fitness Function):
- 定义架构的质量指标
- 自动化验证架构是否符合预期
- 如:模块间依赖不能有循环、API响应时间<200ms
架构守护:
- ArchUnit:Java架构规则测试
- 依赖检查:防止不允许的依赖关系
- API兼容性检查:防止破坏性变更
增量变更:
- 小步迭代,每次只改一小部分
- 特性开关(Feature Toggle)控制新功能的发布
- 蓝绿部署、金丝雀发布降低变更风险
可逆性:
- 优先选择可逆的决策
- 不可逆的决策要更加谨慎
- 数据库Schema变更要向后兼容
ArchUnit示例:
1 |
|
108. ⚫ 如何平衡系统的一致性和可用性?
答:一致性和可用性的权衡是架构设计中最核心的决策。
决策框架:
业务分析:这个数据/操作对一致性的要求有多高?
- 资金相关:强一致性(不能多扣/少扣)
- 库存相关:准强一致性(不能超卖,但可以少卖)
- 统计相关:最终一致性(允许短暂不准确)
- 展示相关:弱一致性(缓存延迟可接受)
成本分析:强一致性的代价是什么?
- 性能下降(同步等待)
- 可用性降低(部分节点故障时不可用)
- 实现复杂度增加
折中方案:
- 核心路径强一致,非核心路径最终一致
- 写入强一致,读取最终一致
- 正常情况强一致,故障时降级为最终一致
实际案例:
- 电商下单:库存扣减强一致(Redis原子操作),订单创建最终一致(MQ异步)
- 银行转账:账户余额强一致(分布式事务),交易记录最终一致(异步写入)
- 社交点赞:计数最终一致(异步聚合),去重强一致(Redis Set)
关键认知:
- 没有银弹,只有权衡
- 同一个系统的不同部分可以有不同的一致性级别
- 一致性级别应该由业务需求决定,而不是技术偏好
109. ⚫ 如何做好架构的可观测性设计?
答:可观测性是理解系统内部状态的能力,是运维和排障的基础。
可观测性三支柱:
指标(Metrics):数值型的时间序列数据
- 系统指标:CPU、内存、磁盘、网络
- 应用指标:QPS、RT、错误率
- 业务指标:订单量、支付成功率
- 工具:Prometheus + Grafana
日志(Logs):离散的事件记录
- 结构化日志(JSON格式)
- 统一的日志格式和字段
- 工具:ELK/EFK、Loki
链路追踪(Traces):请求在系统中的完整路径
- 跨服务的调用链路
- 每个Span的耗时和状态
- 工具:Jaeger、SkyWalking
OpenTelemetry(统一标准):
- 统一的API和SDK
- 统一的数据格式(OTLP协议)
- 支持Metrics、Logs、Traces
- 厂商无关,可以对接任何后端
可观测性设计原则:
- 从设计阶段就考虑可观测性(不是事后补充)
- 关键业务路径必须有完整的追踪
- 告警基于SLO,而不是基于资源指标
- 建立Runbook:每个告警对应一个处理手册
110. ⚫ 架构师如何推动技术变革?
答:技术变革不仅是技术问题,更是组织和人的问题。
推动策略:
建立共识:
- 用数据说话(当前系统的问题、成本、风险)
- 展示变革的收益(性能提升、成本降低、效率提高)
- 获得管理层的支持
小步验证:
- 选择一个小项目做试点
- 用成功案例证明方案可行
- 逐步扩大范围
降低风险:
- 制定详细的迁移计划
- 准备回滚方案
- 灰度发布,逐步切换
赋能团队:
- 培训和分享
- 编写文档和最佳实践
- 建立内部社区
持续跟进:
- 定期review进展
- 及时解决遇到的问题
- 调整计划适应实际情况
常见阻力:
- “现在的系统跑得好好的,为什么要改?”
- “我们没有时间做这个”
- “这个技术我们不熟悉”
- “上次改造就失败了”
应对方式:
- 用数据和案例说服,而不是用权威
- 从痛点出发,解决实际问题
- 提供培训和支持,降低学习成本
- 承认风险,展示风险控制措施
111. ⚫ 什么是架构的简单性原则?如何在实践中应用?
答:简单性是好架构的核心特征。
KISS原则(Keep It Simple, Stupid):
- 能用简单方案解决的不要用复杂方案
- 复杂性是系统最大的敌人
- 每增加一层抽象都有成本
过度设计的信号:
- 为了”可能”的需求增加复杂度
- 使用了团队不熟悉的技术
- 系统中有大量未使用的抽象层
- 新人需要很长时间才能理解系统
简单性实践:
- YAGNI(You Aren’t Gonna Need It):不要为未来不确定的需求设计
- 最少组件:能用一个组件解决的不要用两个
- 最少抽象:能直接实现的不要过度抽象
- 最少依赖:减少外部依赖,降低系统复杂度
案例:
- 日活1000的系统不需要微服务架构
- 数据量100万的表不需要分库分表
- 单机能扛住的流量不需要分布式缓存
- 简单的CRUD不需要DDD
架构师的价值不在于设计复杂的系统,而在于用最简单的方案解决复杂的问题。
112. ⚫ 如何设计系统的可测试性?
答:可测试性是软件质量的基础,也是持续交付的前提。
可测试性设计原则:
- 依赖注入:通过接口注入依赖,方便Mock
- 关注点分离:业务逻辑与基础设施分离
- 纯函数:相同输入总是产生相同输出,无副作用
- 小方法:每个方法只做一件事,容易测试
测试金字塔:
- 单元测试(70%):测试单个类/方法,快速、稳定
- 集成测试(20%):测试模块间的交互,如数据库、缓存
- 端到端测试(10%):测试完整的业务流程
微服务测试策略:
- 单元测试:测试业务逻辑
- 组件测试:测试单个微服务(Mock外部依赖)
- 契约测试:测试服务间的接口兼容性
- 集成测试:测试服务间的实际交互
- 端到端测试:测试完整的业务流程
提高可测试性的架构模式:
- 六边形架构:核心逻辑不依赖外部,容易测试
- CQRS:读写分离,各自独立测试
- 事件驱动:通过事件验证行为
测试基础设施:
- Testcontainers:用Docker容器运行测试依赖(数据库、Redis)
- WireMock:Mock HTTP服务
- Embedded Kafka:内嵌Kafka用于测试
113. ⚫ 如何做好系统的容量评估?
答:容量评估是保障系统稳定性的前置工作。
评估步骤:
业务量预估:
- 用户数增长预测
- 核心业务量预测(订单量、消息量)
- 峰值系数(日常的3-10倍)
流量模型:
- 读写比例(如读:写 = 10:1)
- 请求大小(平均请求/响应大小)
- 并发模型(同时在线用户数)
资源计算:
- CPU:QPS × 单请求CPU时间
- 内存:并发连接数 × 单连接内存 + 缓存大小
- 磁盘:数据量 × 副本数 × 增长系数
- 网络:QPS × 请求大小 × 2(请求+响应)
压测验证:
- 单机压测:确定单实例的极限
- 集群压测:验证集群的线性扩展能力
- 全链路压测:验证整个系统的容量
冗余设计:
- 预留30-50%的冗余容量
- 考虑故障场景(一个节点挂了,剩余节点能否承受)
容量计算示例:
- 日活100万,每用户日均10次请求
- 日请求量 = 1000万
- 平均QPS = 1000万 / 86400 ≈ 116
- 峰值QPS = 116 × 5 = 580
- 单机QPS = 200(压测得出)
- 所需实例 = 580 / 200 × 1.5(冗余)≈ 5台
114. ⚫ 如何处理遗留系统?
答:遗留系统是每个架构师都会面对的挑战。
评估遗留系统:
- 业务价值:系统是否仍然有业务价值?
- 技术状态:代码质量、技术栈、可维护性
- 风险:安全漏洞、性能瓶颈、单点故障
- 成本:维护成本、改造成本
处理策略:
- 维持现状:系统稳定且维护成本可接受
- 渐进改造:绞杀者模式,逐步替换
- 包装(Wrap):用API包装遗留系统,对外提供新接口
- 重写:完全重写(风险最高,慎用)
绞杀者模式实践:
- 在遗留系统前面加一个路由层(API Gateway)
- 新功能用新系统实现
- 旧功能逐步迁移到新系统
- 路由层逐步将流量切换到新系统
- 最终下线遗留系统
关键原则:
- 不要大爆炸重写(失败率极高)
- 先理解再改造(不理解的代码不要动)
- 加测试再重构(没有测试的重构是冒险)
- 保持系统始终可用(改造过程中不能停服)
115. ⚫ 架构师需要具备哪些软技能?
答:架构师不仅是技术专家,更是技术领导者。
核心软技能:
沟通能力:
- 能向非技术人员解释技术方案
- 能倾听不同意见并整合
- 能写清晰的技术文档
影响力:
- 不靠权威而靠专业能力影响团队
- 能推动技术决策的落地
- 能在组织中建立技术影响力
权衡能力:
- 在多个方案中做出合理选择
- 理解每个决策的trade-off
- 在理想和现实之间找到平衡
全局视野:
- 理解业务目标和技术目标的关系
- 关注系统整体而不是局部
- 考虑长期影响而不仅是短期效果
学习能力:
- 持续学习新技术和新理念
- 从失败中学习
- 保持技术敏感度
决策能力:
- 在信息不完整时做出决策
- 区分可逆和不可逆的决策
- 敢于承担决策的后果
116. ⚫ 什么是架构的适应度函数?如何定义?
答:适应度函数是衡量架构是否满足预期目标的自动化检查。
适应度函数类型:
原子适应度函数:检查单一维度
- 代码覆盖率 > 80%
- API响应时间P99 < 500ms
- 循环依赖数 = 0
整体适应度函数:检查多个维度的组合
- 可用性 > 99.9% AND 延迟P99 < 200ms
- 安全扫描通过 AND 性能测试通过
触发式适应度函数:特定事件触发
- 每次代码提交触发架构规则检查
- 每次部署触发性能测试
持续式适应度函数:持续运行
- 生产环境的SLO监控
- 安全漏洞持续扫描
实现工具:
- ArchUnit:Java架构规则测试
- Fitness Function Runner:自定义适应度函数框架
- SonarQube:代码质量检查
- Prometheus + AlertManager:运行时指标监控
示例:
1 | // 领域层不能依赖基础设施层 |
117. ⚫ 如何设计一个高效的技术团队协作模式?
答:技术团队的协作模式直接影响交付效率和系统质量。
团队模式:
特性团队(Feature Team):
- 跨职能团队,端到端负责一个业务特性
- 包含前端、后端、测试、运维
- 优点:交付速度快,沟通成本低
- 缺点:技术深度可能不够
组件团队(Component Team):
- 按技术组件划分(前端团队、后端团队、DBA团队)
- 优点:技术深度好
- 缺点:跨团队协调成本高
平台团队 + 业务团队:
- 平台团队提供基础设施和工具
- 业务团队专注业务开发
- 平台即产品(Platform as a Product)
内部开源模式:
- 核心代码由负责团队维护
- 其他团队可以提交PR
- 代码review后合并
- 降低跨团队协调成本
架构师在团队中的角色:
- 技术方向的引导者(不是独裁者)
- 技术标准的制定者和守护者
- 团队能力的提升者
- 跨团队协调的桥梁
118. ⚫ 如何做好系统的安全架构设计?
答:安全是架构设计中不可忽视的维度。
安全架构层次:
网络安全:
- 防火墙、WAF(Web应用防火墙)
- DDoS防护
- 网络隔离(VPC、安全组)
应用安全:
- 输入验证(防SQL注入、XSS)
- 认证授权(OAuth、JWT、RBAC)
- 加密(传输TLS、存储AES)
- 安全编码规范
数据安全:
- 数据分类分级
- 敏感数据加密存储
- 数据脱敏
- 数据备份和恢复
运维安全:
- 最小权限原则
- 操作审计
- 密钥管理(Vault)
- 安全扫描(SAST/DAST)
OWASP Top 10防护:
- SQL注入:参数化查询、ORM
- XSS:输出编码、CSP
- CSRF:Token验证
- 敏感数据泄露:加密、脱敏
- 认证失效:多因素认证、Session管理
安全左移(Shift Left Security):
- 在开发阶段就考虑安全
- 代码review包含安全检查
- CI/CD集成安全扫描
- 安全培训和意识提升
119. ⚫ 如何评估和管理架构风险?
答:风险管理是架构师的核心职责之一。
风险识别:
- 技术风险:新技术不成熟、性能不达标、兼容性问题
- 架构风险:单点故障、扩展性不足、安全漏洞
- 人员风险:关键人员离职、团队能力不足
- 进度风险:需求变更、技术难点、依赖延迟
- 运维风险:部署复杂、监控不足、故障恢复慢
风险评估矩阵:
| 概率\影响 | 低影响 | 中影响 | 高影响 |
|---|---|---|---|
| 高概率 | 中风险 | 高风险 | 极高风险 |
| 中概率 | 低风险 | 中风险 | 高风险 |
| 低概率 | 低风险 | 低风险 | 中风险 |
风险应对策略:
- 规避:改变方案,避免风险发生
- 缓解:降低风险的概率或影响
- 转移:将风险转移给第三方(如使用云服务)
- 接受:风险可控,接受并准备应急方案
实践建议:
- 项目启动时做风险评估
- 定期review风险清单
- 为高风险项准备Plan B
- 关键技术点做POC验证
120. ⚫ 你认为好的架构应该具备哪些特征?
答:这是一个开放性问题,考察架构师的架构哲学。
好架构的特征:
- 简单:能用简单方案解决的不要复杂化。复杂性是系统最大的敌人
- 演进:支持增量变化,不需要大规模重写。拥抱变化而不是抵抗变化
- 弹性:能够优雅地处理故障,而不是脆弱地崩溃
- 可观测:能够理解系统内部的状态,快速定位问题
- 可测试:容易编写和运行测试,保证质量
- 松耦合:模块之间依赖最小,可以独立变更和部署
- 高内聚:相关的功能聚集在一起,职责清晰
- 适合团队:团队能够理解、实施和维护
架构的本质:
- 架构是关于权衡的艺术,不是追求完美的科学
- 没有最好的架构,只有最合适的架构
- 架构决策要考虑当前的约束(团队、时间、成本)
- 好的架构师知道什么时候该做什么,什么时候不该做什么
我的架构原则:
- 先让它工作,再让它正确,最后让它快
- 推迟决策到最后责任时刻
- 优先选择可逆的决策
- 用最简单的方案解决问题
- 架构服务于业务,而不是相反