系统设计
系统设计的步骤
- 系统设计面试:内幕指南链接
系统的需求
- 功能性需求
- 系统包含哪些功能
- 非功能性需求
- 性能和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系统设计/系统设计/