可扩展性
向外扩展
分片?还是不分片?
这是一个问题,对吧?答案很简单:如非必要,尽量不分片。首先看是否能通过性能调优或者更好的应用或数据库设计来推迟分片。如果能足够长时间地推迟分片,也许可以直接购买更大地服务器,升级MySQL到性能更优地版本,然后继续使用单台服务器,也可以增加或减少复制。
简单地说,对单台服务器而言,数据大小或写负载变得太大时,分片将是不可避免的。如果不分片,而是尽可能地优化应用,系统能扩展到什么程度呢?答案可能会让你惊讶。有些非常受欢迎的应用,你可能以为从一开始就分片了,但实际上直到已经值数十亿美元并且流量及其巨大也还没有采用分片的设计。分片不是城里唯一的游戏,在没有必要的情况下采用分片的架构来构建应用会步履维艰
3.选择分区键(partitioning key)
数据分片最大的挑战是查找和获取数据:如何查找数据取决于如何进行分片。有很多方法,其中有一些方法会比另外一些更好。我们的目标是对那些最重要并且频繁查询的数据减少分片(记住,可扩展性法则的其中一条就是要避免不同节点间的交互)。这其中最重要的是如何为数据选择一个或多个分区键。分区键决定了每一行分配到哪一个分片中。如果直到一个对象的分区键,就可以回答如下两个问题:
- 1.应该在哪里存储数据?
- 2.应该从哪里取到希望得到的数据?
后面讲展示多个选择和使用分区键的方法。先看一个例子。假设像MySQL NDB Cluster那样来操作,并对每个表的主键使用哈希来将数据分割到各个分片中。这是一种非常简单的实现,但可扩展性不好,因为可能需要频繁检查所有的分片来获得需要的数据。例如,如果想查看user3的博客文章,可以从哪里找到呢?由于使用主键值而非用户名进行分割,博客文章可能均匀分散在所有的数据分片中。使用主键值哈希简化了判断数据存储在何处的操作,但却可能增加获取数据的难度,具体取决于需要什么数据以及是否知道主键。跨多个分片的查询比单个分片上的查询性能要差,但只要不涉及太多的分片,也不会太糟糕。最糟糕的情况是不知道需要的数据存储在哪里,这时候就需要扫描所有分片。
一个好的分区键常常是数据库中一个非常重要的实体的主键。这些键值决定了分片单元。例如,如果通过用户ID或客户端ID来分割数据,分片单元就是用户或者客户端。确定分区键一个比较好的办法是用实体——关系图,或一个等效的能显示所有实体及其关系的工具来展示数据模型。尽量把相关联的实体靠的更近。这样可以很直观地找出候选分区键。当然不要仅仅看图,同样地也要考虑应用的查询。即使两个实体在某些方面是相关联的,但如果很少或几乎不对其做关联操作,也可以打断这种联系来实现分片。
某些数据模型比其他的更容易进行分片,具体取决于实体——关系图中的关联性程度。如图的左边展示了一个易于分片的数据模型,右边的那个则很难分片。
左边的数据模型比较容易分片,因为与之相连的子图中大多数节点只有一个连接,很容易切断子图之间的联系。右边的数据模型则很难分片,因为它没有类似的子图,幸好大多数数据模型更像左边的图。
选择分区键的时候,尽可能选择那些能够避免跨分片查询的,但同时也要让分片足够小,以免过大的数据片导致问题。如果可能,应该期望分片尽可能同样小,这样在为不同数量的分片进行分组时能够很容易平衡。例如,如果应用只在美国使用,并且希望将数据分割为20个分片,则可能不应该按照州来划分,因为加利福尼亚的人口非常多。但可以按照县或者电话区号来划分,因为尽管并不是均匀的,但足以选择20个集合以粗略地表示等同的密集程度,并且基本上避免跨分片查询。
4.多个分区键
复杂的数据模型会使苏剧分片更加困难。许多应用拥有多个分区键,特别是存在两个或更多个"维度"的时候。换句话说,应用需要从不同的角度看到有效且连贯的数据视图。这意味着某些数据在系统内至少需要存储两份。例如,需要将博客应用的数据按照用户ID和文章ID进行分片,因为这两者都是应用查询数据时使用比较普遍的方式。试想一下这种情形:频繁地读取某个用户的所有文章,以及某个文章的所有评论。如果按用户分片就无法找到某篇文章的所有评论,而按文章分片则无法找到某个用户的所有文章。如果希望这两个查询都落到同一个分片上,就需要从两个维度进行分片。
需要多个分区键并不意味着需要去设计两个完全冗余的数据存储。我们来看看另一个例子:一个社交网站下的读书俱乐部站点,该站点的所有用户都可以对书进行评论。该网站可以显示所有书籍的所有评论,也能显示某个用户已经读过或评论过的所有书籍。假设为用户数据和书籍数据都设计了分片数据存储。而评论同时拥有用户ID和评论ID。这样就跨越了两个分片的边界。实际上却无须冗余存储两份评论数据,替代方案时,将评论和用户数据一起存储,然后把每个评论的标题和ID与书籍数据存储在一起。这样在渲染大多数关于某本书的评论的视图时无须同时访问用户和书籍数据存储,如果需要显示完整的评论内容,可以从用户数据存储中获得。
5.跨分片查询
大多数分片应用多少都有一些查询需要对多个分片的数据进行聚合或关联操作,例如,一个读书俱乐部网站要显示最受欢迎或最活跃的用户,就必须访问每一个分片。如何让这类查询很好地执行,是实现数据分片的架构中最困难的部分。虽然从应用的角度来看,这是一条查询,但实际上需要拆分成多条并行执行的查询,每个分片上执行一条。一个设计良好的数据库抽象层能够减轻这个问题,但类似的查询仍然会比分片内查询要慢并且更加昂贵,所以通常会更加依赖缓存。一些语言,如PHP,对并行执行多条查询的支持不够好。普遍的做法是使用C或Java编写一个辅助应用来执行查询并聚合结果集。PHP应用只需要查询该辅助应用即可,例如Web服务或者类似Gearman的工作者服务。
跨分片查询也可以借助汇总表来执行。可以遍历所有分片来生成汇总表并将结果在每个分片上冗余存储。如果在每个分片上存储重复数据太过浪费,也可以把汇总表放到另外一个数据存储中,这样就只需要存储一份了。未分片的数据通常存储在全局节点中,可以使用缓存来分担负载。如果数据的均衡分布非常重要,或者没有很好的分区键,一些应用会采用随机分片的方式。分布式检索应用就是个很好的例子。这种场景下,跨分片查询和聚合查询非常常见。跨分片查询并不是数据分片面临的唯一难题。维护数据一致性同样困难。外键无法在分片间工作,因此需要由应用来检查参照一致性,或者只在分片内使用外键,因为分片内的内部一致性可能是最重要的。还可以使用XA事务,但由于开销太大,现实中使用很少。还可以设计一些定期执行的清理过程。例如,如果一个用户的读书俱乐部账号到期,并不需要立刻将其移除。可以写一个定期任务将用户评论从每个书籍分片中移除,也可以写一个检查脚本周期性运行以确保分片间的数据一致性
6.分配数据、分片和节点
分片和节点不一定是一对一的关系,应该尽可能地让分片地大小比节点容量小很多,这样就可以在单个节点上存储多个分片。保持分片足够小更容易管理。这将使数据地备份和恢复更加容易,如果表很小,那么像更改表结构这样的操作会更加容易。例如,假设有一个100GB的表,你可以直接存储,也可以将其划分为100个1GB的分片,并存储在单个节点上。现在假如要向表上增加一个索引,在单个100GB的表上的执行时间会比100个1GB分片上执行的总时间更长,因为1GB的分片更容易全部加载到内存中,并且在执行ALTER TABLE时还会导致数据不可用,阻塞1GB的数据比阻塞100GB的数据要好得多。
小一点的分片也便于转移。这有助于重新分配容量,平衡各个节点的分片。转移分片的效率一般都不高。通常需要先将受影响的分片设置为只读模式(也是需要在应用中构建的特性),提取数据,然后转移到另外一个节点。这包括使用mysqldump获取数据然后使用mysql命令将其重新导入。如果使用的是Percona Server,可以通过XtraBackup在服务器间转移文件,这比转储和重新载入要高效得多。
除了在节点间移动分片,你可能还需要考虑在分片间移动数据,并尽量不中断整个应用提供服务。如果分片太大,就很难通过移动整个分片来平衡容量,这时候可能需要将一部分数据(例如一个用户)转移到其他分片。分片间转移数据比转移分片要更复杂,应该尽量避免这么做。这也是我们建议设置分片大小尽量易于管理的原因之一。分片的相对大小取决于应用的需求。简单地说,我们说的"易于管理的大小"是指保持表足够小,以便能在5或10分钟内提供日常的维护工作,例如ALTER TABLE、CHECK TABLE或者OPTIMIZE TABLE.
如果将分片设置得太小,会产生太多得表,这可能引发文件系统或MySQL内部结构得问题。另外太小的分片还会导致跨分片查询增多。