掘金 后端 ( ) • 2024-03-31 17:24

theme: minimalism highlight: agate

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

背景介绍

一个系统之所以逐渐走向分布式架构,必然有其深刻且必要的动因。在众多驱动因素中,伸缩性(Scalability)无疑是最为显著的。对于Scalability的需求,我们可以细致地划分为两个维度进行剖析: 在这里插入图片描述


数据可伸缩性

随着数据量的急剧增长,单台机器很难以经济高效的方式承载所有资料。因此,将数据分布到多个节点上成为了解决之道,从而确保了数据的可靠性、可扩展性以及处理性能。

案例

一个典型的例子便是分布式数据库,它采用分布式架构,能够轻松应对海量数据的存储和访问需求。

计算可伸缩性

当面对复杂且计算量巨大的任务时,如科学运算等,单台机器往往难以在合理的时间内完成运算。为了提升运算效率,我们需要将计算任务分散到多个节点上并行处理。这种分散式计算模式不仅提高了运算速度,还降低了成本,使得大规模计算任务变得更为可行。

案例

Apache Hadoop、Apache Spark、Apache Flink和Apache Storm是大数据分布式计算框架的代表,其中Hadoop包括HDFS用于存储大规模数据和MapReduce用于并行处理,Spark则在内存中提供高性能数据处理,Flink支持流式和批处理,而Storm则专注于实时数据流的高吞吐量处理。

以上两个维度都是推动系统走向分散式架构的重要力量。通过合理地利用分布式架构的优势,我们可以有效地解决数据存储和运算能力上的挑战,为系统的可持续发展奠定坚实基础。


代价和权衡

分布式的代价

接下来,我将深入剖析这两种需求在实施过程中可能遭遇的难题及其常见的解决策略。无论选择哪种需求,一旦决定采用分布式架构,就意味着需要做出一些必要的妥协:

  • 效率上的牺牲:在分布式架构中,网络延迟以及节点间协同工作的复杂性常常导致执行效率的降低。

分布式系统所固有的特性,需要我们在追求高可用性、扩展性的同时,对执行效率做出一定的妥协。

  • AP弹性的折中:单机环境下轻松应对的运算任务,在分散式架构中可能会面临实现上的挑战。

分布式系统需要处理更多的分布式协调和数据一致性问题,从而导致某些运算任务的难度增加。

  • 运维能力的考验:分布式架构中的问题通常具有难以复现和追踪的特性,这无疑增加了系统维护的难度。

运维人员需要面对更复杂的系统结构和更多的潜在故障点,这需要在维护分布式系统时具备更高的技术水平和更丰富的经验。

与单机系统相似,分布式架构在系统设计上同样存在一些权衡与取舍。这些取舍要求我们根据实际情况做出合理的选择和权衡,以实现系统的整体性能和稳定性的最大化。

分布式的权衡

如同单机系统一般,分布式架构在系统设计上也存在一系列权衡和考量。这些设计决策涉及多方面的因素,需要我们综合考虑系统性能、可扩展性、容错性等多个维度,以实现最优的系统设计和性能表现。

权衡策略

我们分析以下针对于分布式系统的性能和可用性的权衡策略和方向,总体为以下三点为主:

  1. 致力于精细调整CPU使用效率和IO效率,旨在实现系统性能的最大化。
  2. 针对读取和写入操作,我们采取专项优化策略,以提升数据处理效率,确保数据处理的迅捷与高效。
  3. 注重平衡Throughput(吞吐量)与Latency(延迟)之间的关系,以满足不同业务场景对性能的不同需求,实现系统性能的综合提升。 在这里插入图片描述 选择了不同的权衡策略,就意味着会形成迥异的系统架构。在后续的探讨中,我们将深入剖析那些面向设计的决策因素,它们如何塑造出各具特色的分布式系统。

分布式技术方向

分布式系统具有高度的特化性,而非通用性。因此,不同的设计决策将产生出不同应用功能的系统。正如先前所述,我初步将分布式系统划分为两大类:数据系统和运算系统。

数据系统

数据系统的核心技术手段主要聚焦于partition(分区)和replication(复制),在以不同的读写方式,便能衍生出众多不同的变种。在此过程中,几个关键的设计决策起着至关重要的作用。 在这里插入图片描述

  • 资料分区:通过有效的资料分区策略,实现数据的合理分布与存储,提升系统处理性能。
  • 读写分离:将读写操作进行分离处理,优化系统资源的分配与利用,提高系统的并发处理能力。
  • 数据复制:采用数据复制技术,实现数据的冗余存储,提高系统的可用性和容错能力。
  • 错误恢复:建立完善的错误恢复机制,快速响应并处理各种异常情况,确保系统的稳定运行和数据安全。
  • 处理粒度:合理设定处理粒度,以平衡计算资源的使用与系统性能,实现高效的数据处理。
  • 可用性保证:通过多种技术手段确保系统的高可用性,减少服务中断的风险,保障用户持续稳定的访问体验。

