系统设计

系统设计的步骤

  • 系统设计面试:内幕指南链接

系统的需求

  • 功能性需求
    • 系统包含哪些功能
  • 非功能性需求
    • 性能和QPS,TPS等

对系统进行抽象设计

考虑系统目前需要优化的点

系统设计三要素

  • 高性能架构设计:熟悉系统常见性能优化手段比如引入读写分离缓存负载均衡异步
  • 高可用架构设计:CAP理论和BASE理论,通过集群来提高系统的稳定性、超时和重试机制、应对接口级的故障:降级、熔断、限流、排队
  • 高扩展架构设计:如何拆分系统和模块,尽可能做到低耦合高内聚

一些常见软件的QPS

  • Nginx: 30w+
  • redis : 单机QPS 8W+
  • mysql: 单机QPS 4k
  • Tomcat: 单机 2w

高性能

热点数据的处理

  • 热点数据的分类
    • 静态热点数据:可以提前预测到的热点数据比如要秒杀的商品
    • 动态热点数据: 不能够提前预测的热点数据,需要通过一些手段动态监测系统运行情况产生
  • 问题的关键在于如何找到这些热点数据,然后将它们存在jvm内存中。

静态资源的处理

  • CND

高可用

基础设施和架构层面

冗余设计

  • 无单点故障: 这是基石。系统中的任何关键组件(服务器、网络设备、数据库、中间件、负载均衡器)都应有备份或集群部署。
  • 多机房/多可用区部署: 将应用部署在同一个云服务商的不同可用区或不同地域的数据中心。利用云服务商的AZ间高速网络,实现故障隔离。当单个机房/可用区发生重大故障(如断电、网络中断)时,流量可以快速切换到其他健康的机房/可用区。
  • 同城双活/异地多活: 更高级别的冗余。多个机房同时对外提供服务,实时同步数据(有不同同步策略)。任何一个机房故障,用户几乎无感知。异地多活还能应对城市级灾难。

负载均衡

  • 作用: 将用户请求均匀分发到后端多个服务器实例,避免单个实例过载;同时检测实例健康状态,自动剔除故障节点,将流量导向健康节点。
  • 层级:
    • 全局负载均衡: 通常基于DNS(如GeoDNS、云厂商的GSLB),根据用户地理位置或健康检查结果,将用户引导到最优或健康的机房入口。
    • 本地负载均衡: 在机房/集群内部,使用硬件(如F5)或软件(如Nginx, LVS, HAProxy, 云厂商的CLB/ALB/NLB)分发流量到具体的应用服务器。

弹性伸缩

  • 自动扩缩容: 根据预设的监控指标(CPU、内存、请求量、队列长度等),在流量高峰时自动增加服务器实例,在低谷时自动减少实例。云服务商(如AWS Auto Scaling, Azure VM Scale Sets, GCP Managed Instance Groups)或容器编排平台(Kubernetes HPA)提供了成熟方案。
  • 快速扩容能力: 基础设施(IaaS/PaaS)和部署流水线(CI/CD)要保证能在几分钟内完成新实例的创建、配置和应用部署。

应用服务层面

服务化/微服务架构

  • 解耦与隔离: 将单体应用拆分为松耦合、独立部署的微服务。一个服务的故障不会直接导致整个系统崩溃,故障被限制在服务边界内。
  • 独立扩展: 每个服务可以根据自身负载独立进行伸缩。

容错设计

  • 超时与重试:
    • 为所有外部调用(数据库、缓存、其他服务、第三方API)设置合理的超时时间,避免线程被长时间阻塞耗尽资源(如线程池)。
    • 可重试的、幂等的操作配置有退避策略的重试机制(如指数退避),避免在依赖服务短暂故障时雪上加霜。使用Resilience4j或Spring Retry等库。
  • 熔断器:
    • 当依赖服务调用失败率超过阈值时,快速失败(直接返回错误或降级结果),避免大量请求堆积导致调用方资源耗尽(雪崩效应)。
    • 经过一段时间后,允许少量试探请求通过,以探测依赖服务是否恢复。
    • 常用库: Resilience4j, Hystrix (Netflix, 已进入维护模式,但原理重要), Sentinel (Alibaba)。
  • 限流:
    • 在服务入口或关键资源处限制单位时间内的请求量(QPS、并发连接数),防止突发流量压垮系统。
    • 算法: 令牌桶、漏桶、滑动窗口计数等。
    • 实现层面: API Gateway (如Spring Cloud Gateway, Zuul), Nginx限流模块, 专门的限流中间件(如Sentinel),或应用内集成Resilience4j限流器。
  • 降级:
    • 在系统压力过大或依赖服务不可用时,暂时牺牲非核心功能或降低服务质量,保证核心功能的可用性。
    • 例子: 商品详情页暂时不展示推荐列表;评论功能暂时只读;将缓存中的旧数据返回给用户(即使可能略旧);返回静态兜底页面。

