分片或数据分区
数据分区(Data Partitioning),也称为分片(Sharding),是一种将大型数据库(DB)拆分为多个较小部分的技术。其核心是在多台机器上拆分数据库或数据表,以提升应用的可管理性、性能、可用性和负载均衡能力。数据分片的主要动机在于,当数据库规模达到一定程度后,相较于纵向扩展(升级更强大的服务器),通过增加机器实现横向扩展更具成本效益和可行性。
分区方法
决定如何将应用数据库拆分为多个较小数据库的方法多种多样,以下是大型应用中最常见的三种分片方案。
a. 水平分区(Horizontal Partitioning)
在这种方案中,不同的行(row)存储在不同的表中。例如,如果我们在一张表中存储不同的地点信息,可以按照 ZIP 码(邮政编码)进行分片:将 ZIP 码小于 10000 的地点存储在一张表中,而 ZIP 码大于 10000 的地点存储在另一张表中。这种方式也被称为基于范围的分片(Range-based Sharding),因为它按照数据范围进行划分。
此方法的主要问题在于,如果用于分片的范围选择不当,可能会导致服务器负载不均衡。例如,上述基于 ZIP 码的分片方案假设地点在各个 ZIP 码区域内分布均匀,然而实际上,曼哈顿等人口密集地区的地点数远多于其周边的郊区城市,导致数据倾斜(skew)。
b. 垂直分区(Vertical Partitioning)
在这种方案中,不同类别的数据存储在不同的服务器上。例如,在构建类似 Instagram 的应用时,我们需要存储用户信息、上传的照片以及用户关注列表。可以将用户资料存储在一台数据库服务器上,好友列表存储在另一台服务器,而照片存储在第三台服务器上。
垂直分区实现相对简单,对应用程序影响较小。然而,该方法的主要问题是,随着应用规模的增长,可能需要进一步拆分单个功能的数据库。例如,如果应用增长到 1.4 亿用户,共存储了 100 亿张照片,仅靠一台服务器难以处理所有的照片元数据查询,此时仍需进一步分片。
c. 基于目录的分区(Directory Based Partitioning)
为解决上述方法中的问题,可以采用一种**松耦合(Loosely Coupled)**的方法,即构建一个目录服务(lookup service),用于记录当前的分区方案,并在数据库访问代码中对其进行抽象化处理。查询特定数据实体时,应用程序会先请求目录服务器,以获取数据所在的数据库服务器映射信息。
这种松耦合方法的优势在于,可以动态调整数据库集群,例如增加服务器或更改分区策略,而不会对应用程序产生影响,提高了系统的灵活性和可扩展性。
分区准则
a. 基于键或哈希的分区(Key or Hash-based Partitioning)
在该方案中,我们对存储实体的某个键属性应用哈希函数,以确定分区编号。例如,假设我们有 100 台数据库服务器,且每次插入新记录时 ID 递增,我们可以使用哈希函数 ID % 100
来计算该记录应存储或读取的服务器编号。此方法能够确保数据在各服务器间均匀分布。
该方法的主要问题是,它固定了数据库服务器的总数。如果要增加服务器,则必须修改哈希函数,这将导致数据重新分布(Data Redistribution),可能会引发服务停机(Downtime)。为解决这一问题,可以采用一致性哈希(Consistent Hashing)。
b. 列表分区(List Partitioning)
在该方案中,每个分区都被分配一组特定的值。当插入新记录时,系统会查找该记录的键值属于哪个分区,并存储到相应分区。例如,可以设定所有居住在冰岛、挪威、瑞典、芬兰和丹麦的用户存储在北欧国家(Nordic Countries)分区。
c. 轮询分区(Round-robin Partitioning)
这是一种非常简单的策略,可确保数据均匀分布。假设有 n
个分区,那么第 i
条记录将被分配到分区 (i mod n)
。
d. 复合分区(Composite Partitioning)
在该方案中,我们结合上述任何分区方法来构建新的分区策略。例如,可以先使用列表分区,再应用哈希分区。一致性哈希(Consistent Hashing)也可以视为哈希分区与列表分区的组合,其中哈希函数将键值空间缩小到一个可管理的列表大小。
分片的常见问题
在分片数据库上,执行不同操作时会面临一些额外的约束。这些约束大多数源于跨多个表或同一表的多行操作不再在同一台服务器上执行。以下是分片引入的一些约束和额外复杂性:
a. 联接与去规范化(Joins and Denormalization)
在单台服务器上执行联接操作是直接且简单的,但一旦数据库被分区并分布到多个机器上,通常无法执行跨分片的联接操作。这类联接的性能效率较低,因为数据需要从多个服务器汇总。解决此问题的常见方法是去规范化数据库,使得原本需要联接的查询可以从单一表中执行。当然,服务现在需要处理去规范化带来的所有问题,如数据不一致性。
b. 参照完整性(Referential Integrity)
如我们所见,在分区数据库上执行跨分片查询是不可行的,同样,在分片数据库中强制执行外键等数据完整性约束也会变得极其困难。大多数关系数据库管理系统(RDBMS)不支持跨不同数据库服务器的外键约束,这意味着需要参照完整性的应用程序往往不得不在应用代码中强制执行这些约束。在这种情况下,应用程序通常需要定期运行 SQL 任务,以清理悬挂的引用(dangling references)。
c. 重新平衡(Rebalancing)
有多种原因可能导致我们需要改变分片方案:
- 数据分布不均匀,例如某个特定的 ZIP 代码有大量地点,无法容纳在一个数据库分区中。
- 某个分片的负载过高,例如承载用户照片的数据库分片处理了过多的请求。
在这种情况下,我们需要创建更多的数据库分片,或者重新平衡现有的分片,即更改分区方案并将所有现有数据迁移到新的位置。完成这一操作且不产生停机时间是非常困难的。使用如基于目录的分区等方案可以使重新平衡过程变得更易接受,但这会增加系统的复杂性,并引入新的单点故障(如查找服务/数据库)。