运算系统

运算系统而言,其核心技术手段在于数据平行化运算平行化。尤其在运算过程中涉及状态变更或参照易变资料时,情况会变得更加错综复杂。几个关键的设计决策涵盖: 在这里插入图片描述

  • 分工模式:合理设计系统内部的分工模式,以优化资源分配和提高处理效率。
  • 信息交互:高效的信息交换方式,确保各组件之间的顺畅通信,降低通信延迟。
  • 运算支持:不同运算需求,提供丰富的运算种类支持,满足多样化的业务场景。
  • 可用性保障:采取多种措施确保系统的高可用性,降低服务中断的风险,提升用户体验。
  • 状态管理与回滚机制:建立完善的状态管理机制,并引入回滚机制,以应对异常情况,保障系统的稳定运行。

分布式数据系统

分布式数据系统的两大核心问题根源在于partition(分区)和replication(复制)。

Partition(分区)

首先,我们来谈谈partition。当数据量庞大到无法单独存储在一台机器上,或者对数据的运算需求超出了单台机器的处理能力时,就需要考虑进行partition。简而言之,partition就是将数据切分并分散存储到多台机器上,以此实现数据的水平扩展和高效处理。 在这里插入图片描述

Round-Robin(轮询)

将数据依次轮流分配给多台机器,其显著优势在于实现负载均衡,确保每台机器都能得到适量的处理任务。 在这里插入图片描述 **优化Round-Robin策略,可以采用线程池作为变型方案:**每台机器分配固定数量的线程,以限制单个运算的耗时,避免长时间占用资源而影响后续运算,能有效提高系统的吞吐量和响应速度。

局限性

不适用于涉及会话(session)或数据相依性(如需要join操作)的应用场景,因为轮流分配可能导致相关数据分散在不同机器上,从而增加处理复杂性和延迟。

Range(范围)

为每台机器划定一个数据范围,例如,将key值在1- 5000之间的数据分配给A机器,将key值在5001 - 10000之间的数据分配给B机器。 在这里插入图片描述 这种方法的优点是简单直观,仅需维护少量元数据。

局限性

弹性较差,难以应对动态变化的需求;同时可能存在热点问题,即大量资料数值集中在某个范围内,导致某台机器负载过重。MongoDB在早期版本就采用了这种切割方式。

Hash(平均)

利用哈希函数来决定资料应存放在哪台机器上。简单的哈希方法如取余数法存在资料迁移问题,特别是在新增机器时。 在这里插入图片描述 现在更常用的是一致性哈希(Consistent Hashing)来避免这一问题。一致性哈希能够实现数据的均匀分布,并减少所需维护的元数据量。

Consistent Hashing(一致性哈希)

  1. 一致性哈希算法的核心在于其独特的数据结构——一致性哈希环。这个环的起点设定在0,终点为2^32 - 1,首尾相连,形成了一个闭合的圆环。环上的整数依照逆时针方向依次排列,因此整个环的整数分布范围覆盖了从0到2^32 - 1的完整区间。

  2. 整个哈希值空间被巧妙地组织成一个虚拟的圆环。每个节点通过其IP地址或主机名作为关键字进行哈希计算,得到的哈希值确定了该节点在环上的具体位置。当数据需要进行存储时,其哈希值将按顺时针方向在环上寻找最近的节点进行存放。以图中的例子来说,data的哈希位置决定了它应当存放在node2节点上。 在这里插入图片描述

  3. 当从n个节点扩容至n+1个节点时,一致性Hash算法所影响的缓存数据量相对较少,其影响范围仅为0~1/n,这意味着最多只有一个节点的缓存数据会失效。

局限性

缓存在每个节点上分布不均,毕竟 hash 值随机,那节点在环上的位置也随机。

Manual(手动制表)

手动创建一个对照表来指定数据的分配方式,这种方法的优点在于灵活性高,可以根据实际需求进行任意分配。 在这里插入图片描述

常见的解决方案是利用Apache ZooKeeper(ZK),它是一个用于维护集群中共享状态的分布式系统。通过ZK来管理这些元数据已经成为众多分布式系统的标准做法。ZK内置了高可用性和一致性机制,确保数据的可靠性和一致性。

