32

数据密集型系统基础:可靠、可扩展与可维护的数据系统

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA4NzA5NzE5Ng%3D%3D&%3Bmid=2650229742&%3Bidx=1&%3Bsn=9bbbf89775120e43cf850d441775ff7f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

点击关注上方“ 知了小巷 ”,

设为“置顶或星标”,第一时间送达干货。

数据密集型系统基础:可靠、可扩展与可维护的数据系统

作为互联网软件行业从业者,一定听过以下这些名词,NoSQL、Big Data、Web-scale、Sharding、Eventual consistency、ACID、CAP理论、云服务、MapReduce和Real-time等等。所有这些都是围绕着一个核心主题:就是 「如何构建一个高效的数据存储与数据处理系统或应用。」

对于一个应用系统,如果数据是其成败的决定性因素,包括 「数据的规模、数据的复杂度或者数据产生与更新的速率」 等,就可以称其为 「数据密集型应用系统」 ,与之对应的是计算密集型(CPU主频往往是最大的制约瓶颈)。

现在许多新型应用都属于数据密集型(data-intensive),而不是计算密集型 (compute-intensive) 。对于这类应用, 「CPU的处理能力往往不是第一制约因素,关键在于数据量、数据的复杂度及数据的快速多变性。」

许多数据密集型应用通常包括如下一些模块:

  • 数据库:主要用于OLTP联机交易处理,包括快速存储数据和快速的数据查询访问,比如MySQL。

  • 高速缓存:缓存那些复杂或操作代价昂贵的结果,以加快下一次访问,比如Redis。

  • 索引:用户可以按关键字搜索数据并支持各种过滤,比如ElasticSearch、Solr。

  • 流式处理:持续发送消息至另一个进程,处理采用异步方式,比如Kafka+SparkStreaming或Kafka+Flink。

  • 批处理:定期处理大量的累积数据,比如Hive等。

可靠、可扩展与可维护的数据系统

我们通常将数据库、队列、高速缓存等视为不同类型的系统。虽然数据库和消息队列存在某些相似性,例如两者都会保存数据(至少保存一段时间),但它们却有着截然不同的访问模式,这就意味着它们 「有着不同的性能特征和设计实现」

实际上,越来越多的应用系统需求广泛,单个组件往往无法满足所有数据处理与存储需求。因而需要将任务分解,每个组件负责高效完成其中一部分, 「多个组件依靠应用层代码驱动有机衔接起来」

例如下面一种数据系统架构,它包含了多个不同组件:

640?wx_fmt=png

上图组合使用了多个组件来提供服务,而对外提供服务的界面或者API会隐藏很多内部实现细节。这样基本上我们 「基于一个个较小的、通用的组件,构建而成一个全新的、专用的数据系统」 。这样的集成数据系统会提供某些技术保证,例如,缓存要正确刷新以保证外部客户端看到一致的结果。

影响数据系统设计的因素有很多,但大多数软件系统都极为重要的三个问题是:

  1. 可靠性(Reliability)

  2. 可扩展性(Scalability)

  3. 可维护性(Maintainability)

到底什么样的特征表现可以认为是可靠的或者是不可靠的?

对于软件,典型的期望包括:

  • 应用程序执行用户所期望的功能(满足需求)。

  • 可以容忍用户出现错误或者不正确的软件使用方法。

  • 性能可以应对典型场景、合理负载压力和数据量。

  • 系统可防止任何未经授权的访问和滥用(安全)。

一句话就是: 「即使发生了某些错误,系统仍可以继续正常工作。」

「软件运行过程中可能出错的事情称为错误(faults)或者故障,软件系统自动应对这些错误的过程则称为容错(fault- tolerant) 或者弹性(resilient) 。」当然,系统是不可能全部容忍所有可能的故障类型,容错总是指特定类型的故障,这样的系统才更有实际意义。由于我们不太可能将故障概率降低到零,因此通常 「设计容错机制来避免因为故障而引发系统失效(Failure)」

