到目前为止,我们讨论的系统中,假设了所有的机器都会存储所有数据,在前面讨论复制的章节中,主节点在收到客户端的写入请求时,将全部数据分别保存在本地以及其它的从节点上,这样的存储方式存在以下的问题:
- 可扩展性:如果使用主备方式进行数据复制,所有的工作都落在主节点上,在负载很大的情况下,主节点很快会成为系统的瓶颈;
- 单机瓶颈:单台机器由于其物理硬件的能力(硬盘、内存、CPU等),总会遇到处理的上限。
- 故障隔离:如果一部分数据出现故障,会影响所有数据的访问,降低了系统的整体可用性。例如,如果能够将不同城市的数据保存在不同的节点上,当某个地区的服务出现异常时,就不会影响其它地区的数据。
在系统需要进行扩展时,通常有两种不同的扩展方案。如图6.1所示:
- 垂直扩展(Vertical Scaling):也称为"Scale Up",通过在单一服务器上增加更多资源(如更快的 CPU、更多的 RAM、更大的硬盘)来提升处理能力。
- 水平扩展(Horizontal Scaling):也称为"Scale Out",通过增加更多的服务器来分担负载,将它们组成一个集群共同工作。

垂直扩展方案只需要扩展单台机器的处理能力,有以下的优点:
- 简单:管理和维护一台机器远比管理一个集群简单。
- 数据一致性强:所有数据都在一台机器上,不需要处理分布式系统中的数据同步和一致性问题,事务处理简单。
- 低延迟:进程间通信(IPC)在单机内部非常快,没有网络开销。
- 对应用透明:应用代码通常不需要做大的改动来适应更强的硬件。
然而,任何机器的CPU、内存扩展都有其物理上限,无法无限制一直提升下去。同时由于存在单点问题,服务器一旦宕机,整个服务将完全中断,可用性低,也给维护单台机器带来很大挑战,通常需要停机维护。
对比而言,水平扩展通过增加机器扩展系统的处理能力,有以下的优点:
- 理论上无限扩展:当负载增加时,只需向集群中添加更多标准服务器即可,扩展性极佳。
- 高可用性与容错性:集群中的一台或多台服务器宕机,系统可以继续服务(可能会有性能下降),没有单点故障。
- 成本效益高:可以使用大量廉价的硬件来组成集群,总体拥有成本更低。
- 弹性伸缩:可以根据负载动态地增加或减少服务器数量,特别适合云环境。
- 在某些场景下,必须进行数据分区。比如服务可以在不同大洲、国家、区域被访问,需要在这些地区分别部署服务。
水平扩展方案中,由于数据分摊到集群上的不同节点上,面临以下的挑战:
- 架构复杂性:需要引入额外的组件和技术,如负载均衡器、服务发现、分布式数据存储、配置管理等。
- 数据一致性挑战:跨多台机器管理数据状态和保证一致性非常困难,这将是后续分布式事务章节讨论的重点。
- 网络延迟:节点间的通信依赖网络,相比单机内的 IPC,延迟更高且不可靠。
以上简述了垂直和水平两种扩展方案的优缺点,如果业务处于早期阶段,用户量和数据量增长可预测且在单机范围内,推荐优先选择垂直扩展方案;而如果预期用户量会爆炸式增长,需要应对不可预测的流量洪峰,则优先选择水平扩展。本章中主要讨论水平分区策略。
分布式系统中的水平扩展,引入了***“分区(Partition)”***的概念,将数据分摊到不同的机器,如图6.2所示,数据被分摊到两个不同的节点上,不再是所有节点保存所有数据的情况。

注意
这里的分区指的是不同节点之间划分数据的方式,而不是网络分区导致的通信中断。
“分区"在不同的系统中有不同的叫法,在有的系统中使用"shared"表示分区,有的系统使用"region”。
通常而言,分区和复制技术会结合在一起。对于一份数据,会保存在同一分区的多个节点上,这意味着一条记录会保存在特定的分区,而分区有多个节点保存副本数据,以提高系统的容错性,如图6.3所示。

在数据进行分区之后,有些操作会变得复杂:
- 如何访问数据:原先只需要访问主节点即可,现在需要考虑如何路由数据请求到对应的分区。这是一个请求路由问题,我们将在6.3.2节中深入讨论这个问题。
- 分区再平衡:划分数据分区的目的之一是将数据尽量均衡得分布在多个分区中,但是当不同分区数据分布不均时,要考虑数据分区的再平衡问题。
- 全排序问题:我们将看到,不同的分区策略,对全排序的支持不一样。如果全排序操作是系统需要考虑的常见操作,要尽量选择全排序友好的分区策略。
在本章中,我们将讨论以下和分区相关的话题:
- 有哪些常见的数据分区方式,它们的优缺点是什么;
- 如何路由数据请求到对应的分区,实现请求路由的常见策略。
- 在某些分区的访问量较大时,出现了分区热点问题,应该如何解决。
- 最后,做为复制和分区这两章的阶段性总结,我们将考察几个项目的实现方案。
6.1 分区策略 #
6.1.1 范围分区 #
范围分区是分布式数据存储中最直观、最符合直觉的分区策略。它的核心思想非常朴素:将数据主键看作一个连续的有序序列,将每一段切分出来的连续范围分配给不同的节点管理。
想象一本字典,按照词条的首字母划分了不同的分卷:
- 卷1:包含 A - B 开头的词条。
- 卷2:包含 C - E 开头的词条。
- …
这就是典型的范围分区,在这个类比中:词条单词就是分区键(Partition Key),每一卷书就是一个分区(Partition/Tablet/Region)。当你需要查找单词 “Apple” 时,你知道它一定在卷1;查找 “Cake” 时,它一定在卷2。