在软件系统里面,功能性需求是面向用户、详细明确的需求,由产品人员根据市场的需要提炼出来,是产品生命周期里最重要的一环。比如电商系统里面的优惠券功能,通常包含需求:优惠券分类、细分领券人群、核销优惠券等等。一旦需求通过技术评审,开发人员必须依照文档实现功能,不允许轻易变更。
非功能性需求是什么呢?保障系统持续健康运转的辅助需求。依然以电商系统的优惠券为例,在促销活动期间发放大量优惠券,如何防止用户集中领券时系统不崩盘呢?活动结束后,如何收缩服务器,节省服务器资源呢?非功能性需求是面向运维的,重要但是不太紧迫,有时候可以没有操作界面,由架构师提出解决方案,再推动各个业务开发部门去接入相应组件。这些辅助系统对业务系统性能影响很小,并且长期处于优化状态。
1.可伸缩性
可伸缩性是指系统根据外部负载自动调节计算能力,常见的做法是水平扩展和垂直扩展。
- 水平扩展
当系统负载很高时,增加服务端的节点数量,也叫做扩容;当负载很低时,释放闲置的机器,也叫做缩容。这两个过程有几个重要的思考点:
(1)节点越多,承载能力越高吗:任何事情都有两面性,为了解决问题引入一个方案,就必然带来新的问题。节点越多,协调节点的开销就越大,额外增加的计算资源抵不上协调节点的开销,并发能力不升反降。(2)扩容多少节点才够用:资源总是有限的,用有限的资源做更多的事情,才能得到资本家的欢心。合理扩容的数量根据压测的结果来定。在日常的运维中,要求系统能够自动化扩容少量机器处理突发流量,如果超出负载再发出警报。
- 垂直扩展
垂直扩展是指增强单机处理能力,比如增加内存、升级CPU、更换固态硬盘等等。这个做法的好处是简单快速,无需调整系统设计;缺点是单机的处理能力始终有限,不可能无限升级,而且更换硬件必须停机,影响系统可用性。
2.可用性
系统可用性是指系统在规定的时间内正常运行的能力,是衡量系统质量和稳定性的重要指标之一。例如,一个系统的可用性为99.9%,表示在一年中的时间里,系统正常运行的时间占据了99.9%。以下是计算公式:
Availability = (Total Time - Downtime) / Total Time * 100%
Total Time表示总的时间,Downtime表示系统的停机时间。
除了自然灾害如水灾、地震等不可抗因素,降低可用性的主要原因是:
- 系统错误:系统代码逻辑存在BUG或者服务端配置错误等,导致用户无法正常访问。2019年2月22日,京东金融App中的理财资产被系统清零,故障持续约20分钟。京东客服表示,故障原因是系统升级导致的。
- 基础设施:通常是硬件故障,比如机房故障、网络故障。2022 年 12 月 18 日,阿里云香港 Region 可用区 C 机房部分服务器宕机了超过 15 小时,原因是机房水冷装置的故障。
- 恶意攻击:黑客攻击。常用的攻击方式是DDoS(Distributed Denial of Service Attack),向目标服务器发送大量恶意请求来使目标服务器不可用,合法用户无法访问在线服务。
- 系统过载:没有合理规划计算节点,访问量超出系统可以承载的范围,无法响应用户的正常请求。
对于大型互联网公司尤其是SaaS、云服务等公司,系统可用性就是生命线,只要出现时间过长的服务不可用,会大大影响口碑和品牌。通常这类公司采取的技术措施是异地多活,在不同城市建立独立的数据中心。“活”是相对于冷备份而言的,冷备份是备份全量数据,平时不支撑业务需求,只有在主机房出现故障的时候才会切换到备用机房,而多活,是指这些机房在日常的业务中也需要做业务支撑。
3.可维护性
可维护性是衡量系统升级的能力,修改或者增加需求,开发周期越短越好,注意与“可伸缩性”区分。
需求变更是无比寻常的事情,可维护性是所有团队都会关注的重点。一旦系统的可维护性变差,程序员的头发会迅速脱落。影响可维护性的因素主有三个:
- 业务本身的复杂度,项目成员对业务的熟悉程度。
- 工程代码的质量好坏,研发流程的科学与合理性。
- 需求评审等环节的执行质量,技术文档沉淀的质量。
近些年在大型项目开发里,领域驱动设计(Domain Driven Design)的出镜率很高。领域驱动设计是一套从系统分析到软件建模的设计思想和方法论。核心思想是以领域为核心驱动力构建软件设计体系,并围绕业务概念抽象出领域模型,通过领域和边界划分将复杂的业务模型抽象化、简单化,最终实现复杂软件应用系统的拆解和封装。领域驱动设计并没有发明新的东西,每个概念都是沿用已久的软件开发理念。它的实施成本很高,项目代码更加繁琐,人员的沟通成本也较高。
如果团队规模很小或者开发小型项目,提高可维护性至少要做好两个事情:
- 开发流程规范化:简化开发流程但是要严格执行重要的节点,比如需求评审;严格执行工程代码规范;只撰写重要的技术文档。
- 模块化和可重用性:将重要功能或者工具模块化,能够重用到新需求上;模块的命名要准确、功能要单一。避免过早做抽象设计,否则陷入过度设计。
4.一致性
数据是系统最重要的资产,数据不能丢也不能错,保持数据一致性万分重要。一致性分两种情况:
- 事务数据一致性:在传统关系数据库中,事务提交后数据保持一致状态,即事务的ACID特性。比如同行转账,资金从一个账户转移到新的账户,两个账户的总额保持不变。
- 副本数据一致性:分布式系统包含多个数据副本,数据可能被同时写入多个节点,也可能被同时读取多个节点。最理想的情况是所有副本在任何时刻都具有相同的值。比如跨行转账,隶属不同系统的账户,资金从一个账户转移到新的账户,两个账户的总额保持不变。
在分布式系统中,保持数据一致性是公认的难题,主要体现在下面几点:
- 独立进程:分布式系统无法像单机系统那样共享内存或者进程,需要分别获得各个进程的本地状态,再组合成全局状态。
- 全局时钟:分布式系统没有全局时钟,各个进程无法正确获得事件消息的时序关系,状态的一致性难以保障。
- 网络超时:分布式环境下网络超时状态的存在,需要具有高度容错特性的解决办法。
保持强一致性的成本很高,最好的解决方案就是避免分布式事务,但是在金融、电信领域中的部分业务场景要求数据强一致性,同时要保证服务的可扩展性和可靠性。结合实际的业务场景,一致性可以细分五个级别:
- 强一致性(strong consistency):任何时刻、任何用户或节点都可以读到最近一次成功更新的副本数据。强一致性是程度最高的一致性要求。
- 单调一致性(monotonic consistency):任何时刻、任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值。单调一致性是弱于强一致性却非常实用的一种级别。通常来说,用户只关心从自身视角观察到的一致性,而不会关注其他用户。
- 会话一致性(session consistency):任何用户在某一次会话内一旦读到某个数据在某次更新后的值,这个用户在这次会话过程中不会再读到比这个值更旧的值。会话一致性通过引入会话的概念,在单调一致性的基础上进一步放松约束,会话一致性只保证单个用户单次会话内数据的单调修改,对于不同用户间的一致性和同一用户不同会话间的一致性没有保障。
- 最终一致性(eventual consistency):一旦更新成功,各个副本上的数据最终将达到完全一致的状态,但达到完全一致状态所需要的时间不能保障。一个用户只要始终读取某一个副本的数据,则可以实现类似单调一致性的效果,但用户更换读取的副本,则无法保障任何一致性。
5.弹性
弹性,是指系统可以优雅地处理意外、从故障中恢复过来。故障是不可避免的,甚至不可预测。由于微服务的普及,故障发生的几率与计算节点数量成正比。分布式系统具备一定的容忍故障的能力,故而弹性设计又称容错设计,主要的解决方法有如下两点:
- 故障隔离
系统必须具备防止故障从一个系统传播到另一个系统的能力,常见场景如下:(1)系统间强依赖:如果系统间存在强依赖,当一个系统发生故障时,强依赖它的组件将无法正常工作。通常的手段是将强依赖转化为弱依赖或最弱依赖,比如设置合适的超时、捕获异常、同步依赖转异步依赖、提供备份组件等。(2)系统共享资源:如果系统间存在共享的资源,如线程池、数据库连接池、网络连接池、内存区等等。当一个系统因为故障耗尽了共享的资源后,所有依赖该资源的系统也都会发生故障。通常的手段是对组件的资源使用建立配额体系,或者为重要组件提供专用资源。
- 服务降级、限流、熔断
(1)服务降级:当出现系统故障后,在有限的资源情况下牺牲某些业务功能或者某些客户群体,保障更关键的业务服务质量。服务降级可以是人工触发的,也可以是系统自动执行的。所有核心交易场景下的非关键服务访问均应进行服务降级设计,以保证核心交易成功率。(2)服务限流:当负载超出系统处理能力时,可能会造成系统部分业务失败,需要通过业务限流来防止系统进一步化。例如在一个分布式系统中,每秒最多只能处理2000个请求。为了防止系统过载,可以设置规则限制上游服务请求量,当超过2000时随机抛弃一些请求来实现限流。(3)服务熔断:微服务与微服务之间有依赖性,可能导致故障传播,对整个系统造成灾难性的后果,即服务的雪崩效应。熔断器是通过快速失败(Fail Fast)的机制,避免请求大量阻塞,从而保护调用方。比如:服务A调用当下游B失败时,会导致请求超时引起堆积队列,进而加大了B系统的压力,增加了整个链路的请求时间。B系统本身就出现了问题,不断的请求又把问题加重了。如果使用了A触发了熔断,拒绝了上游的请求,会降低下游B服务的压力,给与B服务恢复的时间。
6.可观察性
可观测性是指通过度量、监控和分析系统组件,了解系统的状态、性能和问题的能力。可观测性可以帮助开发人员快速定位和解决系统中的问题,提高系统的稳定性和可靠性。系统可观测性设计还有助于优化系统性能,帮助研发人员针对性地做出调整和优化,提高系统的吞吐量和响应速度。系统可观测性设计应遵循几个重要原则:
- 全面性原则:全局角度考虑系统的可观测性,涵盖系统的各个方面,如性能、错误、日志、指标等。其次是实时性原则,即要确保监测和日志记录的数据是实时生成的,这样可以及时发现问题并做出调整。
- 简洁性原则:避免过多冗余的监测数据和日志信息,确保数据的准确性和有效性。
- 安全性原则:确保监测数据和日志信息的安全存储和传输,防止信息泄露和数据丢失。
主流的可观察系统基于三类数据构建,覆盖了一个应用服务器产生的大部分数据:
- 日志:每个应用中产生事件日志、事务日志、消息日志和服务器日志,存储在日志数据库。
- 指标:在单位时间的测量值,包括时间戳、地址等数据。指标数据是固定的结构,以便于系统查询和存储,存储在时序数据库。
- 链路:跟踪每个请求的来龙去脉,记录每个处理节点的地址、时间、发起者等等数据,存储在日志数据库。
可观察性系统是监控系统的超集,监控能够检测到系统当前的问题,但是可观察性系统要帮助研发人员预判故障发生的可能性。
7.安全性
安全性是指保障硬件正常运行,让用户只能访问合法授权的资源。安全是架构设计中很重要的一部分,很多大型企业都因为安全漏洞泄露过数据。广义的安全性涉及到所有的软硬件,比如物理机房、服务器及网络、操作系统、应用系统。架构安全设计可以遵循如下五个原则:
- 认证和授权:用户通过正确的账号和密码等凭证访问系统,授权决定用户进入系统后可以做什么。从最小权限原则开始,一开始不应该具有任何访问权限,根据其工作内容分配权限。也可以根据工作内容创建访问组,通过集中式用户数据库实现单点登录,进行统一管理授权策略。
- 全方位保障:通常企业主要关注数据中心和物理安全和保护外层网络免受攻击,其实企业应使用深度防御方法,将安全防护应用于架构的每一层。例如web应用需要通过保护边缘网络和域名系统路由来使其免受外部互联网流量攻击,在负载均衡和网络层使用安全防护工具组织恶意流量。可以通过限制web应用层和数据层只允许必须的入站、出站流量,来保护应用程序的每一个实例,用杀毒软件保护操作系统。
- 缩小影响半径:在每一层应用安全措施时,应该始终将系统进行合理的隔离,以减小影响半径。如果攻击者获得了系统某个部分的访问权限,应该能够将安全漏洞限制在应用程序的最小区域内。提供最小的访问权限可以确保不会暴露整个系统,提供临时凭证可以确保访问权限不会长期开放,提供开放式接口时要特别谨慎,务必要设置安全令牌,并且定期更换密钥。
- 监控和审计:将系统中每一项活动都记录日志,并定期审计。审计功能要按行业法规要求制定,同时采取主动监控,配备告警能力,从而在用户受到影响之前对事件进行处理。
- 数据保护:建立一些机制来减少直接访问数据,通过自动化的工具处理数据,避免人工处理数据,消除人为错误。尽可能对数据进行访问控制,减少数据丢失和数据篡改的风险。数据不仅在静止状态下需要保护,在传输过程中也要保护。
系统安全等级越高,运作成本越高。为了节省成本,中小型团队主要聚焦应用系统的安全,其他的交给云服务。云服务商负责云端安全,尤其是用于托管资源的物理基础设施的安全,包含如下内容:
- 数据中心:全天候安全警卫、双因子认证、访问记录和审查、视频监控、磁盘消磁和销毁等。
- 硬件基础设施:服务器、存储设备和其他依赖云服务的设备。
- 软件基础设施:主机操作系统、服务应用和虚拟化软件。
- 网络基础设施:路由器、交换机、负载均衡器、防火墙、布线等,还包括对外部边界、安全接入点和冗余基础设施的持续网络监控。
开发团队根据自身人力和财力,酌情实施安全设计,通常可以借鉴的安全措施有5条:
- 操作系统:及时升级服务端操作系统的补丁,避免服务器遭受外部攻击。
- 应用程序:主要包含应用程序和它的环境(如开发、测试和生产环境),及其所属密码策略和访问管理的安全措施。
- 防火墙:采用防火墙保护整个系统免受外部攻击。云服务提供了这方面的安全保障,但使用者可以考虑增加额外的安全层。
- 网络配置和安全组:云服务提供了创建网络防火墙的工具,使用者需要设置防火墙规则,以确保其系统免受来自外部和内部的网络流量的破坏。
- 数据加密:通过加密机制来保护业务数据,导出重要数据时要考虑脱敏。