容错测试:可以通过故意引发故障的方式,来持续检验、测试系统的容错机制,增加对真实发生故障时应对的信心,比如随机杀死某个进程,来确保系统仍保持健壮。

「硬件故障」

硬盘崩溃,内存故障,电网停电,甚至有人误拔掉了网线。任何与大型数据中心合作过的人都会有这样的结论,当有很多机器时,这类事情迟早会发生。有研究表明,在一个包括10000个磁盘的存储集群中,可以预期平均每天有一个磁盘发生故障。这类故障,通常是为硬件添加冗余来减少系统故障率。例如对磁盘配置RAID,服务器配备双电源,甚至热插拔CPU,数据中心添加备用电源、发电机等。只要可以将备份迅速恢复到新机器上,故障的停机时间在大多数应用中并不是灾难性的。而多机冗余则只对少量的关键应用更有意义,对于这些应用,高可用性是绝对必要的。

「软件错误」

我们通常认为硬件故障之间多是相互独立的:一台机器的磁盘出现故障并不意味着另一台机器的磁盘也要失效。除非存在某种弱相关(例如一些共性原因,如服务器机架中的温度过高),否则通常不太可能出现大量硬件组件同时失效的情况。而软件故障事先更加难以预料,而且因为节点之间是由软件关联的,因而往往会导致更多的系统故障。

  • 由于软件错误,导致当输入特定值时应用服务器总是崩溃。

  • 一个应用进程使用了某些共享资源如CPU、内存、磁盘或网络带宽,但却不幸失控被跑满。

  • 系统依赖于某些服务,但该服务突然变慢,甚至无响应或者开始返回异常的响应。

  • 级联故障,其中某个组件的小故障触发另一个组件故障,进而引发更多的系统问题。

  • 导致软件故障的bug通常会长时间处于引而不发的状态,直到碰到特定的触发条件。软件系统问题有时没有快速解决办法,而只能仔细考虑很多细节,包括认真检査依赖的假设条件与系统之间交互,进行全面的测试,进程隔离,允许进程崩潰并自动重启,反复评估,监控并分析生产环节的行为表现等。如果系统提供某些保证,例如,在消息队列中,输出消息的数量应等于输入消息的数量,则可以不断地检査确认,如发现差异则立即告警。

「人为失误」

设计和构建软件系统总是由人类完成,也是由人来运维这些系统。即使有时意图是好的,但人却无法做到万无一失。如果我们 「假定人是不可靠的,那么该如何保证系统的可靠性?」 可以尝试结合以下多种方法:

  • 以最小出错的方式来设计系统。例如,精心设计的抽象层、API以及管理界面,使“做正确的事情”很轻松,但搞坏很复杂。但是,如果限制过多,人们就会想法来绕过它,这会抵消其正面作用。因此解决之道在于很好的平衡。

  • 想办法分离最容易出错的地方、容易引发故障的接口。特别是,提供一个功能齐全但非生产用的沙箱环境,使人们可以放心的尝试、体验,包括导入真实的数据,万一出现问题,不会影响真实用户。

  • 充分的测试:从各单元测试到全系统集成测试以及手动测试。另外,自动化测试已被广泛使用,对于覆盖正常操作中很少出现的边界条件等尤为重要。

  • 当出现人为失误时,提供快速的恢复机制以尽量减少故障影响。例如,快速回滚配置改动,滚动发布新代码(这样任何意外的错误仅会影响一小部分用户),并提供校验数据的工具(防止旧的计算方式不正确)。

  • 设置 「详细而清晰的监控子系统」 ,包括性能指标和错误率。在其他行业称为遥测(Telemetry), 一旦火箭离开地面,遥测对于跟踪运行和了解故障至关重要。监控可以向我们发送告警信号,并检查是否存在假设不成立或违反约束条件等。这些检测指标对于诊断问题也非常有用。

  • 推行管理流程并加以培训。

「可靠性的重要性」

常见的如商业软件中的错误会导致效率下降(如数据报告错误,甚至带来法律风险),电子商务网站的暂停会对营收和声誉带来巨大损失等。

