数据库分片:它是如何工作的?
Krzysztof Ksiazek 【hudson 译】
2016年9月20日

具有大型数据集或高吞吐量应用程序的数据库系统会挑战单个数据库服务器的容量。高查询率会耗尽CPU容量、I/O资源、RAM甚至网络带宽。
水平扩展通常是扩展基础架构的唯一方法。您可以升级到更强大的硬件,但单个主机可以处理的负载是有限制的。您可能能够购买市场上最昂贵、速度最快的CPU或存储,但这可能仍不足以处理您的工作负载。超越单个主机的限制的唯一可行方法是利用多个主机作为集群的一部分一起工作或使用复制技术相互连接。
然而,水平扩展也有其局限性。当涉及到扩展读取时,它非常有效——只需添加一个节点,您就可以利用额外的处理能力。对于写操作,情况完全不同。考虑一个MySQL复制设置。过去,MySQL复制使用单个线程来处理写操作,在多用户、高度并发的环境中,这是一个严重的限制。这种情况最近有所改变。在MySQL 5.6中,可以并行复制多个模式。在MySQL 5.7中,添加了“逻辑时钟”调度程序后,单个模式工作负载就可以从多线程复制的并行化中受益。Galera Cluster for MySQL还允许利用多个工作线程应用写操作集和进行多线程复制。尽管如此,即使有了这些增强,也只是在写吞吐量方面获得一些增量改进, 这并不是问题的解决方案。
一种解决方案是使用某种模式将数据拆分到多个服务器上,并以这种方式将写入拆分到多台MySQL主机上。这是分片。
这个想法实际上很简单——如果我的数据库服务器无法处理写入量,那么让我们以某种方式将数据拆分,并将一部分存储在一个数据库主机上,另一部分存储到另一个主机上,从而生成部分写流量。这样,每个主机只需要处理一半的写操作,这应该在其硬件限制范围内。如果写入工作负载增加,我们可以进一步分割数据并将其分发到更多服务器上。
实际实现更为复杂,因为在实现切分之前需要解决许多问题。您需要回答的第一个非常重要的问题是–您将如何分割数据?
功能性切分
让我们假设您的应用程序是由多个模块或微服务构建而成的,如果我们想时尚的话。假设这是一家大型在线商店,有多个仓库作为后端。此类站点可能包含一个处理仓库物流的模块-检查项目的可用性,跟踪从仓库到客户的发货。另一个模块可能是在线商店——一个展示可用商品的网站。另一个模块是交易模块——收集和存储信用卡,处理交易等。也许网上商店有一个大型的、热闹的论坛,在那里客户可以分享对商品的意见,讨论支持问题等。你可以通过每个模块使用一个单独的数据库来开始你在碎片世界的旅程。这将让你获得一些喘息空间,并计划下一步。另一方面,如果每个切分都能轻松处理其工作负载,那么下一步可能根本不需要。当然,这种设置也有缺点——您无法轻松跨模块(碎片)查询数据——您必须对单独的数据库执行单独的查询,然后将结果集合并在一起。
##基于表达式的分片
跨分片分割数据的另一种方法是使用某种表达式或函数/算法来帮助我们决定数据应该放在哪里。让我们假设您有一个数据库,其中有一个通常被访问和写入的大表。例如,假设一个社交媒体网站,我们最大的表包含有关用户及其活动的数据。该表使用某种类型的id列作为主键–我们需要以某种方式将其拆分,其中一种方法是将表达式应用于id值。一个非常流行的选择是使用模函数——如果我们想生成128个分片,我们只需应用表达式“id%128”,这将计算出存储给定行应该存储的分片号。另一种方法包括利用日期范围,例如,2015年的所有用户活动存储在一个数据库中,2016年的活动存储在另外一个数据库中。另一种方法是根据属性列表分发数据,例如,来自特定国家的所有用户最终都在同一个分片中。
基于元数据的分片
正如我们前面所讨论的,功能性切分和基于表达式的切分在分片分数量方面都有局限性。还有一种方法可以让您更灵活地管理分片,即基于元数据的分片。想法很简单——我们不使用某种硬编码算法,只写下给定行的位置:行id=1–分片1,行id=2–分片5。最后,我们构建一个数据库来保存此元数据。 这种方法有很大的好处——您可以在任何分片中存储任何行。您还可以轻松地将新分片添加到组合中,只需设置它们并开始在其上存储数据。您还可以轻松地在分片之间迁移数据–没有什么可以阻止您在分片之间复制数据,然后对元数据进行调整。实际上,它比听起来更复杂,因为您必须确保移动所有数据,所以需要某种类型的数据锁定。例如,要在分片之间复制数据,您必须跨分片对数据进行初始复制,锁定对迁移数据部分的访问,进行最终同步,最后更改元数据库中的条目并解锁数据。