在 Cassandra 和 Dynamo DB 等分布式数据库中释放高效数据分区的威力
导言
我们生活在一个每天都会产生大量数据的世界。在大型企业中,几乎不可能将所有数据都存储在一台服务器上。这就是我们需要横向扩展的原因,在横向扩展中,每个数据部分都存储在单独的服务器上。
纵向扩展时,我们可以简单地将所有数据存储在一个地方,与此相反,横向扩展时,关键是要以能够快速访问不同服务器上的数据的方式组织存储。在了解了传统系统在性能方面的缺点后,我们将设计一个弹性系统来缓解上述问题。
在系统设计中,我们将采用一致性散列原则。
问题
假设我们有 n 个数据对象,需要存储在 k 台不同的服务器上。服务器的配置会随着时间的推移而改变:
- 任何服务器都可能关闭;
- 系统中可能会添加新的服务器。
考虑到这些潜在的配置变化,我们必须设计一个系统,在配置变化的情况下,能够快速检索所需的数据块,并在服务器之间传输数据。
简单实施
最简单的实现方式包括根据哈希函数在不同服务器之间分配数据。例如,当我们需要在系统中添加一个新数据块时,我们将其密钥插入哈希函数,该函数将输出该数据块所属的服务器编号。
![](https://i-blog.csdnimg.cn/blog_migrate/8b91a25723774c1ff3082327a80c0977.png)
当我们需要从给定的密钥中检索信息时,我们会计算它的哈希值,以找出与该密钥相关的信息存储在哪台服务器上。在实施这种系统时,重要的是要确保哈希函数均匀地分配数据,使每个服务器上存储的数据量大致相同。
这个系统运行良好,直到我们对其进行修改。例如,假设上面的例子中,服务器 S3 关闭了:我们无法再访问它的数据,也不会添加将散列到其桶中的新数据。
![](https://i-blog.csdnimg.cn/blog_migrate/d971bb0195cd7de31c4cefed7a0b81bc.png)
唯一可能的解决办法是将所有数据块重新分配到服务器上。由于我们现在有 k-1 台服务器,我们不应忘记哈希函数的余数必须减 1。
![](https://i-blog.csdnimg.cn/blog_migrate/85b77834bf49bdc0c767e094fa79a8bc.png)
遗憾的是,数据重新分配是一项耗费资源的操作。在数据量大、配置变化频繁的情况下,这种存储系统会变得非常低效。
一致性散列
一致性散列是上述系统的一个很好的替代方案,在任何配置更改的情况下都有更强的恢复能力。
一致性散列不仅包括数据散列,还包括服务器散列。数据密钥和服务器散列到同一组值 [0, n]。为了便于理解和可视化,让我们假设所有哈希值都位于一个环(或时钟)上。每个服务器都有自己的哈希值范围。
一个服务器的哈希范围被定义为哈希环上所有哈希值的间隔,该间隔位于该服务器哈希值之前,位于逆时针方向上另一个最近服务器的哈希值之后。
要确定某个密钥属于哪台服务器,我们需要从密钥的哈希值开始顺时针旋转,直到找到其中一台服务器对应的哈希值为止。该服务器将存储该密钥的数据。
![](https://i-blog.csdnimg.cn/blog_migrate/94cb7459049f788fd4f3675810e80459.png)
服务器 S1 的哈希范围用蓝色表示
服务器的哈希值应按升序存储在其他地方,以便快速访问。使用二进制搜索,可以在 O(logS)时间(S 为服务器数量)内找到存储给定密钥的服务器。
![](https://i-blog.csdnimg.cn/blog_migrate/e40bdad8f969d9872500d420c9406540.png)
与给定密钥相关的服务器编号,其中 S 是服务器的总数
关闭服务器
如果有服务器关闭,我们只需删除服务器的相关哈希值,然后按顺时针方向将数据从该服务器转移到下一个服务器即可。这是一致性散列与简单散列相比的一大优势,因为我们不再需要像以前那样重新分配所有数据。
![](https://i-blog.csdnimg.cn/blog_migrate/fd0ac26dfb27871005256d7e6d73b005.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c37db55748391bfcad3b314659ec075e.png)
添加新服务器
如果需要在系统中添加新的服务器,那么我们只需要按逆时针方向传输新服务器的哈希值与最近服务器的哈希值之间的所有哈希值相关数据。
![](https://i-blog.csdnimg.cn/blog_migrate/9f179220477b2172bec9ef6b4c6d38b2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/930b09a3b2fc9a30e970e809a71fd7b2.png)
分布不均
虽然一致散列似乎能抵御各种配置变化,但也有可能出现数据在服务器之间分布不均的情况。
- 首先,这种情况可能是由于选择的散列函数造成的。实际上,我们无法保证它能均匀地为数据生成密钥。因此,这可能会导致服务器的散列范围长度极不相称的情况。
- 即使数据在某一特定时刻是均匀分布的,随着各种配置的变化,数据也会很快发生急剧变化,再次变得不均匀。
分布越不均匀,平均响应时间就会相应延长。
缓解这一问题的可行方法之一是,当分布变得不均匀时,定期重新分配系统中的所有数据(可能使用另一个哈希函数)。虽然有时这可能是一种解决方案,但当数据对象达到数百万或数十亿时,这种方法仍然不是最佳选择。
虚拟节点
虚拟节点是散列组成的一种扩展,它使系统对不均匀的数据分布更有弹性。这一想法包括对每个服务器进行多次散列(使用不同的散列函数)。每个服务器的总散列范围被定义为与其所有密钥相关的散列范围的总和。
![](https://i-blog.csdnimg.cn/blog_migrate/ec4e8b3e0c765bd788868b9d59deb9b4.png)
- 关闭服务器意味着删除与该服务器相关的所有虚拟节点。该服务器上的所有数据都将转移到其他多个服务器上。
- 在添加新服务器时,其虚拟节点的所有哈希值都应通过之前用于其他服务器的哈希函数来计算。
在现实中,虚拟节点的数量通常比上面的例子多得多。
一方面,随着虚拟节点数量的增加,散列范围平均变得更加一致。另一方面,执行与配置变更相关的标准操作需要更多时间。此外,还需要存储有关虚拟节点的额外元数据。
在大多数情况下,最好根据特定问题、可用服务器数量和数据量来选择虚拟节点的数量。如果难以估计一个好的数量,建议调整该参数,以找到完美的权衡。
应用
一致性散列有广泛的应用。大多数情况下,它用于分布式应用,尤其是在许多服务器上存储大量数据的数据库中。最常见的例子有
- Apache Cassandra - 分布式 NoSQL 列数据库
- Amazon Dynamo DB - 分布式 NoSQL 键值数据库
- Discord - 视频和聊天应用程序
结论
随着分布式系统的兴起,一致散列开始迅速普及。它能适应频繁的配置更改,为在不同集群间分割数据提供了一种简单而有效的解决方案。同时,虚拟数的数量也是一个重要参数,它能让一致散列更好地适应大多数系统设置。
资源
除非另有说明,否则所有图片均由原作者提供。