第1步: 明确需求
不管是系统设计还是业务开发,都必须先弄清楚需求,这好比是回答别人的提问,如果连对方的问题都没有弄清楚,后面所有的回答都可能是答非所问。
因此,系统设计的第一步是彻底理解需求,从实际工作经验来看,需求主要包括 2种类型:功能性需求和非功能性需求。
(1) 功能性需求
功能性需求是指系统需要执行的功能和行为,也就是系统实实在在要完成的功能,主要包含以下几个点:
- 系统应支持哪些核心功能?
- 是否有任何特定功能比其他功能更重要?
- 谁将使用这个系统(客户、技术团队、客服等)?
- 用户应该能够在系统上执行哪些特定操作?
- 用户将如何与系统交互(Web、移动应用程序、API 等)?
- 系统是否需要支持多语言?
- 系统必须处理哪些关键数据类型(文本、图像、结构化数据等)?
- 系统是否需要集成外部系统或三方服务?
(2) 非功能性需求
非功能性需求是指系统的质量属性和性能,主要包含以下几个点:
- 系统的预期规模是多少?
- 系统预计要处理多少数据量?
- 系统的输入和输出是什么?
- 预期的读写比是多少?
- 系统是否可以停机,或者是否需要高可用性?
- 是否有任何特定的延迟要求?
- 数据一致性有多重要?为了可用性,是否可以容忍一些最终的一致性?
- 是否有任何特定的非功能性需求(性能、可伸缩性、可靠性)我们应该关注?
需求是整个系统设计的风向标,因此,明确需求是整个系统设计的第一步,尽早地弄清楚需求,可以帮助我们更好的把握系统走向。
第2步: 系统容量预估
在明确了需求之后,第二步要完成的事情就是评估系统的容量,只有知道了系统的容量,才能更好的预算开发周期、人力投入、服务器投入以及其他的投入,帮助我们更好地做好后期决策。
系统容量预估,一般需要评估以下几个指标:
- 用户数:预估系统需要支撑的总用户数以及高峰时段的活跃用户数和最大并发用户数。
- 流量:计算日常TPS/QPS,以及峰值时的TPS/QPS。
- 存储:需要存储的数据类型(结构化、非结构化等),以及所需的存储总量(及其增长率)。
- 内存:估计系统可能消耗的内存总量。
- 网络:根据估计的流量和数据传输大小估算带宽需求。
另外,系统设计还需要做未来增长和可伸缩性要求考虑,比如支持数据几倍的增长以及支撑几年的数据增长,以确保系统能够处理随时间推移而增加的负载。
第3步: 架构设计
在做完需求分析和容量评估这些准备工作之后,我们就可以进入真正的设计阶段,系统设计(High-Level Design,HLD)是软件开发生命周期中最重要也是最难的一个阶段。
架构设计是一个宏观上的考虑,旨在定义系统的总体结构和高层次的架构,在这个阶段需要完成系统整体设计的蓝图,帮助开发团队理解和规划系统的各个组件及其相互关系,下图是 Google的一张系统设计蓝图:
通过上述的蓝图可以看出:系统设计蓝图中包含以下主要组件:
- DNS:DNS是一种分布式系统,由许多域名服务器和域名解析器组成,提供名称解析服务,将域名转换成IP。
- 客户端应用程序:表明用户将如何与系统(Web 浏览器、移动应用程序、桌面应用程序等)进行交互。
- Web服务器:处理和响应客户端请求的服务器。
- 负载均衡器:用于将流量均匀地分配到服务器以处理大量流量。
- 应用程序服务:实现系统核心功能的后端逻辑层。
- 数据库:指定数据库类型:SQL 与 NoSQL,并简要说明原因。
- 缓存层:指定缓存(例如。Redis, Memcached),用于减少数据库的负载。
- 消息队列:如果系统需要使用异步通信。
- 外部服务:如果系统依赖于第三方 API(例如支付网关),请将其包括在内。
对于每个组件,一定要考虑权衡取舍,并说明为什么选择特定的技术或架构,关于设计图的绘制时,不要过度考虑小细节,而是更多站在宏观的角度。小细节可以在每个组件的设计中去推敲。
第4步: 数据库设计
绝大多数系统是需要和数据打交道的,因此数据库的设计也就显得至关重要,数据库设计通常包括数据库选型、数据建模、数据库结构设计等。
(1) 数据库选型
数据库选型通常是根据业务场景以确定最合适的数据库类型,主要包含以下几个考虑因素:
- 选择何种数据库,关系型数据库、NoSQL、ES等?还是多种数据库的组合?
- 是否需要考虑数据结构、可伸缩性、性能、一致性和查询模式等因素?
- 关系数据库(例如,MySQL、PostgreSQL)适用于具有复杂关系和 ACID属性的结构化数据。
- NoSQL数据库(例如 MongoDB、Cassandra)适用于非结构化或半结构化数据、高可扩展性和最终一致性。
(2) 数据建模
数据建模通常会考虑以下因素:
- 确定系统需要存储和管理的主要数据实体或对象(例如,用户、产品、订单)。
- 考虑实体之间的关系以及它们之间的交互方式。
- 确定与每个实体关联的属性或属性(例如,用户具有电子邮件、姓名、地址)。
- 标识每个实体的任何唯一标识符或主键。
- 考虑规范化技术,以确保数据完整性并最大程度地减少冗余。
(3) 数据库结构设计
数据库结构设计也就是真实的表结构设计,主要需要考虑以下因素:
- 根据所选的数据库类型定义表、列、数据类型和关系。
- 设计主键、外键等。
- 设置合理的索引以优化查询性能。
另外,在更宏观的角度上,还需要考虑分库分表,多活,灾备等问题。
第5步: API设计和通信协议
API和通信协议,它定义了系统内不同的组件间该如何交互以及外部客户端如何访问系统的功能,通常会考虑以下因素:
(1) 明确 API要求
- 确定系统需要通过 API公开的主要功能和服务。
- 考虑与 API交互的客户端类型(例如,Web、移动、第三方服务)。
- 确定每个 API的数据输入、输出和其他要求。
(2) 选择 API类型
- 根据系统要求和客户需求选择合适的 API类型。
- RESTful API通常用于基于 Web 的系统,并为资源操作提供统一的接口。
- GraphQL API为客户端查询和检索特定数据字段提供了一种灵活高效的方法。
- RPC(远程过程调用)API适用于具有明确定义的过程或功能的系统。
(3) 定义API协议
- 根据系统的功能和数据模型设计清晰直观的 API URL。
- 为API选择适当的 HTTP方法(例如,GET、POST、PUT、DELETE)。
第6步:细化组件设计
在第3步中,我们分析了架构设计,但是它从宏观上的一个把握,而不会过分的关注细节,因此在此步骤中,我们需要对第3步中的一些核心组件进行更详细的设计,这里以 Java后端为例:
作为 Java后端,你需要了解自己业务的领域,比如金融,电商,财务,出行等,因为不同的领域会有一定的差异性。下面是组件细化的一些考虑点:
- 三高系统:是否是高可用,高性能,高扩展性的系统?如何保证三高?
- 微服务:是否采用微服务,微服务的框架是什么,SpringCloud还是 Dubbo?
- 架构:是否需要使用DDD架构?
- 数据库:是否需要分库分表?是否需要多活?是否需要定时备份?
- 负载均衡器:使用哪些负载均衡技术和算法?
- 缓存:使用什么缓存?缓存放在哪里?如何处理缓存失效?
- 单点故障:是否有单点问题?如何解决单点问题?
- 身份验证/授权:如何安全地管理用户访问和权限?
- 速率限制:如何防止过度使用或滥用 API?
- 安全问题:如何保证系统安全和API安全?
以下都是在后端组件中需要考虑的问题,当然,我们需要根据自己所处的角色和领域,灵活的设计。
第7步: 解决关键问题
系统设计中难免会遇到一些技术难点以及核心挑战,这些挑战主要包括可扩展性和性能,以及可靠性、安全性和成本问题。为了更好的解决这些问题,下面也给出了具体的思路:
(1) 解决可扩展性和性能问题
- 增加节点进行水平扩展(横向扩展)。
- 增加单个资源(例如 CPU、内存、存储)的容量进行垂直扩展(纵向扩展)。
- 增加缓存以减少数据库压力并缩短响应时间。
- 优化数据结构和算法。
- 优化数据库查询和索引。
- 数据库分区和分库分片可提高查询性能。
- 增加CDN,加速静态资源访问。
- 利用异步编程模型高效处理并发请求。
(2) 解决可靠性问题
可靠性是指系统即使在出现故障或错误的情况下也能正确和一致地运行的能力。以下是系统在可靠性上的一些关键考虑因素:
- 识别系统架构中的单点问题,通过集群等方式消除单点故障。
- 服务或者数据做多活,以防止区域故障或灾难。
- 数据备份,确保数据可用性和持久性。
- 限流和降级机制,以防止级联故障并保护系统免受过载影响。
- 加强监控和警报,以及时检测故障、性能问题和异常情况。
总结
最后,我们再总结下系统设计的 7个步骤:
- 第1步: 明确需求
- 第2步: 系统容量预估
- 第3步: 架构设计
- 第4步: 数据库设计
- 第5步: API设计和通信协议
- 第6步: 细化组件设计
- 第7步: 解决关键问题
有了上述 7个步骤,在做系统设计时就有一个清晰的思路,最终方案如何实施还需要结合实际的业务以及最终的权衡来定。另外,上述 7个步骤也可以帮助我们轻松的应对面试中的各种系统设计问题。