可扩展性

即使系统现在比较可靠,并不意味着它将来一定能够可靠运转。发生 「系统退化」 的一个常见原因是 「负载增加」 :例如也许并发用户从最初1 0000个增长到10 0000个,或从100万到1000万;又或者系统目前要处理的数据量超出之前很多倍。可扩展性是用来描述系统应对负载增加后处理能力的术语。如果系统以某种方式增长,我们应对增长的措施有哪些?我们该如何添加计算资源来处理额外的负载?

「描述负载」

负载可以用称为负载参数的若干数字来描述。参数的最佳选择取决于系统的体系结构,它可能是Web服务器的每秒请求处理次数,数据库中写入的比例,聊天室的同时活动用户数量,缓存命中率等。有时平均值很重要,有时系统瓶颈来自于少数峰值。

「描述性能」

如果负载增加将会发生什么。有两种考虑方式:

  • 负载增加,但系统资源(如CPU、内存、网络带宽等)保持不变,系统性能会发生什么变化?

  • 负载增加,如果要保持性能不变,需要增加多少资源?

在批处理系统如Hadoop中,我们通常关心吞吐量(throughput),即每秒可处理的记录条数,或者在某指定数据集上运行作业所需的总时间;而在线系统通常更看重服务的响应时间(response time),即 「客户端从发送请求到接收响应之间的间隔。」

「延迟与响应时间」

延迟(latency)和响应时间(response time)容易混淆使用,但它们并不完全一样。 「通常响应时间是客户端看到的」 :除了处理请求时间(服务时间,service time)外,还包括来回网络延迟和各种排队延迟。 「延迟则是请求花费在处理上的时间。」

理想情况下, 「批量作业的运行时间是数据集的总大小除以呑吐量」 。在实践中,由于数据倾斜 (数据在多个工作进程中不均匀分布)问题,系统需要等待最慢的任务完成,所以运行时间往往更长。

即使是反复发送、处理相同的请求,毎次可能都会产生略微不同的响应时间。实际情况往往更复杂,由于系统要处理各种不同的请求,响应时间可能变化很大。因此, 「最好不要将响应时间视为一个固定的数字,而是可度量的一种数值分布。」

「中位数指标」非常适合描述多少用户需要等待多长时间:一半的用户请求的服务时间少于中位数响应时间,另一半则多于中位数的时间。因此中位数也称为 「50百分位数,有时缩写为p50」

为了弄清楚异常值有多糟糕,需要关注更大的百分位数如常见的第95、99和99.9 (缩写为 「p95、p99和p999」 )值。作为典型的响应时间阈值,它们分别表示有95%、99%或99.9%的请求响应时间快于阈值。例如,如果95百分位数响应时间为1.5s,这意味着 100个请求中的95个请求快于1.5s,而5个请求则需要1.5s或更长时间。

「应对负载增加的方法」

当负载参数增加时,应如何保持良好性能?

「实践中的百分位数:」对于后台服务,如果一次完整的服务里包含了多次请求调用,此时高百分位数指标尤为重要。即使这些子请求是并行发送、处理,但最终用户仍然需要等待最慢的那个调用完成才行。哪怕一个缓慢的请求处理,即可拖累整个服务。即使只有很小百分比的请求缓慢,如果某用户总是频繁产生这种调用,最终总体变慢的概率就会增加(即长尾效应)。最好将响应时间百分位数添加到服务系统监控中,持续跟踪该指标。例如,设置一个10min的滑动窗口,监控其中响应时间,滚动计算窗口中的中位数和各种百分位数,然后绘制性能图表。

首先,针对特定级别负载而设计的架构不太可能应付超出预设目标10倍的实际负载。如果目标服务处于快速增长阶段,那么需要认真考虑每增加一个数量级的负载,架构应如何设计。