在生产环境中,为了确保系统的健壮性,通常建议至少部署2n+1(n>0)个节点(最少为3个节点),这样即使不超过n个节点发生故障,整个系统仍然能够正常运行并提供服务。这种设计使得ZooKeeper成为分布式系统中元数据管理的可靠选择。

局限性

它也带来了额外的挑战,需要手动控制资料和负载的均衡,同时维护大量的元数据。这增加了系统的复杂性和管理成本。

技术取舍简介

在读写操作中常常存在权衡取舍,优化写入操作可能会对读取操作产生负面影响。

  • 基于Round-Robin,尽管它能够平均分配写入负载,但在执行范围查询(Range Query)时,需要向所有机器发送查询请求,这可能导致查询效率降低。

  • 基于Range,虽然有助于针对范围查询,但写入操作可能集中在同一台机器上,造成写入瓶颈。

案例介绍

若用户表格数据按区域进行切分,这对于那些经常带有区域条件的应用来说是有利的,因为这些应用可以仅锁定几个区域进行查询,而无需将查询请求分散到所有机器上。

注意,这种切分方式可能不利于其他不涉及地区条件的应用,它们可能需要跨多个分区甚至所有分区进行数据检索,从而增加了查询的复杂性和开销。

Replication(复制)

在partition时,首要考虑的是如何合理切分数据。这一决策涉及多个因素,包括数据的访问模式、计算需求、以及系统的容错能力等。一个合理的切分策略能够确保数据分布的均衡性,提高系统的整体性能和可靠性。

数据复制

数据复制是确保系统可用性的关键手段。通过将数据复制到不同的机器上,即使其中一台机器出现故障,我们依然能够获取到所需的数据,从而保障了数据的可靠性和可访问性。 在这里插入图片描述

非强一致性

数据复制也带来了一个固有的问题,那就是延迟。在复制完成之前,不同机器上的数据可能会存在不一致的情况。这种不一致性是数据复制过程中不可避免的现象。 在这里插入图片描述 有些系统对数据一致性读取的要求非常高,为了满足这一需求,它们会采用同步复制的方式。

同步复制

在这种模式下,只有当数据复制完成后,数据的写入操作才会被认为是完成的。然而,这种同步复制的方式会导致操作速度变慢,特别是在副本数量不断增加的情况下,性能问题会变得更加突出。

同步复制和异步复制

为了提高操作效率,非同步复制成为了一个更为有效的选择。虽然非同步复制会导致数据在一定时间内存在不一致性,但在许多应用场景中,这种短暂的不一致性是可以接受的。通过权衡数据一致性和操作效率,我们可以根据实际需求选择适合的复制方式。

基于前面的介绍,我们了解到同步复制和异步复制各自适用于特定的场景,但它们的选择有时显得较为极端。那么,是否存在一种折衷的方法呢?答案是肯定的。这时候就衍生出了Quorum算法。

Quorum算法

Quorum算法是一种在分布式系统中常用的投票算法,主要用于保证数据冗余和最终一致性。它的主要数学思想来源于鸽巢原理,通过赋予分布式系统中的每一份数据拷贝对象一票,确保每一个读操作或写操作都需要获得足够的票数才能执行。

公式介绍

Quorum算法通过设定R(读)+W(写)>N的条件,实现了分布式系统中数据一致性的保障。这里的N表示数据所具有的副本数,R表示完成读操作所需要读取的最小副本数,W表示完成写操作所需要写入的最小副本数。

当R+W>N时,意味着读操作和写操作所涉及的副本集合有重叠,即至少有一个副本同时参与了读和写操作。这种重叠确保了读操作能够读取到最新的、已经写入的数据,从而保证了强一致性。

批量出发同步

每次数据更新都立即触发复本更新显然不是一种高效的做法。为了提高效率,我们通常会采用批量更新的策略,即累积一定数量的更新操作或等待一段时间后再进行统一的复本更新,这种方式能够显著减少复本更新的频率,进而提升系统的整体性能。

CAP最终一致性

追求最终一致性(Eventually Consistency),最终一致性意味着,只要数据不再更新,最终会有一个时刻,所有节点会协调达到一致的状态。这种说法听起来似乎并不那么稳固可靠。尽管存在这样的不可靠性,但为什么我们还要使用这种机制呢?这依然是权衡取舍的问题。我们选择在某种程度上舍弃强一致性,以换取其他更为重要的特性。

CAP定理告诉我们,在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)这三个特性最多只能同时满足两个。