在我们进入这个问题之前,有必要了解为什么我们要对数据存储进行分片,以及在开始分片之前你拥有的各种选项。
数据库分片信息图
当表达到特定大小时,人们通常会觉得分片是解决所有扩展问题的神奇方法。然而,我有数十亿行的表,并且没有看到一个令人信服的理由进行分片,因为我的使用模式很适合单个表并且没有看到任何强有力的理由(除了管理这么大的表,它在某些情况下是足够的理由)对表进行分片。
一、什么是数据库分片
简单地说,分片是一种跨多台机器分布数据的方法。当没有一台机器可以处理预期的工作负载时,分片就特别有必要。
分片是水平扩展的一个例子,而垂直扩展是一个越来越大的机器来支持新工作负载的例子。
水平扩展
工程师们经常陷入复杂的做事方式,但随着程序的发展,尽早保持简单会使以后面对具有挑战性的事情时变得容易得多。因此,如果通过获得具有更多资源的机器来解决你的问题,这是正确的答案。
我们已经讨论了潜在的服务器架构,现在我们谈谈数据布局。
你也可以通过几种方式对数据进行分区并将特定表移动到它们自己的数据库中,这与你在微服务架构中看到的非常相似,程序的特定部分有其自身数据库服务器。程序知道每个数据库该在哪里寻找。或者,你可以跨多个数据库节点存储同一个表的行,这带来了分片键等想法;稍后会详细介绍。
分区策略
更现代的数据库,如 Cassandra 和其他数据库,将分区方法从程序逻辑中抽象出来,并在数据库级别进行维护。
二、分片前我有哪些选择
与任何分布式架构一样,数据库分片也需要花钱。设置分片、使每个分片上的数据保持最新以及确保将请求发送到正确的分片既费时又复杂。在开始分片之前,你可能想看看这些其他选项之一是否适合你。
我被问过无数次,在没有任何明显的瓶颈或限制因素,比如用完了可以支持工作负载的硬件,分片是否是一个好主意。我的答案是如果程序仍正常,请不要修理它。
在获得具有更多资源的机器、添加额外的 RAM、为计算繁重的工作负载添加更多 CPU 内核以及添加额外的存储之后,我们也可以避开分片。这些都是不需要重新设计程序和数据库架构的选项。其他最终限制,例如带宽(网络或系统内部),也会迫使你进行分片。
垂直扩展
如果你对数据所做的大部分工作是读取,复制可以提高数据的可用性并加快读取数据的速度。这可以帮助你避免数据库分片的一些复杂性。可以通过制作更多数据库副本来提高读取性能。当然,假设你已经补充了缓存。这可以通过负载均衡或根据他们在世界上的位置路由查询来完成。
但是复制使得频繁写入的工作负载更难处理,因为每次写入都必须复制到每个节点。这可能因数据存储而异,其中一些是异步执行的,而另一些可能会延迟初始写入以确保其被复制。
WAL
WAL(预写日志)是磁盘上的一种额外结构,只能添加到其中。在将更改写入数据库之前,它们首先写入日志,此日志必须在持久存储上。它用于从崩溃和丢失的事务中恢复。此日志还用于支持某些数据库(如 PostgreSQL 和 MySQL)中的复制。
扩展读取
性能不佳是因为数据库需要对其服务的工作负载进行更好地设计。例如,将搜索数据存储在关系数据存储中可能意义不大。将类似的东西移到 Elasticsearch 会更有效。将 blob 移动到像 S3 这样的对象存储可能是一个相当大的胜利,而不是将它们存储在你的关系存储中。外包此功能可能比尝试对整个数据库进行分片更有意义。
如果你的程序数据库管理大量数据,需要大量读写,和 / 或需要始终可用,则分片数据库可能是最佳选择。让我们来看看分片的优缺点。
三、必要时分片
分片可以在增加系统吞吐量、存储容量和可用性方面为你提供几乎无限的可扩展性。有许多更小、更容易控制的系统,每个系统都可以通过各自的副本自行扩展和缩减。
所有这些优势都要在操作复杂性、程序开销和支持这种新设计的基础设施成本上进行权衡。
它是如何工作的?
在我们可以分片数据库之前,我们需要回答几个重要问题。你的计划将取决于你如何回答这些问题。
我们如何跨分片分布数据?如果数据分布不均,是否存在潜在热点(hotspots)?
我们运行哪些查询操作,以及表如何交互?
数据将如何增长?以后需要如何重新分配?
术语热点是指节点的工作负载已超过特定资源(内存、io 等)的阈值。有一个有趣的例子叫做 Bieber Bug。
在我们进入下一点之前,理解以下术语很重要。
Shard Key(分片键)是主键的一部分,它告诉我们数据应该如何分布。使用分片键,你可以通过将操作路由到正确的数据库来快速查找和更改数据。
同一节点包含具有相同分片键的条目。共享相同分片键的一组数据称为逻辑分片。一个数据库节点包含多个逻辑分片,也称为物理分片。
分离逻辑分片
最关键的推定也是未来最难改变的推定。逻辑分片只能跨越一个节点,因为它是一个原子存储单元。在分片对于单个节点来说太大的情况下,数据库集群实际上空间不足。
基于键的分片
算法分片数据库使用哈希函数来定位数据。这允许我们给定一个特定的分片键来找到正确的物理分片来请求数据。
数据仅通过散列函数进行分发。它不考虑有效负载的大小或空间使用情况。当没有合适的分区键时,散列的好处是允许更均匀的分布,并且如果你有合适的分区键,则可以即时计算位置。
基于键的分片
如果你对确定性哈希函数感兴趣,请查看我关于该主题的 Redis 帖子:
https://architecturenotes.co/redis/
这种分片策略的缺点是重新分片数据可能很困难,并且在可用的同时保持一致性更难。
四、基于范围的分片
在基于范围的分片中,根据某个值的范围将数据分成块。
基于范围的分片
这需要一个查找表来查看数据应该存储在哪里。保持此类表格的一致性并在此处选择范围至关重要。
在为这种分片类型选择分片键时,必须选择一个具有高基数的分片键,因此该键的可能值的数量很多。例如,可能值为 North、South、East 和 West 的键的基数较低,因为只有 4 个。此外,理想情况下,你希望在该基数内有良好的分布。
均匀/不均匀分布
如果一切都落在 50% 的可能值中,你的一些分片将开始出现热点。使用准确的数据将其作为实验运行很容易,只需几行代码即可完成。首先,选择一个键和范围并检查潜在分布。
五、基于关系的分片
这种共享机制将相关数据保存在一个物理分片上。例如,相关数据通常分布在关系数据库中的多个表中。
例如,对于像 Instagram 这样的程序,用户和所有相关数据将被分片到同一个物理节点,其中分别包含帖子和评论。通过将相关实体放在同一个分区中,你可以从单个分区中获得更多收益。因此,在整个物理分片和更少的跨物理分片查询中保持更强的一致性。
最后,我想总结一些复杂性的细节,当你需要进行跨多个分片的事务时,这些复杂性可能会引入。无论你计划多少,足够长寿命的服务或程序最终都会遇到一些跨分片事务。
这实质上意味着你需要由 ACID 兼容数据库提供的事务保证,但跨分片时数据库不能确保这种合规性,因为你操作的数据不在启动它的事务范围内。
这通常被称为全局事务,其中多个子事务需要协调并成功。作为一般规则,事务打开的时间越长,争用就越多,并且可能会发生故障。
两阶段提交在理论上很简单,但在实践中很难执行。
领导者写入一个持久的事务记录,指示跨分片事务。
参与者写下他们愿意承诺的永久记录并通知领导者。
领导者在收到所有响应后通过更新持久事务记录来提交事务。(如果没有人响应,它可以中止事务。)
参与者可以在领导者宣布提交决定后显示新状态。(如果领导者中止事务,他们会删除暂存状态。)
协议路径中的读写放大是一个主要问题。写入放大的发生是因为你必须写入事务记录并持久地进行提交,这需要每个参与者至少进行一次写入。过多的写入可能会导致锁争用和程序不稳定。数据库必须额外过滤每次读取,以确保它看不到任何依赖于挂起的跨分片事务的状态,这会影响系统中的所有读取,甚至是非事务性读取。
结论
我们已经讨论了分片、何时使用它以及如何设置它。对于需要管理大量数据并使其随时可用于大量读取和写入的程序,分片是一种出色的解决方案。尽管如此,它还是使操作变得更加复杂。在开始实施之前,你应该考虑收益是否值得付出代价,或者是否有更直接的解决方案。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721