数据密集型应用系统设计 学习笔记(一):可靠性、可拓展性与可维护性

可靠性、可拓展性与可维护性

现如今大多应用程序都是 数据密集型(data-intensive) 的,而非 计算密集型(compute-intensive) 的。因此CPU很少成为这类应用的瓶颈,更大的问题通常来自数据量、数据复杂性、以及数据的变更速度。

数据密集型应用通常也是基于标准模块构建而成,每个模块负责单一的常用功能。例如,许多应用系统都包含以下模块:

  • 数据库(DataBase):用以存储数据,这样之后应用可以再次访问。

  • 缓存(Cache):缓存那些复杂或操作代价昂贵的结果,加快读取速度。

  • 索引(Search Indexe):用户可以按关键字搜索数据井支持以各种方式对数据进行过滤

  • 流处理(Stream Processing):持续发送消息至另一个进程,处理采用异步方式。

  • 批处理(Batch Processing):定期处理大量的累积数据。

认识数据系统

影响数据系统设计的因素有很多,其中包括相关人员技能和经验水平、历史遗留问题、交付周期、对不同风险因素的容忍度、监管合规等。这些因素往往因时因地而异。本书将专注于对大多数软件系统都极为重要的三个问题:

  • 可靠性(Reliability):系统在困境(adversity,如硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。
  • 可伸缩性(Scalability):系统有合理的办法应对规模的增长(如数据量、流量、复杂性等)。
  • 可维护性(Maintainability):许多不同的人(工程师、运维)在不同的生命周期,都能高效地在系统上工作(使系统保持现有行为,并适应新的应用场景)。

可靠性

人们对于一个东西是否可靠,都有一个直观的想法。人们对可靠软件的典型期望包括:

  • 应用程序表现出用户所期望的功能。
  • 允许用户犯错,允许用户以出乎意料的方式使用软件。
  • 在预期的负载和数据量下,性能满足要求。
  • 系统能防止未经授权的访问和滥用。

如果所有这些在一起意味着“正确工作”,那么可以把可靠性粗略理解为即使出现问题,也能继续正确工作

尽管比起阻止错误,我们通常更倾向于容忍错误。可以恢复的故障种类有硬件故障、软件错误,人为失误三种。

硬件故障

当考虑系统故障时,对于硬件故障总是很容易想到硬盘崩溃,内存故障,电网停电,甚至有人误拔掉了网线。

那我们如何应对这些问题呢?

  • 硬件冗余:例如对磁盘配置 RAID ,服务器配备双电源,甚至热插拔 CPU,数据中心添加备用电源、发电机等。
  • 软件容错:例如当需要重启计算机时为操作系统打安全补丁,可以每次给一个节点打补丁然后重启,而不需要同时下线整个系统。

软件错误

这类错误难以预料,而且因为是跨节点相关的,所以比起不相关的硬件故障往往可能造成更多的系统失效。例子包括:

  • 由于软件错误,导致当输入特定值时应用服务器总是崩溃。例如2012年6月30日的闰秒,由于Linux内核中的一个错误,许多应用同时挂掉。
  • 失控进程会用尽一些共享资源,包括 CPU、内存、磁盘空间或网络带宽。
  • 系统依赖的服务变慢,没有响应,或者开始返回错误的响应。
  • 级联故障,一个组件中的小故障触发另一个组件中的故障,进而触发更多的故障。

虽然软件中的系统性故障没有速效药,但我们还是有很多小办法,如:包括认真检查依赖的假设条件与系统之间交互 、进行全面的测试、进程隔离,允许进程崩溃并自动重启、反复评估,监控井分析生产环节的行为表现等。

人为失误

设计和构建软件系统总是由人类完成,也是由人来运维这些系统。即使有时意图是好的,但人却无发做到万无一失。经过统计,运维人员的配置错误是系统下线的首要原因。

如果我们假定人是不可靠的,那么该如何保证系统的可靠性呢?我们可以尝试以下方法:

  • 以最小出错的方式来设计系统。
  • 想办法分离最容易出错的地方、容易引发故障的接口。
  • 采用充分的测试,如从各单元测试到全系统集成测试以及手动测试。
  • 当出现人为失失误时,提供快速的恢复机制以尽量减少故障影响。
  • 设置详细而清晰的监控子系统,包括性能指标和错误率。
  • 推行管理流程井加以培训 。

可拓展性

系统今天能可靠运行,并不意味未来也能可靠运行。**降级(degradation)**的一个常见原因是负载增加,例如:系统负载已经从一万个并发用户增长到十万个并发用户,或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多。

负载

负载可以用一些称为**负载参数(load parameters)**的数字来描述。参数的最佳选择取决于系统架构,它可能是每秒向Web服务器发出的请求、数据库中的读写比率、聊天室中同时活跃的用户数量、缓存命中率或其他东西。

性能

常见的用于描述系统性能的指标如下:

  • 吞吐量(throughput):每秒可以处理的记录数量,或者在特定规模数据集上运行作业的总时间。通常用于评估批处理系统。
  • 响应时间(response time):即客户端发送请求到接收响应之间的时间。通常用于评估在线系统。

延迟(latency)响应时间(response time) 经常用作同义词,但实际上它们并不一样。响应时间是客户所看到的,除了实际处理请求的时间( 服务时间(service time) )之外,还包括网络延迟和排队延迟。延迟是某个请求等待处理的持续时长,在此期间它处于 休眠(latent) 状态,并等待服务。

应对方法

拓展方式分为两种:

  • 水平拓展:即将负载分布到多个更小的机器。
  • 垂直拓展:即升级到更强大的机器。

跨多台机器分配负载也称为无共享(shared-nothing)架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要水平伸缩。

有些系统是弹性(elastic)的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动伸缩(人工分析容量并决定向系统添加更多的机器)。如果负载极难预测(highly unpredictable),则弹性系统可能很有用,但手动伸缩系统更简单,并且意外操作可能会更少。

跨多台机器部署**无状态服务(stateless services)**非常简单,但将有状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向伸缩),直到伸缩成本或可用性需求迫使其改为分布式。

扩展能力好的架构通常会做出某些假设,然后有针对性地优化设计。

可维护性

软件的大部分开销并不在最初的开发阶段,而是在持续的维护阶段,包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债、添加新的功能等等。

因此在设计之初就尽量考虑尽可能减少维护期间的痛苦,从而避免自己的软件系统变成遗留系统。 为此,我们将特别关注软件系统的三个设计原则:

  • 可运维性(Operability):便于运维团队保持系统平稳运行。

  • 简单性(Simplicity):简化系统复杂性,使新工程师能够轻松理解系统。

  • 可演化性(evolvability):后续工程师能够轻松地对系统进行改进,井根据需求变化将其适配到非典型场

    景。

Built with Hugo
主题 StackJimmy 设计