场景
在我们的系统中不可避免要进行数据的存储,比如系统中会有用户上传的大量图片,这些图片数据要存放在文件服务器磁盘上,而一台服务器的存储空间往往是有限的,那么当图片量超过服务器磁盘的时候,一般都需要进行扩容,使用很多台服务器来存放我们的图片数据。
而这时我们会面临着两个问题:
- 如何确定将一张图片存放在哪一台服务器上,以及想要查看某一张图片时应该去哪个服务器找;
- 如何保证在某一台服务器崩溃或磁盘损坏时,不让图片数据丢失。
这两个问题是在设计一个可升缩系统时,需要重点考虑的两个核心问题,第一个问题本质是数据分区,第二个是数据复制。
数据分区和复制策略是分布式系统的核心。精心设计的数据分区和复制方案可以提高系统的性能、可用性和可靠性,还定义了系统的扩展和管理效率。
数据分区
首先我们需要考虑的是如何确定该将图片存放在哪个服务器节点上。
普通hash算法
一种简单的方法就是通过HASH算法,将图片ID转换为一个数字,然后,对这个数字和服务器总数进行取模运算,找到对应的服务器。例如:
上图中描述的方案解决了如何确定存储/检索服务器的问题,但是存在一个很严重的问题。
随着图片数据量的不断增大,肯定会需要增加新的服务器节点,或者某台服务器如果发生故障或损坏,还会面临删除服务器节点的情况,这会导致服务器总数被改变。那么我们所有图片的映射将会被破坏,导致无法查询到对应的图片。必须重新对所有图片和图片名称信息进行映射,并且需要按照新的服务器数量移动图片数据,非常复杂,这种方式肯定不是我们真正想要的。
一致性hash算法
我们可以使用一致性哈希算法来解决上面的问题。
一致性hash算法可以用于数据和服务器节点,并确保在添加或删除服务器节点时只移动一小部分数据。
一致性hash算法在逻辑上将分布式系统中数据存储在一个环中。并给环中的每个服务器节点分配一个数据范围。下面是一致性hash环的一个示例:
一致性hash算法将环划分为较小的预定义范围,每个范围都会分配给一个服务器节点。范围的开始称之为token,也就是给每个节点分配一个token。分配给每个节点的范围计算如下:
范围开始值:token值
范围结束值:下一个token值-1
以下是上图中描述的四个服务器节点的token和数据范围:
每当系统需要读取或保存文件(数据)时,它执行的第一步是使用hash算法计算出文件名称(ID)的hash值;
根据hash值确定文件位于哪个范围内,确定文件应该存储在哪个节点上。
在这种一致性hash算法情况下,当需要向一致性hash环中添加或者移除服务器节点时,则可以很好的运行,因为只会有当前要增加或移除节点的下一个节点会受到影响。
例如,当一个节点被删除时,由下一个节点负责存储被删除节点上的所有数据。
但是,这种方案还存在一个问题,就是可能会导致数据和负载分布的不均匀,比如有大量的数据hash值都分布在1-25区间,那么Server 1会存储大量的数据,并承担绝大多数的请求负载。
比如在下面的例子中,所有数据的hash值最后都落在(76-100)范围内,那么在Server 4上会保存所有的数据,而server 1,2,3上一条数据都没有。
这个问题在一致性hash算法中通过虚拟节点来解决。
虚拟节点
在上面的一致性hash方案中,为每个服务器节点分配单个Token,也就是给一个服务器节点分配一个相对较大的数据范围。这是一种静态范围划分,需要根据给定的节点数量计算出对应的数据范围。
在这个方案中添加或者删除节点时,如果我们希望各个服务器节点能够保持数据平衡和负载均衡,需要做大量的数据移动的工作。主要存在以下问题:
- 增加或删除节点:将导致重新计算数据范围,给集群带来巨大的管理开销;
- 热点数据问题:由于每个节点都被分配了一个很大的范围,如果数据分布不均匀,一些节点可能会成为热点节点。
为了处理这些问题,一致性hash算法引入了虚拟节点,将原本较大的数据范围划分为很多个较小的数据范围,然后将多个数据范围分发给不同的服务器节点,这些较小的数据范围被称为虚拟节点,对于每一个物理服务器节点来说,不再是只管理一个数据范围的数据,而是管理很多个小数据范围。
并且虚拟节点在集群中会随机分布,尽量的不将连续的数据范围放在一个物理服务器节点,这样能尽量避免连续范围内的热点数据都保存在某一个节点上。
此外,每个节点还会保存其他节点的副本,以实现分区容错。
并且由于群集中的服务器可能存在性能上的差异,因此某些服务器可能比其他服务器包含更多的虚拟节点。
下图显示了物服务器节点A、B、C、D、E如何使用一致性hash算法的虚拟节点,每个物理节点都分配了一组虚拟节点,并且每个虚拟节点都被复制一次存放在其他节点上。
虚拟节点的优势
一致性hash算法的虚拟节点具备很多优势。
- 由于虚拟节点将哈希范围划分为更小的子范围,可以帮助集群上的物理节点更均匀地分配负载,因此在添加或删除节点时能更快的重新平衡;
- 当添加新的服务器节点时,它会从现有的节点转移一些虚拟节点过来,以维护集群的平衡;
- 同样,当一个节点需要重建时,许多节点都会参与重建过程,而不是从固定数量的副本中获取数据。使用虚拟节点在服务器性能存在差异化时更容易管理;
- 我们可以将大量的虚拟节点分配给功能强大的服务器,而将数量较少的虚拟节点分配给功能较弱的服务器;
- 由于虚拟节点帮助为每个物理节点分配很多较小的范围,这也降低了出现热点问题的可能性。
数据复制
通常为了保证分布式系统的高可用性和持久性,我们需要将一个节点的数据冗余复制到多个其他节点,防止当前节点出现异常崩溃不可用。
一致性哈希算法会将每个数据项复制到系统中固定数量的节点上,假设需要冗余备份3份数据。
每个数据在计算出hash值后,会被分配给一个协调节点,这个协调节点首先在本地存储数据,然后将数据复制到顺时针的2个后续节点上。
这样每个节点都会拥有其与其第3个前导节点之间的环上的区域的数据。
总结
一致性hash算法有助于有效地进行数据分区和数据复制,任何需要扩展或希望通过数据复制实现高可用性的分布式系统都可以利用一致性hash算法。
比如,需要根据流量负载添加或删除缓存服务器来动态调整其缓存使用情况的分布式系统;或者使用一些列存储服务器来存储文件的系统都需要根据使用情况进行扩容缩容等。
作者:小黑说Java
链接:
https://juejin.cn/post/7043680437046575140
</article>