可扩展性
向外扩展
7.在节点上部署分片
需要确定如何在节点上部署数据分片。以下是一些常用的办法:
- 1.每个分片使用单一数据库,并且数据库名要相同。典型的应用场景是需要为每个分片都能镜像到原应用的结构。这在部署多个应用实例,并且每个实例对应一个分片时很有用
- 2.将多个分片的表放到一个数据库中,在每个表名上包含分片号(例如bookclub.comments_23),这种配置下,单个数据库可以支持多个数据分片。
- 3.为每个分片使用一个数据库,并在数据库中包含所有应用需要的表。在数据库中包含分片号(例如表名可能是bookclub_23.comments或者bookclub_23.users等),但表名不包括分片号。当应用连接到单个数据库并且不在查询中指定数据库名时,这种做法很常见。其优点是无须为每个分片专门编写查询,也便于对只使用单个数据库的应用进行分片
- 4.每个分片使用一个数据库,并在数据库名和表名中包含分片号(例如表名可以是bookclub_23.comments_23)
- 5.在每个节点上运行多个MySQL实例,每个实例上有一个或多个分片,可以使用上面提到的方式的任意组合来安排分片。
如果在标命中包含了分片号,就需要在查询模板里插入分片号。常用的方法是在查询中使用特殊的"神奇的"占位符,例如sprintf()这样的格式化函数中的%s,或者使用变量做字符串插值。以下是在PHP中创建查询模板的方法:
php
$sql = "SELECT book_id, book_title FROM bookclub_%d.comments_%d .....";
$res=mysql_query(sprintf($sql, $shardno, $shardno), $conn);
也可以就使用字符串插值的方法:
php
$sql="SELECT book_id, book_title FROM bookclub_$shardno.comments_$shardno ...";
$res = mysql_query($sql, $conn);
这在新应用中很容易实现,但对于已有的应用则优点困难。构建新应用时,查询模板并不是问题,我们倾向于使用每个分片一个数据库的方式,并把分片号写到数据库名和表名中。这会增加ALTER TABLE这类操作的复杂度,但也有如下一些优点:
- 1.如果分片全部在一个数据库中,转移分片会比较容易
- 2.因为数据库本身是文件系统中的一个目录,所以可以很方便地管理一个分片地文件。
- 3.如果分片互不关联,则很容易查看分片的大小
- 4.全局唯一表名可避免误操作。如果表名每个地方都相同,很容易因为连接到错误的节点而查询了错误的分片,或者是将一个分片的数据误导入另外一个分片的表中.
你可能想知道应用的数据是否具有某种"分片亲和性"。也许将某些分片放在一起(在同一台服务器,同一个子网,同一个数据中心,或者同一个交换网络中)可以利用数据访问模式的相关性,能够带来些好处。例如,可以按照用户进行分片,然后将同一个国家的用户放到同一个节点的分片上。
为已有的应用增加分片支持的结果往往是一个节点对应一个分片。这种简化的设计可以减少对应用查询的修改。分片对应用而言通常是一种颠覆性的改变,所以应尽可能简化它。如果在分片后,每个节点看起来就像是整个应用数据的缩略图,就无须改变大多数查询或者担心查询是否传递到期望的节点
8.固定分配
将数据分配到分片中有两种主要的方法:固定分片和动态分配。两种方法都需要一个分区函数,使用行的分区键值作为输入,返回存储该行的分片(这里的"函数"使用了其数学含义,表示从输入(域)到输出(区间)的映射。如你所见,可以用很多方式来创建类似的函数,包括在数据中使用查找表)。固定分配使用的分区函数仅仅依赖于分区键的值。哈希函数和取模运算就是很好的例子。这些函数按照每个分区键的值将数据分散到一定数量的"桶"中。
假设有100个桶,你希望弄清楚用户111该放到哪个桶里。如果使用的是对数字取模的方式,答案很简单:111对100取模的值为11,所以应该将其放到第11个分片中。而入股哦使用CRC32()函数来做哈希,答案是81.
sql
mysql> SELECT CRC32(111)%100;
固定分配的主要优点是简单,开销低,甚至可以在应用中直接硬编码。但固定分配也有如下缺点:
- 1.如果分片很大并且数量不多,就很难平衡不同分片间的负载
- 2.固定分片的方式无法自定义数据放到哪个分片上,这一点对于那些在分片间负载不均衡的应用来说尤其重要。一些数据可能比其他的更加活跃,入股哦这些热点数据都分配到同一个分片中,固定分配的方式就无法通过热点数据转移的方式来平衡负载。(如果每个分片的数据量切分得比较小,这个问题就没那么严重,根据大数定律,这样做会更容易将热点数据平均分配到不同分片)
- 3.修改分片策略通常比较困难,因为需要重新分配已有的数据。例如,如果通过模10的哈希函数来进行分片,就会有10个分片。如果应用增长使得分片变大,如果要拆成20个分片,就需要对所有数据重新哈希,这回导致更新大量数据,并在分片间转移数据
正是由于这些限制,我们倾向于为新应用选择动态分配的方式。但如果是为已有的应用做分片,使用固定分配策略可能会更容易些,因为它更简单。也就是说,大多数使用固定分配的应用最后迟早要使用动态分配策略
9.动态分配
另外一个选择使用动态分配,将每个数据单元映射到一个分片。假设一个有两列的表,包括用户ID和分片ID.
sql
mysql>CREATE TABLE user_to_shard(user_id INT NOT NULL, shard_id INT NOT NULL, PRIMARY KEY(user_id));
这个表本身就是分区函数。给定分区键(用户ID)的值就可以获得分片号。如果该行不存在,就从目标分片中找到并将其加入到表中。也可以推迟更新------这就是动态分配的含义。动态分配增加了分区函数的开销,因为需要额外调用一次外部资源,例如目录服务器(存储映射关系的数据存储节点)。出于效率方面的考虑,这种架构常常需要更多的分层。例如,可以使用一个分布式缓存系统将目录服务器的数据加载到内存中,因为这些数据平时改动很小。或者更普遍地,你可以直接向USERS表中增加一个shard_id列用于存储分片号。
动态分配的最大好处是可以对数据存储位置做细粒度的控制。这使得均衡分配数据到分片更加容易,并可提供适应未知改变的灵活性。动态映射可以在简单的键------分片(key-to-shard)映射的基础上建立多层次的分片策略。例如,可以建立一个双重映射,将每个分片单元指定到一个分组中(例如,读书俱乐部的用户组),然后尽可能将这些组保持在同一个分片中。这样可以利用分片亲和性,避免跨分片查询。
如果使用动态分配策略,可以生成不均衡的分片。如果服务器能力不相同,或者希望将其中一些分片用于特定目的(例如归档数据),这可能会有用。如果能够做到随时重新平衡分片,也可以为分片和节点间维持一一对应的映射关系,这不会浪费容量。也有些人喜欢简单的每个节点一个分片的方式。(但是请记住,保持分片尽可能小是有好处的。)
动态分配以及灵活地利用分片亲和性有助于减轻规模扩大而带来的跨分片查询。假设一个跨分片查询涉及四个节点,当使用固定分配时,任何给定的查询可能需要访问所有分片,但考虑一些当数据存储到400个分片时会发生什么?固定分配策略需要访问400个分片,而动态分配方式依然只需要访问3个。
动态分配可以让分片策略根据需要变得很复杂。固定分配则没有这么多选择。
10.混合动态分配和固定分配
可以混合使用固定分配和动态分配。这种方法通常很有用。有时候甚至必需要混合使用。目录映射不太大时,动态分配可以很好胜任。但如果分片单元太多,效果就会变差。以一个存储网站链接的系统为例。这样一个站点需要存储数百亿的行,所使用的分区键时源地址和目的地址URL的组合.(这两个URL的任意一个都可能有好几亿的链接,因此,单独一个URL并不适合做分区键)。但是在映射表中存储所有的源地址和目的地址URL组合并不合理,因为数据量太大了,每个URL都需要很多存储空间。
一个解决方案是将URL相连并将其哈希到固定数目的桶中,然后把桶动态地映射到分片上。如果桶地数目足够大------例如100万个------你就能把大多数数据分配到每个分片上,获得动态分配的大部分好处,而无须使用庞大的映射表
11.显式分配
第三种分配策略是在应用插入新的数据行时,显式地选择目标分片。这种策略在已有的数据上很难做到。所以在为应用增加分片时很少使用,但在某些情况下还是有用的。这个方法使把数据分片好编码到ID中 ,这和之前提到的避免主------主复制主键冲突策略比较相似。例如,假设应用要创建一个用户3,将其分配到第11个分片中,并使用BIGINT列的高八位来保存分片号。这样最终的ID就是(11<<56) +3,即792633534417207299。应用可以很方便地从中抽取用户ID和分片好,如下例所示:
sql
mysql> SELECT (792633534417207299 >> 56) AS shard_id, 792633534417207299 & ~(11 << 56) AS user_id;
现在假设要为该用户创建一条评论,并存储在同一个分片中。应用可以为该用户分配一个评论ID5,然后以同样地方式组合5和分片号11.这种方法地好处使每个对象的ID同时包含了分区键,而其他方法需要一次关联或查找来确定分区键。如果要从数据库中检索某个特定的评论,无须知道哪个用户拥有它,对象ID会告诉你到哪里去着。如果对象使通过用户ID动态分片的,就得先找到该评论的用户,然后通过目录服务器找到对应的数据分片。
另一个解决方案使将分区键存储在一个单独的列里。例如,你可能从不会单独引用评论5,但是评论5属于用户3.这种方式可能会让一些人高性,因为这不未被第一范式;然而额外的列会增加开销、编码,以及其他不便之处。(这也是我们将两值存在单独一列的优点之一)。
显式分配的缺点使分片方式是固定的,很难做到分片间的负载均衡。但结合固定分配和动态分配,该方法就能够很好地工作。不再像之前那样哈希到固定数目的桶里并将其映射到节点,而是将桶作为对象的一部分进行编码。这样应用就能够控制数据的存储未知,因此可以将相关联的数据一起放到同样的分片中。
BoardRead使用了该技术的一个变种:它把分区键编码到Sphinx的文档ID内。这使得在分片数据存储中查找每个查询结果的关联数据变得容易。我们讨论了混合分配方式,因为在某些场景下它是有用的。但正常情况下并不推荐这样用。倾向于尽可能使用动态分配,避免显式分配。