异步化和削峰填谷

  • 消息队列: 将耗时操作、非实时性操作(如发送通知、记录日志、更新非核心数据)异步化,通过消息队列(如RabbitMQ, Kafka, RocketMQ)解耦生产者和消费者。消费者按照自身处理能力消费消息,有效应对流量洪峰。
  • 线程池隔离: 使用不同的线程池处理不同类型或优先级的任务,避免低优先级或高风险任务阻塞高优先级任务的核心线程。

数据存储层

数据库高可用

  • 主从复制: 最常见的模式。一个主库(Master)负责写,多个从库(Slave)异步/半同步复制数据负责读。主库故障时,通过手动或自动(如MHA, Orchestrator)将某个从库提升为新主库(主从切换)。
  • 双主/多主复制: 多个节点均可读写,需要解决写冲突(冲突检测与解决策略较复杂)。较少用于强一致性要求高的场景。
  • 分布式数据库: 如TiDB, CockroachDB, 云厂商的Aurora, Spanner等,天然具备高可用和水平扩展能力。
  • 数据库代理/中间件: 如MyCAT, ShardingSphere-Proxy, Vitess,可以屏蔽后端数据库集群的复杂性,提供读写分离、故障切换功能。

缓存高可用

  • Redis Sentinel: 哨兵模式,监控主从节点状态,在主库故障时自动进行主从切换并通知客户端。
  • Redis Cluster: 分布式分片模式,数据分布在多个节点上,部分节点故障不影响整体服务(只要主节点和其对应的从节点不同时故障)。
  • 多级缓存: 本地缓存(如Caffeine, Ehcache)+ 分布式缓存(如Redis),提升访问速度并缓解分布式缓存压力。本地缓存需要关注一致性问题。

数据备份与恢复

  • 定期全量/增量备份: 将数据备份到异地存储(如对象存储OSS/S3)。
  • 备份验证: 定期演练恢复流程,确保备份有效。
  • 快速恢复能力: 利用快照技术(云厂商普遍提供)或逻辑备份工具(如mysqldump, pg_dump, mongodump)实现快速数据恢复。

运维与监控层面

全面的监控

  • 监控对象: 基础设施(CPU, 内存, 磁盘, 网络)、应用(JVM指标 - GC, 堆内存, 线程池状态;接口响应时间, 错误率, QPS)、中间件(数据库连接池, Redis命中率, MQ堆积)、业务指标(订单量, 支付成功率)。
  • 工具栈: Prometheus + Grafana (时序数据监控与可视化), ELK/EFK (日志收集、分析与可视化), Zabbix, Nagios, 以及云厂商的监控服务(如CloudWatch, Azure Monitor, 阿里云ARMS/SLS)。

告警系统

  • 基于监控指标设置合理且不冗余的告警阈值(如错误率>1%,平均响应时间>1s)。
  • 告警需要分级(P0/P1/P2)并通知到正确的负责人(如电话、短信、企业微信、钉钉、PagerDuty)。
  • 避免告警风暴,确保告警信息清晰可操作。

自动化部署与回滚

  • CI/CD流水线: 实现代码提交 -> 自动化构建 -> 自动化测试(单元、集成、API)-> 自动化部署到不同环境(测试、预发、生产)。
  • 蓝绿部署/金丝雀发布:
    • 蓝绿部署: 准备两套完全相同的生产环境(蓝和绿)。新版本部署到绿环境并测试通过后,通过切换负载均衡将流量瞬间从蓝切到绿。出问题可瞬间切回蓝。
    • 金丝雀发布: 将新版本先部署给一小部分用户(如1%),监控其运行状况和业务指标。确认稳定后,逐步扩大新版本流量比例,直至全量替换旧版本。风险更低。
  • 快速回滚机制: 一旦新版本上线出现问题,必须能够快速(分钟级)回滚到上一个稳定版本。这要求部署过程可逆且回滚脚本经过充分测试。

流程与组织保障

  • 变更管理: 任何对生产环境的变更(代码、配置、基础设施)都应经过评审、测试,并在低峰期进行。
  • 预案与演练: 针对已知的故障场景(如数据库主从切换、机房切换、核心服务宕机)制定详细的应急预案(Runbook),并定期进行演练,确保相关人员熟悉流程。
  • On-Call机制: 建立清晰的On-Call轮值和升级机制,确保问题发生时能快速响应。
  • 复盘文化: 每次故障后,进行彻底的根因分析,形成改进项并落实,避免同类问题再次发生(Blameless Postmortem)。