现在谈论更多的是 「如何在垂直扩展(即升级到更强大的机器)和水平扩展(即将负载分布到多个更小的机器)之间做取舍」 。在多台机器上分配负载也被称为无共享体系结构。在单台机器上运行的系统通常更简单,然而高端机器可能非常昂贵,且扩展水平有限,最终往往还是无法避免需要水平扩展。实际上,好的架构通常要做些实际取舍,例如,使用几个强悍的服务器仍可以比大量的小型虚拟机来得更简单、便宜。

某些系统具有弹性特征,它可以自动检测负载增加,然后自动添加更多计算资源,而其他系统则是手动扩展(人工分析性能表现,之后决定添加更多计算)。如果负载高度不可预测,则自动弹性系统会更加高效,但或许手动方式可以减少执行期间的意外情况。

把无状态服务分布然后扩展至多台机器相对比较容易,而有状态服务从单个节点扩展到分布式多机环境的复杂性会大大增加。出于这个原因,一般都是将数据库运行在一个节点上(采用垂直扩展策略),直到高扩展性或高可用性的要求迫使不得不做水平扩展。

随着相关分布式系统专门组件和编程接口越来越好,至少对于某些应用类型来讲,上述通常做法或许会发生改变。可以乐观设想,即使应用可能并不会处理大量数据或流量,但 「未来分布式数据系统将成为标配」

「超大规模的系统往往针对特定应用而高度定制,很难有一种通用的架构。」背后取舍因素包括 「数据读取量、写入量、待存储的数据量、数据的复杂程度、响应时间要求、访问模式等」 ,或者更多的是上述所有因素的叠加,再加上其他更复杂的问题。例如,即使两个系统的数据吞吐量折算下来是一样的,但是为每秒处理100000次请求 (每个大小为1KB)而设计的系统,与为每分钟3个请求(每个大小为2GB)设计的系统会大不相同。可扩展架构通常都是从通用模块逐步构建而来,背后往往有规律可循。

可维护性

软件的大部分成本并不在最初的开发阶段,而是在于整个生命周期内持续的投入,这包括维护与缺陷修复,监控系统来保持正常运行、故障排査、适配新平台、搭配新场景、技术缺陷的完善以及增加新功能等。从软件设计时开始考虑,尽可能较少维护期间的麻烦,甚至避免造出容易过期的系统。为此,我们将特别关注软件系统的三个设计原则:

  1. 可运维性

  2. 简单性

  3. 可演化性

「可运维性:运维更轻松」

虽然某些运维操作可以而且应该是自动化的,但最终还是需要人来执行配置并确保正常工作。运维团队对于保持软件系统顺利运行至关重要。一个优秀的运维团队通常至少负责以下内容:

  • 监视系统的健康状况,并在服务出现异常状态时快速恢复服务。

  • 追踪问题的原因,例如系统故障或性能下降。

  • 保持软件和平台至最新状态,例如安全补丁方面。

  • 了解不同系统如何相互影响,避免执行带有破坏性的操作。

  • 预测未来可能的问题,并在问题发生之前即使解决(例如容量规划)。

  • 建立用于部署、配置管理等良好的实践规范和工具包。

  • 执行复杂的维护任务,例如将应用程序从一个平台迁移到另一个平台。

  • 当配置更改时,维护系统的安全稳健。

  • 制定流程来规范操作行为,并保持生产环境稳定。

  • 保持相关知识的传承(如对系统理解),例如发生团队人员离职或者新员工加入等。

良好的可操作性意味着使日常工作变得简单,使运维团队能够专注于高附加值的任务。数据系统设计可以在这方面贡献很多,包括:

  • 提供对系统运行时行为和内部的可观测性,方便监控。

  • 支持自动化,与标准工具集成。

  • 避免绑定特定的机器,这样在整个系统不间断运行的同时,允许机器停机维护。

  • 提供良好的文档和易于理解的操作模式,诸如“如果我做了X,会发生Y”。

  • 提供良好的默认配置,且允许管理员在需要时方便地修改默认值。

  • 尝试自我修复,在需要时让管理员手动控制系统状态。

  • 行为可预测,减少意外发生。 「简单性:简化复杂度」

    小型软件项目通常可以写出简单而漂亮的代码,但随着项目越来越大,就会越来越复杂和难以理解。这种复杂性拖慢了开发效率,增加了维护成本。 「一个过于复杂的软件项目有时被称为一个“大泥潭”」

