目录

2. 构建怎样的数据系统

构建数据密集型应用的目标

/images/db/%E7%B3%BB%E7%BB%9F%E7%9B%AE%E6%A0%87.png

1. 构建怎样的系统

影响数据系统设计的因素很多,每个因素都需要具体问题具体分析。而我们着重讨论三个在大多数软件系统中都很重要的问题:

  1. 可靠性(Reliability)
  2. 可扩展性(Scalability)
  3. 可维护性(Maintainability)

相比于模棱两可的概念,我们需要清楚的知道它们意味着什么,如何系统的衡量和评估他们。

1.1 说明

要想说清楚这些概念非常不容易,它们其实涵盖了非常广泛的话题。下面大多数内容都是摘录自原著的开源翻译版本,特此说明。

2. 可靠性

可靠性可粗略理解为“即使出现问题,也能继续正确工作”。造成错误的原因叫做故障(fault) ,能预料并应对故障的系统特性可称为容错(faulttolerant)韧性(resilient)。但显然系统不能容忍所有错误,所以在讨论容错时,只有谈论特定类型的错误才有意义。

注意故障(fault)不同于失效(failure) 。故障通常定义为系统的一部分状态偏离其标准,而失效则是系统作为一个整体停止向用户提供服务。故障的概率不可能降到零,因此最好设计容错机制以防因故障而导致失效。

尽管比起阻止错误(prevent error),我们通常更倾向于容忍错误。但也存在预防错误的情况,比如安全问题就属于这种情况。但我们主要讨论的是可以恢复的故障种类,包括:

  1. 硬件故障(hardware faults)
  2. 软件错误
  3. 人为错误

3. 可扩展性

可扩展性(Scalability)意味着即使在负载增加的情况下也有保持性能的策略,是用来描述系统应对负载增长能力的术语。讨论可扩展性意味着考虑诸如

  • “如果系统以特定方式增长,有什么选项可以应对增长?”
  • “如何增加计算资源来处理额外的负载?”等问题。 在我们讨论可扩展性之前,我们需要能够衡量系统的负载和性能。

3.1 系统负载

负载可以用一些称为负载参数(load parameters) 的数字来描述。参数的最佳选择取决于系统架构,它可能是每秒向Web服务器发出的请求、数据库中的读写比率、聊天室中同时活跃的用户数量、缓存命中率或其他东西。除此之外,也许平均情况对你很重要,也许你的瓶颈是少数极端场景。你需要基于对系统的监控,找到系统中最耗费资源的地方。

3.2 描述性能

量化了系统负载之后,我们要简单地看一下如何描述系统性能。

对于Hadoop这样的批处理系统,通常关心的是吞吐量(throughput) ,即每秒可以处理的记录数量,或者在特定规模数据集上运行作业的总时间 。对于在线系统,通常更重要的是服务的响应时间(response time) ,即客户端发送请求到接收响应之间的时间。

响应时间

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

通常使用百分位点来衡量系统的响应时间。中位数是一个好的度量标准,中位数也被称为第50百分位点,有时缩写为p50。为了弄清异常值有多糟糕,可以看看更高的百分位点,例如第95、99和99.9百分位点(缩写为p95,p99和p999)

响应时间的高百分位点(也称为尾部延迟 tail latencies) 非常重要,因为它们直接影响用户的服务体验。例如亚马逊在描述内部服务的响应时间要求时以99.9百分位点为准。这是因为请求响应最慢的客户往往也是数据最多的客户,也可以说是最有价值的客户 —— 因为他们掏钱了

另一方面,优化第99.99百分位点(一万个请求中最慢的一个) 被认为太昂贵了,减小高百分位点处的响应时间相当困难,因为它很容易受到随机事件的影响,这超出了控制范围,而且效益也很小。

排队延迟(queueing delay) 通常占了高百分位点处响应时间的很大一部分。由于服务器只能并行处理少量的事务(如受其CPU核数的限制),所以只要有少量缓慢的请求就能阻碍后续请求的处理,这种效应有时被称为头部阻塞(head-of-line blocking)

在多重调用的后端服务里,高百分位数变得特别重要。即使并行调用,最终用户请求仍然需要等待最慢的并行呼叫完成。如果您想将响应时间百分点添加到您的服务的监视仪表板,则需要持续有效地计算它们。有一些算法能够以最小的CPU和内存成本(如前向衰减t-digestHdrHistogram)来计算百分位数的近似值。

3.3 应对负载增加

应对负载增加,包括两种方式:

  • 纵向扩展: scaling up 又称 垂直扩展(vertical scaling),即转向更强大的机器
  • 横向扩展: scaling out 又称 水平扩展(horizontal scaling),即将负载分布到多台小机器上

可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向扩展。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。

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

​随着分布式系统的工具和抽象越来越好,至少对于某些类型的应用而言,这种常识可能会改变。可以预见分布式数据系统将成为未来的默认设置,即使对不处理大量数据或流量的场景也如此。

大规模的系统架构通常是应用特定的—— 没有一招鲜吃遍天的通用可扩展架构。应用的问题可能是读取量、写入量、要存储的数据量、数据的复杂度、响应时间要求、访问模式或者所有问题的大杂烩。

一个良好适配应用的可扩展架构,是围绕着假设(assumption) 建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为扩展所做的工程投入就白费了,最糟糕的是适得其反。

尽管这些架构是应用程序特定的,但可扩展的架构通常也是从通用的积木块搭建而成的,并以常见的模式排列。在本书中,我们将讨论这些构件和模式。

4. 可维护性

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

为此,我们将特别关注软件系统的三个设计原则:

  1. 可操作性(Operability):
  • 意味着对系统的健康状态具有良好的可见性,并拥有有效的管理手段
  • 便于运维团队保持系统平稳运行,意味着更轻松的日常工作,进而运维团队能专注于高价值的事情
  1. 简单性(Simplicity):
  • 从系统中消除尽可能多的复杂度(complexity),使新工程师也能轻松理解系统
  • 复杂度(complexity)有各种可能的症状,例如:状态空间激增、模块间紧密耦合、纠结的依赖关系、不一致的命名和术语、解决性能问题的Hack、需要绕开的特例等
  • 用于消除额外复杂度的最好工具之一是抽象(abstraction)
  1. 可演化性(evolability):
  • 使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配
  • 也称为可扩展性(extensibility),可修改性(modifiability) 或可塑性(plasticity)