高可用的七大原则

  • 少依赖原则:能不依赖的,尽可能不依赖,越少越好
  • 弱依赖原则: 一定要依赖的,尽可能弱依赖,越弱越好
  • 分散原则:鸡蛋不要放一个篮子,分散风险
  • 均衡原则: 均匀分散风险,避免不均衡
  • 隔离原则: 控制风险不扩散,不放大
    • 隔离是有级别的,隔离级别越高,风险传播扩散的难度就越大,容灾能力越强
    • 是以上原则的前提,隔离没做好,上面四个原则都是收效甚若的
  • 无单点原则: 要有冗余或其他版本,做到有路可退
    • 快速止血的方式是切换,回滚,扩容等;回滚和扩容属于特殊的切换,回滚指的是切换到某个版本,扩容指的是将流量切换到新扩容的机器上。
    • 无单点原则和分散原则的区别:
      • 当节点无状态的情况下,打散拆分成N份,每份都是相同的功能,互为冗余,即:节点无状态情况下,分散原则和无单点原则等价,满足一个即可。
      • 当节点有状态的情况下,打散拆分成N份,每份都是不相同的,每份都没有冗余,需要针对每份再做冗余,即:节点有状态情况下,既要满足分散原则又要满足单点原则
  • 自我保护原则: 少流血,牺牲一部分,保护另外一部分

软件系统风险的来源

  • 系统资源的变化
    • 系统运行所依赖的服务器资源(如CPU,MEM,IO等),应用资源(RPC线程数,DB连接数等),业务资源(业务ID满了,余额不足,业务额度不够等)的负载等都会影响系统运行的风险期望。
  • 存储系统变化
    • 系统运行所依赖的服务器资源(如CPU,MEM,IO等),存储资源(并发数等),数据资源(单库容量,单表容量等)的负载和数据一致性等都会影响存储系统运行的风险期望。
  • 人的变化:变更出错
  • 硬件变化:损坏
  • 上游变化:请求变化
    • 网络流量过大会造成网络堵塞,影响网络通道中的所有网络流量请求
    • API请求过大会造成对应服务集群过载,影响整个服务机器上的所有API请求,甚至往外传播。
    • KEY请求过大(俗称“热点KEY”)会造成单机过载,影响单机上所有KEY请求,甚至往外传播。
  • 下游变化: 响应变慢,响应错误
    • 下游服务的数量,服务等级,服务可用率等影响下游服务的风险期望。下游响应变慢可能会拖慢上游,下游响应错误可能会影响上游运行结果。
  • 时间变化:
    • 时间到期往往被人忽视,但它往往具有突然性和全局破坏性,一旦时间到期触发故障会导致非常被动,所以要提前识别,尽早预警,如:秘钥到期,证书到期,费用到期,跨时区,跨年,跨月,跨日等

一致性

  • 接口幂等

系统设计的原则

  • 原则一:关注于真正的收益而不是技术本身
    • 关于收益的定义
      • 是否可以降低技术门槛加快整个团队的开发流程
      • 是否可以让整个系统运行的更稳定,是否可以提升真个系统的SLA
      • 是否可以通过简化和自动化降低成本,成本关注度从高到低分别为人力成本,时间成本,资金成本
  • 原则二:以应用服务和API为视角,而不是以资源和技术为视角
    • 现在很多技术和组件已经分不清是dev还是Ops了,所以需要合并Dev和Ops
  • 原则三: 选择最主流和成熟的技术
  • 原则四:完备性比性能更重要
    • 使用最科学严谨的技术模型为主,并以不严谨的模型作为补充
    • 性能上的东西都是多解的
  • 原则五:制定并遵循服从标准、规范和最佳实践
  • 原则六:重视架构扩展性和可运维性
    • 微服务架构的一些建议
      • 所有团队的程序模块都要以通过Service Interface方式将其数据与功能开放出来。
      • 团队间的程序模块的新系通信都要通过这些接口
  • 原则七:对控制逻辑进行全面收口
  • 原则八:不要迁就老旧系统的技术债务
  • 原则九:不要依赖自己的经验,要依赖于经验和学习
  • 原则十:千万要小心X - Y问题,要追原始需求
  • 原则十一: 激进胜于保守,创新与使用并不冲突

系统设计
https://x-leonidas.github.io/2025/10/26/23系统设计/系统设计/
作者
听风
发布于
2025年10月26日
更新于
2025年7月13日
许可协议