复杂性有各种各样的表现方式:状态空间的膨胀,模块紧耦合,令人纠结的相互依赖关系,不一致的命名和术语,为了性能而采取的特殊处理,为解决某特定问题而引入的特殊框架等。复杂性使得维护变得越来越困难,最终会导致预算超支和开发进度滞后。对于复杂的软件系统,变更而引入潜在错误的风险会显著加大,最终开发人员更加难以准确理解、评估或者更加容易忽略相关系统行为,包括背后的假设,潜在的后果,设计之外的模块交互等。相反,降低复杂性可以大大提高软件的可维护性,因此 「简单性应该是我们构建系统的关键目标之一」

简化系统设计并不意味着减少系统功能,而主要意味着消除意外方面的复杂性。消除意外复杂性最好手段之一是抽象。一个好的设计抽象可以隐藏大量的实现细节, 并对外提供干净、易懂的接口。一个好的设计抽象可用于各种不同的应用程序。这 样,复用远比多次重复实现更有效率;另一方面,也带来更高质量的软件,而质量过硬的抽象组件所带来的好处,可以使运行其上的所有应用轻松获益。

例如, 「高级编程语言」 作为一种抽象,可以隐藏机器汇编代码、CPU寄存器和系统调用等细节和复杂性。 「SQL」 作为一种抽象,隐藏了内部复杂的磁盘和内存数据结构,以及来自多客户端的并发请求,系统崩溃之后的不一致等问题。当然,使用高级编程语言最终并没有脱离机器汇编代码,只是并非直接使用,与汇编代码打交道的事情已经由编程语言抽象为高效接口代我们完成。

然而,设计好的抽象还是很有挑战性。在分布式系统领域中,虽然已有许多好的算法可供参考,但很多时候我们并不太清楚究竟该如何利用他们,封装到抽象接口之中, 最终帮助将系统的复杂性降低到可掌控的级别。

「可演化性:易于改变」

一成不变的系统需求几乎没有,想法和目标经常在不断变化:适配新的外部环境,新的用例,业务优先级的变化,用户要求的新功能,新平台取代旧平台,法律或监管要求的变化,业务增长促使架构的演变等。

在组织流程方面,敏捷开发模式为适应变化提供了很好的参考。敏捷社区还发布了很多技术工具和模式,以帮助在频繁变化的环境中开发软件,例如测试驱动开发(TDD)和重构。

一个应用必须完成预期的多种需求,主要包括 「功能性需求(即应该做什么,比如各种存储、检索、搜索和处理数据)」 和一些 「非功能性需求(即常规特性、例如安全性、可 靠性、合规性、可伸缩性、兼容性和可维护性)」

可靠性意味着即使发生故障,系统也可以正常工作。 「故障包括硬件(通常是随机的, 不相关的)、软件(缺陷通常是系统的,更加难以处理)以及人为(总是很难避免时 不时会出错)方面」「容错技术」 可以很好地隐藏某种类型故障,避免影响最终用户。

可扩展性是指 「负载增加时,有效保持系统性能的相关技术策略」 。对于可扩展的系统,增加处理能力的同时,还可以在高负载情况下持续保持系统的高可靠性。

可维护性则意味着许多方面,但究其本质是 「为了让工程和运维团队更为轻松」 。良好的抽象可以帮助降低复杂性,并使系统更易于修改和适配新场景。良好的可操作性意味着对系统健康状况有良好的可观测性和有效的管理方法。

往期推荐:

Hive基础面试题总结

MapReduce和YARN基础面试题总结

HDFS基础面试题总结

建设数据中台到底有什么⽤?

数据中台从哪⾥来,要到哪⾥去?

数据中台在⽹易电商业务的最佳实践

知了小巷

长按识别二维码,一键关注

640?wx_fmt=jpeg

640?wx_fmt=gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK