我对本章非常兴奋。在这里,你将被引入技术需求的世界,更准确地说,是非功能性需求(Non-Functional Requirements, NFRs)的世界。我们将深入分析你希望实现的系统技术特性。这对于确保你的应用能够稳定运行、持续在线至关重要。
本章是关于从技术角度思考系统的入门介绍。在未来的章节中,我们会随着不同服务的编码,进一步探讨这个主题。本章内容包括:
- 什么是非功能性需求?
- 为什么我们需要非功能性需求?
- 用户处理
- 基础 I/O 和数据维护问题
- 处理流程
- 测试
- AI、数据工程与分析
- 灾难恢复
- 协议
正如我们将看到的,非功能性需求是业务需求的最佳伙伴。它们是我们需要为系统实现功能和流程的支柱。没有非功能性需求,系统的结果和性能大多难以预测。
理解非功能性需求
到目前为止,我们讨论的业务需求都属于功能性需求,也就是以代码形式体现的业务功能。这些功能是公司日常必须执行的特定流程。在 HomeIt 示例中,功能性需求涉及房产搜索、收集支付信息以及允许双方发送提案等。这些都是日常业务操作。
相对而言,非功能性需求是指你需要在软件中实现的特性,以保证功能性或业务需求在时间上得以持续,并且所有用户都能从软件中获益。
打个比方:一个需求是支持 Visa 信用卡支付,这是功能性需求。另一个需求是确保软件可以每分钟处理高达一千笔信用卡支付,这是非功能性需求或支持性需求。
你可能会问:为什么我们需要非功能性需求?很多大企业实际上并不太关注非功能性需求,它们太专注于业务流程,而不考虑系统在各种极端情况下的承受能力。例如,网站用户突然激增可能导致系统完全瘫痪。你需要确保系统能够在高负载下正常运行。如果系统出现故障,你还需要确保能够恢复而不会导致数据不一致。
在接下来的章节中,我们将讨论非功能性需求的各个方面。我会提供大量在设计软件时需要问的关键问题。这是我个人多年软件开发经验中积累的宝贵问题清单,我会不时回顾,确保我参与的系统在实际场景下有充分的支持。
请注意,本章中许多问题只是半解释性的。我们将在整本书的后续章节中继续探索。这章将成为你一次性获取丰富问题集的参考,但具体实践细节将在未来章节中展开。内容非常丰富,所以让我们开始吧。
用户需求处理
当我们开始构建系统或系统中的功能时,考虑如何处理用户非常重要。许多问题,比如系统中将存在的用户类型,前面章节已经涉及。这里列出一些关于用户处理的其他问题:
- 理想的用户体验流程是什么样的?
- 系统中是否存在用户“等级”、“类型”,或两者都有?
- 系统中是否有真人用户?
- 系统中是否有其他系统作为客户端/用户?
- 每个用户的注册流程如何进行?
- 其他系统如何被授权访问系统的不同功能?
- 真人用户的认证和授权如何处理?
- 用户会使用什么设备,这些设备如何影响用户体验?
- 每个用户能正确处理的数据格式是什么?我们需要向用户展示哪些数据格式?
- 哪些关键用户操作需要反馈到 BI 系统(如转化数据),为什么?如何对用户数据进行匿名化,以满足 GDPR 等数据合规标准?如果系统需要收集个人信息,用户如何同意数据使用?如何防止组织内部的代理(从开发到运维、市场等)滥用用户个人信息?
- 前端应如何最好地为用户提供服务?通过浏览器、客户端还是移动应用?
- 是否需要保持用户之间或用户与系统之间的实时连接?
通过 HomeIt 项目示例,我们可以尝试回答这些问题。阅读这些问题后,我们可以考虑:
- 提供基于 GPS 的移动搜索体验,让用户能查找附近房产;
- 如果用户在桌面环境中使用 Excel 等软件筛选房产,是否提供 CSV 格式的搜索结果;
- 使用 OAuth 2.0 协议为不同用户授予访问权限;
- 为其他开发者开放 API 接口以获取网站数据;
- 跟踪和记录支付操作,以便快速发现潜在的欺诈行为。
通过这些问题,还可以想到许多其他非功能性方面的设计。
I/O 与数据维护需求
在这里,我们需要考虑如何在系统中处理数据。根据你要实现的功能,需要逐一回答下列问题,以便更好地识别技术难题以及如何在软件中解决它们。关键问题包括:
-
期望的输入和输出是什么?
例如,在 HomeIt 中,房产注册需要哪些图片作为输入?处理完媒体文件后输出的格式是什么? -
使用的数据类型有哪些?
我们期望接收 PNG、JPG 还是视频?系统各重要流程中接收的数据类型是什么? -
数据是否需要地理标记(geo-referenced)?
-
系统中有多少不同的实体或业务对象?
-
使用的数据结构是什么?
例如,在房产搜索功能中,后端应返回给前端的数据是列表还是映射?根据搜索需求,哪种数据结构最合适? -
是否以自动方式生成组合或排列?
例如,自动生成房产 ID 时,数据生成是否正确?算法是否能保证每个房产 ID 唯一? -
数据量有多大,占用多少空间?
我们能生成足够多的组合吗?数据空间是否受限或过大?例如,为每个房产创建 ID 时,所选数据类型是否限制了可生成的 ID 数量? -
数据精度要求是多少?
-
可接受的误差范围是多少?
-
数据是否为关键事务数据,需要保留?
-
数据是否必须立即保持 100% 一致性?
或者,我们是否允许生成暂时不一致的数据,由后续流程修复一致性? -
应使用哪些 I/O 通道?
功能是否需要网络访问?是否需要加载或存储数据到数据库或硬盘?应用是否内存密集? -
数据是否需要持久化?使用何种持久化存储?
-
系统的每秒 I/O(IOPS)、存储容量、持久性和延迟要求是多少?
-
持久化数据使用文件系统还是数据库?
- 文件系统:选择实例存储(临时、快速)、块存储(网络持久化、快速但单机)、对象存储(如 Amazon S3,可扩展性强)
-
是否存在并发访问或写入?
-
数据传输/接收使用哪种协议?
TCP、UDP、FTP、HTTP、GraphQL? -
是否需要全文搜索?
-
系统中有哪些排序/查询模式?
例如 HomeIt 中按邮编或街道名查询,可能需要优化。 -
数据表示方式?
XML、JSON、Avro、Protobuf、二进制、表格或文件? -
协议是否能满足数据量和通信需求?
-
是否需要优化存储或传输?
是否需要处理不同文件分辨率、类型等? -
是否需要裁剪、TTL 或数据清理?
-
数据是否需要在传输或静态存储时加密?
-
是否需要数据分片?
- 垂直分片:不同服务器上不同表
- 水平分片:同表按属性值分布在不同服务器
-
数据访问频率如何?是否为归档数据?
-
数据保留周期有多长?不同数据类别是否有不同要求?
-
数据是否需要脱敏(PII 保护)?
-
异步处理是否可加速?
-
是否需要数据分页?
-
数据传输可靠性要求如何?
UDP 可丢包(如直播视频),TCP/HTTP 必须完整传输。 -
是否有合规限制(存储方式、位置、时间等)?遵守哪些法规?
-
数据存储成本是多少?
HomeIt 示例应用
在分析房产搜索功能时:
- 分页 是必要的;
- PII(房东信息)需隐藏给未注册用户;
- 搜索结果 必须精确,因此使用 TCP 协议;
- 需要 排序功能 并优化查询响应时间,确保快速搜索。
房产注册中涉及 文本和图片数据:
- 可考虑使用 S3 桶 存储图片;
- 需要评估数据存储量及长期成本;
- 每个房产占用的媒体存储空间对基础设施成本有重要影响;
- 需要与产品团队讨论最佳付费方案和上传策略。
存储需求评估
-
数据结构和类型决定数据大小(字节);
-
单个对象占用内存大小;大列表占用内存;
-
系统中对象数量及增长计划;
-
磁盘空间需求;
-
RAM 需求及查询处理能力;
-
根据数据规模选择不同算法;
- 大量对象需使用 流式处理 而非一次性加载整个集合。
存储类型选择
-
SQL 数据库(事务性数据)
- 保证数据一致性,如支付系统、库存管理、用户数据存储
- 示例:Oracle、SQL Server、Postgres、MySQL
-
NoSQL 数据库
- 适合非结构化数据或文本实体
- 示例:MongoDB、Couchbase、Cassandra、DynamoDB
- 优点:访问灵活、速度快
-
二进制文件存储
- Amazon S3、SFTP、NFS 等,用于大文件、媒体文件
- CDN 可用于加速静态资源访问
-
文本搜索
- Elasticsearch、Postgres
-
快速、高可用数据
- Cassandra、DynamoDB
-
事件中心
- Kafka、Kinesis(实时处理)
-
移动端数据存储
- SQLite、Realm
-
分析存储
- Hadoop(批处理大数据)、Spark(流数据快速处理)、Snowflake(数据仓库)
-
地理空间数据库
- 支持地理数据类型:Oracle、MS SQL Server、Postgres
-
图数据库
- 用于表示网络关系,如社交网络
- 示例:Neptune、Neo4J
-
缓存
- 内存数据库:Redis、Memcache
- 用于加速数据访问,常置于持久化存储前
-
时序数据库
- 时间敏感数据,如 InfluxDB、Prometheus
-
聚合日志
- 微服务日志聚合工具:Sumo Logic、Datadog、Splunk
接下来,我们将讨论 数据处理 的方法和策略。
数据处理需求
除了用户和数据管理之外,我们还需要考虑如何处理存储和表示的数据。在系统设计和编程中,我反复使用以下问题来思考数据处理:
-
哪些算法可以快速解决问题?
-
远程客户端系统需要的延迟或响应时间是多少?
-
地理位置相关性是否重要?
-
数据有哪些状态?
-
数据的生命周期如何?
对象如何创建?如何随时间存活?如何终结? -
信息延迟是否允许?
-
是否需要高可用性?
-
需要哪种扩展性?
- 水平扩展:是否需要多台服务器处理请求?
- 垂直扩展:是否需要升级服务器硬件以应对更高负载?
-
系统的弹性如何?
服务是否能在架构某点发生故障时继续提供请求?是否需要实现容错,使应用能在失败后恢复而不丢失关键数据? -
预期吞吐量(Throughput, TP)是多少?
每秒处理多少请求?读写次数?每秒输入输出字节数?这些吞吐量是否随日/周/月/年变化? -
是否存在单点故障?
-
同时在线用户数量是多少?
-
可能的性能瓶颈有哪些?如何解决?
-
是否允许或需要延迟操作?
-
是否需要周期性操作?
例如每日批量数据整合、定期导出数据、将数据转换发送到其他系统(如数据仓库)。 -
是否需要批量数据处理?
例如,在 HomeIt 中,将各房产的所有媒体加入队列,每小时单个进程处理整个队列并生成不同尺寸的优化图片。 -
是否需要流式数据处理?
-
数据是否需要缓冲处理?
-
处理是否可以并行执行?
-
数据量预计较大时,是否需要高性能计算?
-
系统中是否存在并发访问,可能导致竞态条件?
-
是否需要根据设备在运行时调整算法或数据格式?
-
系统是否支持离线使用?
-
处理或访问外部系统时是否需要缓存?
-
是否需要集群(clustering)?
-
运行时处理的数据量波动有多大?
-
启动服务的最大容忍时间是多少?
-
实时计算是否为关键功能?
-
哪些事件需要发布,正常状态或错误,为什么?
-
系统需要通知哪些其他系统?
-
可能出现哪些失败情况?
-
失败时系统返回何种错误?
-
系统如何尝试恢复错误?
-
可用的错误报警有哪些?
HomeIt 示例思考
通过这些问题,我们可以得到一些关键考虑点:
- 对房产媒体处理,需要 快速生成缩略图;
- 必须保证 图像处理失败时能够恢复;
- 高峰负载时间可能为 9:00–17:00,需要支持 动态扩展的基础设施;
- 图像处理集群需支持 同时为多个用户处理任务,防止成为系统瓶颈;
- 需要评估同时服务的用户数量,避免在多个房东同时注册房产时出现延迟或错误。
你在思考 HomeIt 系统时,还有哪些其他方面引起注意?
下一节,我们将探讨一些 重要的测试问题。
测试需求
在本书中,我们会多次讨论测试。不过首先,让我们考虑以下问题,这将帮助我们理解如何为应用设计测试方案:
- 你的功能如何进行测试?
- 系统的不同部分是否需要不同类型的测试?
- 我们希望进行多少单元测试?
- 我们希望进行多少集成测试?
- 是否需要行为测试或 UI 测试?
- 这种设计选择会带来哪些附带影响?
是否存在意外后果?如果有,是可取的还是完全不可取的?
举个例子,假设 HomeIt 系统将同时进行单元测试和集成测试。UI 测试不在本书讨论范围内,因为我们主要关注使用 Spring 6 的后端开发。
AI、数据工程与分析需求
以下是关于数据分析的一些关键问题,帮助指导系统中数据相关的主要考虑点:
- 系统需要哪些报告和分析?
- 需要跟踪哪些关键业务绩效指标(KPI)?
- 我们能否从现有数据中提取这些 KPI?
- 能否根据当前数据设计预测这些 KPI?
- 是否需要 ETL(抽取、转换、加载)?如何实现?
- 该产品是否需要使用机器学习?是否需要数据标注/分类系统?
- 是否需要回归分析技术?
- 是否需要在某处使用生成式 AI?
数据分析对企业至关重要。例如,在 HomeIt 场景中:
- 可以制作仪表盘显示每日收入;
- 报告已关闭或拒绝的提案数量;
- 展示平均每个提案生成的反报价数量;
- 报告不同类型用户的注册情况;
- 自动创建便捷标签用于筛选房产;
- 使用生成式 AI 帮助房东优化房源描述;
- 构建仪表盘时,需要考虑 ETL 系统的性能优化;
- 可使用机器学习实现实时欺诈检测。
数据分析和报表的可能性几乎无限。
灾难恢复能力
另一个关键系统方面是灾难恢复能力,这通常被许多公司忽视。例如:
- 如果公司建筑意外起火,是否能保持关键系统运作?
- 如果服务器被黑客入侵,如何关闭访问并恢复系统正常运行?
- 这些操作能多快完成?
一些重要问题包括:
- 备份频率是多少?
- 从数据丢失到最近备份的可容忍时间是多少?
- 会保留多少备份事件?
- 备份数据如何存储?
- 备份是否有冗余?
- 谁可以访问备份?
- 目标恢复时间(RTO)是多少?
- 可容忍的停机时间是多少?
- 恢复流程如何进行?触发条件是什么?
- 使用哪些地理区域来防范灾难?
- 采用哪种策略?备份/恢复、Pilot Light,还是 Hotsite/多站点?
在 HomeIt 系统中,一些简单做法包括:
- 制定数据库每日备份策略;
- 利用云服务提供的内置备份能力;
- 设置保留窗口,例如保留最近 7 天的快照。
根据损失规模和恢复能力要求,可设置不同策略以实现快速或慢速恢复。例如:
- 若希望有可立即连接的数据库副本以应对主实例故障,则需要支付额外实例,并实时同步写操作到灾难恢复数据库。
总之,有多种配置和设计可确保系统在出现故障时能及时恢复。
协议选择
在前几节中,我们讨论了存储、处理、数据格式、用户注册流程、用户类型等等。现在,我们需要考虑如何传输对象。为了在服务之间交换数据,我们将选择哪些协议?
-
RESTful APIs:
当希望服务能以简单、标准的方式被访问时,我们会选择 REST(Representational State Transfer)设计,使用基本的 HTTP 方法。作为当今最常用的数据传输模式,通过 REST 可以安全地让服务可用。不过 REST 也有一些缺点,其中最大的问题是 JSON 对象的表示相对消耗带宽,而相比之下,像 Google 的 gRPC 等新协议则更高效。REST 服务让用户能够轻松读取和理解数据交换内容,总体来说,很多人熟悉 REST,所以实现服务时采用这一方案相对容易。 -
gRPC:
如果你希望使用更简洁、吞吐量更高的协议,gRPC 是一个强有力的替代方案。它使用 HTTP/2 进行传输,并用 Protobuf 进行高效的数据序列化,提供低延迟和双向流传输。gRPC 特别适用于高性能、具有弹性的应用,尤其是在事件驱动架构中。 -
WebSockets / WebRTC:
- WebSockets 提供了一个基于单一 TCP 连接的全双工通信通道,实现客户端与服务器间的实时数据传输。它适合聊天应用、实时数据流和协作工具等需要高效、持久连接的场景。
- WebRTC 是一套协议,用于在浏览器或应用之间直接进行点对点的音频、视频和数据共享。它支持低延迟通信,无需中间服务器,常用于视频会议和文件共享功能。
-
GraphQL:
GraphQL 是 Facebook 开发的一种 API 查询语言和运行时,提供灵活高效的数据获取方式。客户端可以精确请求所需数据,避免数据过多或不足。它采用基于 schema 的强类型结构,使客户端能在一次请求中查询多个资源。同时,GraphQL 支持实时数据订阅,并允许开发者精确控制 API 响应,以满足前端需求。
由此可见,实现服务间数据交换的方式有很多种,选择哪种协议取决于具体需求。
总结
本章概览了设计应用时需要考虑的各种技术方面。本章并不是你唯一的技术需求参考,通常需要一个拥有不同专长的团队来做出这些决策。
在大公司中,许多决策可能已经由团队预先做出。但了解如何提出讨论、理解现有假设及其对整体性能的影响非常重要。通过本指南,你能够更有效地提出关键问题,发现系统的风险与薄弱环节。
软件开发中还有许多其他方面需要考虑以交付优秀系统。现在,这里是一个很好的起点。以下是我常用的清单,它帮助我评估应用并找到改进机会:
- 如何处理用户?
- 如何表示应用数据?
- 如何处理应用数据?
- 如何存储数据?
- 如何传输数据?
有了这些分析和思考,我们就可以开始理解 Spring 如何帮助你构建此类系统。在下一章,我们将深入 Spring 框架编程,讨论服务设计与开发。