Spring 系统设计实战——为您的领域定义服务

65 阅读18分钟

我对本章非常兴奋。在这里,你将被引入技术需求的世界,更准确地说,是非功能性需求(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 需求及查询处理能力;

  • 根据数据规模选择不同算法;

    • 大量对象需使用 流式处理 而非一次性加载整个集合。

存储类型选择

  1. SQL 数据库(事务性数据)

    • 保证数据一致性,如支付系统、库存管理、用户数据存储
    • 示例:Oracle、SQL Server、Postgres、MySQL
  2. NoSQL 数据库

    • 适合非结构化数据或文本实体
    • 示例:MongoDB、Couchbase、Cassandra、DynamoDB
    • 优点:访问灵活、速度快
  3. 二进制文件存储

    • Amazon S3、SFTP、NFS 等,用于大文件、媒体文件
    • CDN 可用于加速静态资源访问
  4. 文本搜索

    • Elasticsearch、Postgres
  5. 快速、高可用数据

    • Cassandra、DynamoDB
  6. 事件中心

    • Kafka、Kinesis(实时处理)
  7. 移动端数据存储

    • SQLite、Realm
  8. 分析存储

    • Hadoop(批处理大数据)、Spark(流数据快速处理)、Snowflake(数据仓库)
  9. 地理空间数据库

    • 支持地理数据类型:Oracle、MS SQL Server、Postgres
  10. 图数据库

    • 用于表示网络关系,如社交网络
    • 示例:Neptune、Neo4J
  11. 缓存

    • 内存数据库:Redis、Memcache
    • 用于加速数据访问,常置于持久化存储前
  12. 时序数据库

    • 时间敏感数据,如 InfluxDB、Prometheus
  13. 聚合日志

    • 微服务日志聚合工具: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 框架编程,讨论服务设计与开发。