将一个数据存储到一组水平分区或碎片。存储和访问大量数据时,这个模式可以提高可扩展性。
由一个单一的服务器托管的数据存储区可能会受到以下限制:
垂直缩放通过添加更多的磁盘容量,处理能力,内存和网络连接可能会推迟一些这些限制的效果,但它很可能是只是一个临时的解决方案。能够支持大量用户和大量数据的商业云应用程序必须能够扩展几乎无限,所以垂直缩放不一定是最好的解决方案。
划分数据存储到水平分区或碎片。每个碎片都有相同的模式,但保存的数据其独特的子集。甲碎片是在自己的权利(它可以包含许多不同类型的实体的数据)的数据存储器,用作存储节点的服务器上运行。
这种模式具有以下优点:
何时将一个数据存储成碎片,决定哪些数据应该被放置在每个碎片。甲碎片通常包含倒在数据中的一个或多个属性所确定的指定范围内的物品。这些属性形成的分片密钥(有时也被称为分区键)。分片的关键应该是静态的。它不应该根据可能发生变化的数据。
分片身体组织的数据。何时一个应用程序存储和检索数据,该分片的逻辑指示应用到相应的碎片。此拆分逻辑可在应用程序中被实现为数据访问代码的一部分,或者它可以由数据存储系统中实现,如果它透明地支持分片。
抽象的拆分逻辑的数据的物理位置提供了一个高层次的控制在其上的碎片包含的数据,并且使数据分片之间迁移,而再加工的应用程序的业务逻辑应该需要在碎片的数据后面将要引入的(例如,如果碎片变得不平衡)。的折衷是在确定的每个数据项的位置,因为它被检索所需的附加数据存取的开销。
为了确保最佳的性能和可扩展性,分裂的数据的方式,适合的查询类型的应用程序执行是重要的。在许多情况下,这是不可能的拆分计划将完全符合每个查询的要求。例如,在一个多用户系统中的应用,可能需要通过使用承租者ID来检索租户数据,但它也可能需要基于其它属性这个数据查找,如承租人的名称或位置。为了处理这些情况,实行分片策略,即支持最常用的查询执行的一个子库的关键。
如果查询通过使用属性值的组合来定期检索数据,这可能是可以定义通过将属性一起的复合分片键。另外,使用模式,如索引表,以提供快速的查找到未覆盖的碎片关键基础属性数据。
选择所述分片密钥,并决定如何跨碎片分发数据时的三种策略是常用的。注意,并不需要成为碎片和承载它们 - 在单个服务器可以承载多个分块中的服务器之间的一对一的对应关系。这些战略包括:
分片键和物理存储的映射关系可以基于物理分块,每个分片键映射到一个物理分区。可替换地,这种技术提供了重新平衡碎片时更大的灵活性是使用虚拟分区方法,其中分片键映射到虚拟碎片的数量相同,这又映射到较少的物理分区。
在这种方法中,一个应用程序通过使用指的是一个虚拟碎片一个碎片键定位数据,并在系统的虚拟分片透明地映射到物理分区。进行修改,以使用一组不同的碎片的键的虚拟碎片和物理分区可以改变,而不需要对应用程序代码之间的映射。
范围的策略。这在同一个分片相关的项目一起策略组,并把它们的订单通过分片密钥分片键是连续的。它是用于应用程序通过使用范围查询(即返回一组数据项的为落在给定范围内的碎片键查询)经常检索项集有用的。
例如,如果应用程序经常需要找到放置在给定月份所有的订单,该数据可被检索更快如果一个月的所有命令被存储在日期和时间的顺序在同一个分片。如果每个订单被存储在不同的碎片,它们将必须通过进行大量的点查询(返回单个数据项的查询)的单独取出。
在这个例子中,分片键是一个组合键,包括订单月作为最显著元件,其次是为了日和时间。
创建和附加到一个碎片新订单时,订单中的数据自然排序。一些数据存储支持包括识别所述碎片和行密钥唯一地标识该子库中的项分区键元件的两部分分片密钥。数据通常是在碎片中排键顺序举行。该受的范围内的查询和需要的项目组合在一起可以使用一个分片键具有用于分区键但该行键的唯一值相同的值。
哈希策略。这种策略的目的是减少在数据热点的机会。它的目的是分配在实现每个碎片的大小和平均负载,每个碎片会遇到之间的平衡的方式在整个碎片中的数据。分片的逻辑计算,其中基于所述数据的一个或多个属性的散列来存储中的项目的子库。所选择的散列函数应该均匀地分布在整个数据碎片,可能通过引入一些随机元素插入的计算。
了解超过其他分片策略哈希策略的优势,考虑如何依序录取新租户多租户应用程序可能分配租户碎片中的数据存储。当使用范围的策略,租户1到n的数据都将存储在分片 A 中,数据为住户的 n+1 到 m 都将存储在分片 B,依此类推。如果最近登记的租户也是最活跃,最数据活动将发生在少数碎片,这可能会导致热点。与此相反,哈希策略分配租户基于对其租户ID的散列碎片。这意味着顺序租户是最有可能被分配到不同的碎片,如图 3 所示为住户 55 和 56,这将在这些碎片分配负载。
下表列出的主要优点和考虑这三个分片策略。
Lookup 查找
更好地控制碎片的配置和使用方式。
重新平衡数据时,因为新的物理分区可以被添加到拉平工作量使用虚拟碎片减少的影响。可以在不影响使用一个分片键来存储和检索数据的应用程序代码被修改的虚拟碎片和实现该分片的物理分区之间的映射。
Range 范围
易于实现和使用范围查询工作得很好,因为它们通常可以取在单个操作中从单个分片的多个数据项。
更简便的数据管理。例如,如果用户在相同的区域是在相同的子库,更新可以安排在基于本地负载和需求模式的每个时区。
Hash 哈希
一个甚至更多的数据和负荷分布的更好的机会。
请求路由可以直接通过使用哈希函数来实现。没有必要来维护一个地图。
最常见的拆分方案实现上述方法之一,但你也应该考虑你的应用程序的业务需求和他们的数据使用模式。例如,在一个多用户应用:
每个分片策略意味着不同的功能和复杂性管理的规模,向外扩展,数据移动,并保持水平状态。
查找策略允许缩放和数据移动操作来进行,在用户层面,无论是在线还是离线。该技术暂停部分或全部用户活动(也许是在非高峰时段),移动数据到新的虚拟分区或物理碎片,改变映射,无效或刷新持有该数据的缓存,然后让用户活动恢复。通常这种类型的操作可以进行集中管理。查找战略要求的状态是高度可缓存和副本友好。
的范围的策略规定了结垢和数据移动操作,这通常必须进行时的一部分或全部的数据存储为脱机,因为数据必须被分割和整个碎片合并的一些限制。移动的数据,以重新平衡碎片可能无法解决不均匀负荷的问题,如果大多数的活性是对相邻分片密钥或数据标识符是相同的范围之内的。范围的策略可能也需要进行维护,以图范围内的物理分区的一些状态。
哈希策略使得扩展和数据移动操作更为复杂,因为分区键是碎片密钥或数据标识符的哈希值。每个碎片的新位置,必须从散列函数来确定,或者该函数修改,以提供正确的映射。然而,哈希策略不需要维护状态。
在决定如何实现这个模式时,请考虑以下几点:
注意: 在不包括分片键也可能导致问题字段自动递增值。例如,如果您使用自增字段来生成唯一标识,并分布在不同的碎片两个不同的项目可能被分配相同的ID。
注意: 如果在一个子库的实体引用存储在另一个分片的一个实体,包括分片键用于第二实体,作为第一实体的架构的一部分。这可以帮助提高引用跨碎片相关数据查询的性能。
注意: 如果在多个分块的变化保持的基准数据,该系统必须同步所有碎片这些变化。而此同步发生时,系统可能会出现一定程度的混乱。如果你按照这种方法,你应该设计自己的应用程序能够处理这个矛盾。
使用这种模式:
注意: 分片的主要焦点是改进系统的性能和可扩展性,而作为副产物,也可以借助于其中数据被划分成单独的分区的方式提高可用性。在一个分区中的故障不一定阻止应用程序访问的其他分区中保存的数据,并且操作者无需使得整个数据为应用程序无法访问的可以执行的一个或多个分区的维护或复原。欲了解更多信息,请参阅数据分区指导。
下面的示例使用了一组充当碎片的 SQL Server 数据库。每个数据库包含一个应用程序使用的数据的一个子集。应用程序检索该被分布在整个碎片通过使用它自己的分片逻辑(这是一个扇出查询的一个例子)的数据。将位于每个子库中的数据的细节是通过这样的方法称为 GetShards 返回。此方法返回 ShardInformation 对象,其中 ShardInformation 类型包含一个标识符为每个碎片和 SQL Server 的连接字符串,应用程序应该使用连接到碎片的枚举列表(在连接字符串中没有代码示例所示)。
private IEnumerable<ShardInformation> GetShards() { // This retrieves the connection information from a shard store // (commonly a root database). return new[] { new ShardInformation { Id = 1, ConnectionString = ... }, new ShardInformation { Id = 2, ConnectionString = ... } }; }下面的代码显示了如何在应用程序使用 ShardInformation 对象名单进行了从并行每个碎片获取数据的查询。查询的细节没有示出,但在本实施例中所检索的数据包括可以存放信息,如客户的名称,如果碎片包含客户的细节的字符串。该结果由应用聚集成 ConcurrentBag 集合进行处理。
// Retrieve the shards as a ShardInformation[] instance. var shards = GetShards(); var results = new ConcurrentBag<string>(); // Execute the query against each shard in the shard list. // This list would typically be retrieved from configuration // or from a root/master shard store. Parallel.ForEach(shards, shard => { // NOTE: Transient fault handling is not included, // but should be incorporated when used in a real world application. using (var con = new SqlConnection(shard.ConnectionString)) { con.Open(); var cmd = new SqlCommand("SELECT ... FROM ...", con); Trace.TraceInformation("Executing command against shard: {0}", shard.Id); var reader = cmd.ExecuteReader(); // Read the results in to a thread-safe data structure. while (reader.Read()) { results.Add(reader.GetString(0)); } } }); Trace.TraceInformation("Fanout query complete - Record Count: {0}", results.Count);