可扩展性
向外扩展
12.重新均衡分片数据
如有必要,可以通过在分片间移动数据来达到负载均衡。举个例子,许多读者可能听一些大型图片分享网站或流行社区网站的开发者提到过用于分片间移动用户数据的工具。在分片间移动数据的好处很明显。例如,当需要升级硬件时,可以将用户数据从旧分片转移到新分片上,而无须暂停整个分片的服务或将其设置为只读。然而,我们也应该尽量避免重新均衡分片数据,因为这可能会影响用户使用。在分片间转移数据也使得为应用增加新特性更加困难,因为新特性可能还需要包含针对重新均衡脚本的升级。如果分片足够小,就无须这么做;也可以经常移动整个分片来重新均衡负载,这比移动分片中的部分数据要容易得多(并且以每行数据开销来衡量的话,更有效率)。
一个较好的策略是使用动态分片策略,并将新数据随机分配到分片中。当一个分片快满时,可以设置一个标志位,告诉应用不要再往这里放数据了。如果未来需要向分片中放入更多数据,可以直接把标记位清除。假设安装了一个新的MySQL节点,上面有100个分片。先将它们的标记设置为1,这样应用就知道它们正准备接受新数据。一旦它们的数据足够多时(例如,每个分片10 000个用户),就把标记位设置为0.之后,如果节点因为大量废弃账号导致负载不足,可以冲洗你打开一些分片向其中增加新用户。
如果升级应用并且增加的新特性会导致每个分片的查询负载升高,或者只是算错了负载,可以把一些分片移到新节点来减轻负载。缺点时操作期间整个分片会变成只读或者处于离线状态。这需要根据实际情况来看看是否能接受。
另外一种使用得较多的策略时为每个分片设置两台备库,每个备库都有该分片的完整数据,然后每个备库负责其中一半的数据,并完全停止在主库上查询。这样每个备库都有有一半它不会用到的数据;我们可以使用一些工具,例如Percona Toolkit的pt-archiver,在后台运行,移除那些不再需要的数据。这种办法很简单并且几乎不需要停机。
13.生成全局唯一ID
当希望把一个现有系统转换为分片数据存储时,经常会需要在多台及其上生成全局唯一ID.单一数据存储时通常可以使用AUTO_INCREMENT列来获取唯一ID.但涉及多台服务器时就不奏效了。以下几种方法可以解决这个问题:
- 1.使用auto_increment_increment和auto_increment_offset
这两个服务器变量可以让MySQL以期望的值和偏移量来增加AUTO_INCREMENT列的值。举一个最简单的场景,只有两台服务器,可以配置这两台服务器自增幅度为2,其中一台的偏移量为1,另外一台为2(两个都不可以设置为0).这样一台服务器总是包含偶数,另外一台则总是包含奇数。这种设置可以配置到服务器的每一个表里。这种方法简单,并且不依赖于某个节点,因此是生成唯一ID的比较普遍的方法。 但这需要非常仔细地配置服务器。很容易因为配置错误生成重复数字,特别是当增加服务器需要改变其角色,或进行灾难恢复时。 - 2.全局节点中创建表
在一个全局数据库节点中创建一个包含AUTO_INCREMENT列的表,应用可以通过这个表来生成唯一数字。 - 3.使用memcached
在memcached的API中有一个incr()函数,可以自动增长一个数字并返回结果 - 4.批量分配数字。
应用可以从一个全局节点中请求一批数字,用完后再申请 - 5.使用复合值
可以使用一个复合值来做唯一ID,例如分片号和自增数的组合 - 6.使用GUID值
可以使用UUID()函数来生成全局唯一值。注意,尽管这个函数在基于语句的复制时不能正确复制,但是可以先获得这个值,再存放到应用的内存中,然后作为数字在查询中使用。GUID的值很大并且不连续,因此不适合做InnoDB表的主键。在5.1及更新的版本中还有一个函数UUID_SHORT()能够生成连续的值,并使用64位代替了之前的128位
如果使用全局分配器来产生唯一ID,要注意避免单点争用成为应用的性能瓶颈。虽然memcached()方法执行速度快(每秒数万个),但不具备持久性。每次重启memcached服务都需要重新初始化缓存里的值。由于需要首先找到所有的分片中的最大值,因此这一过程非常缓慢并且难以实现原子性
14.分片工具
在设计数据分片应用时,首先要做的事情是编写能够查询多个数据源的代码。如果没有任何抽象层,直接让应用访问多个数据源,那绝对是一个很差的设计,因为这会增加大量的编码复杂性。最好的办法是将数据源隐藏在抽象层中,这个抽象层主要完成以下任务:
- 1.连接到正确的分片并执行查询
- 2.分布式一致性校验
- 3.跨分片结果集聚合
- 4.跨分片关联操作
- 5.锁和事务管理
- 6.创建新的数据分片(或者至少在运行时找到新分片)并重新平衡分片(如果有时间实现)
你可能不需要从头开始构建分片结构。有一些工具和系统可以提供一些必要的功能或专门设计用来实现分片架构。
Hibernate Shards 是一个支持分片的数据库抽象层,基于Java语言的开源的Hibernate ORM库扩展,由谷歌提供。它在Hibernate Core接口上提供了分片感知功能,所以应用无须专门为分片设计;事实上,应用甚至无须知道它正在使用分片。Hibernate Shards 通过固定分配策略向分片分配数据。另外一个基于Java的分片系统是HiveDB.
如果使用的是PHP语言,可以使用Justin Swanhart提供的Shard-Query系统,它可以自动分解查询,并发执行,并合并结果集。另外一些有同样用途的商用系统有ScaleBase、ScalArc,以及dbShards.
Sphinx是一个全文检索引擎,虽然不是分片数据存储和检索系统,但对于一些跨分片数据存储的查询依然有用。Sphinx可以并行查询远程系统并聚合结果集。
通过多实例扩展
一个分片较多的架构可能会更有效地利用硬件。研究和经验表名MySQL并不能完全发挥现代硬件的性能。当扩展到超过24个CPU核心时,MySQL的性能开始趋于平缓,不再上升。当内存超过128GB时也同样如此,MySQL甚至不能完全发挥诸如Virident或Fusion-io卡这样的高端PCIe flash设备的IO性能。
不要在一台性能强悍的机器上只运行一个服务器实例,我们还有别的选择。你可以让数据分片足够小,以使每台机器上都能放置多个分片(这也是一直提倡的),每台服务器上运行多个实例,然后划分服务器的硬件资源,将其分配给每个实例。这样做尽管比较繁琐,但确实有效。这是一种向上扩展和向外扩展的组合方案。也可以用其他方法来实现——不一定需要分派你——但分片对于在大型服务器上的联合扩展具有天然的适应性。
一些人倾向于通过虚拟化技术来实现合并扩展,这有它的好处。但虚拟化技术本身有很大的性能损耗。具体损耗多少取决于具体的技术,但通常都比较明显,尤其是IO非常快的时候损坏会非常惊人。另一种选择是运行多个MySQL实例,每个实例监听不同的网络端口,或绑定到不同的IP地址。
我们已经在一台性能强悍的硬件上获得了10倍或15倍的合并系数。你需要平衡管理复杂度代价和更优性能的收益,以决定哪种方法是最优的。这时候网络可能会成为瓶颈——这个问题大多数MySQL用户都不会遇到。可以通过使用多块网卡并进行绑定来解决这个问题。但Linux内核可能会不理想,这取决于内核版本,因为老的内核对每个绑定设备的网络中断只能使用一个CPU。因此不要把太多的连接绑定到很少的虚拟设备上,否则会遇到内核层的网络瓶颈。新的内核在这一方面会有所改善,所以需要检查你的系统版本,以确定该怎么做。
另一个方法是将每个MySQL实例绑定到待定的CPU核心上。这有两点好处:第一,由于MySQL内部的可扩展性限制,当核心数较少时,能够在每个核心上获得更好的性能;第二,当实例在多个核心上运行线程时,由于需要在多核心上同步共享数据,因为会有一些额外的开销。这可以避免硬件本身的可扩展性限制。限制MySQL到少数几个核心能够帮助减少CPU核心之间的交互。注意到反复出现的问题了没?将进程绑定到具有相同物理套接字的和欣赏可以获得最优的效果.