数据库sharding和一致性哈希算法

时间:2021-05-08 23:39:09

数据库 sharding

分布式数据库的核心算法就是sharding,把一个数据库通过sharding算法映射到不同的机器上处理。
sharding 分为两种方式:

纵向切分:就是把一个表按不同列进行切分。比如我们有一个 User 表,那么可以按照不同的列拆分成 User Profile,User BaseInfo 等表,存在不同的机器上。说的通俗点就是拆表。

横向切分:就是按照行进行切分,比如我们有四台机器,那么每一行数据都通过一个 sharding 算法 映射到其中的一台机器上。

一般来讲,如果表中有很多列,压力分布在多个列中,可以通过 纵向切分的方式进行 sharding。但是如果表中本来就只有一列数据承载了巨大压力,或者因为某些原因无法拆表,那么就无法通过纵向切分,只能进行横向切分。

纵向切分比较简单,但是可能会影响业务代码(data entity的实现要变),因为实际存储的表变了,横向切分会相对复杂一些,不过对业务代码是透明的。两种方式可以结合使用。

下面重点讲一下横向切分,也就是一致性哈希算法。

一致性哈希算法

我们假设现在有2台机器,我们需要把User表横向切分映射到这2台机器上。

一个简单的想法就是 userid % 2,余数可能是 0, 1 分别是2台机器的编号。

但是这样做存在一个巨大的问题,就是如果加一台机器怎么办?我们的算法肯定要有扩展性,能*加减机器。假设我们现在加了一台机器,那么现在有3台,于是我们的sharding变成了 userid % 3
这下可麻烦了,因为很多的数据这样计算完了之后 余数都变了,于是我们要把大量的数据进行迁移,而且这个迁移必须停机做,在迁移完成之前系统是不可用的。

这种哈希算法就叫不一致性哈希算法,那么显然这种方式是不可取的。

那么我们改进一下,这次我们取一个比较大的数,比如 500,然后 userid % 500 来计算一个值,因为我们只有2台机器,所以显然这个余数不是机器编号,而是我们把 0 ~ 499 分配给这2台机器。

这里我们做一个简单的均匀分配:

  • 0 号机器: 0~249
  • 1 号机器: 250~499

现在我们加一台机器的时候,编号为2,那么我们把2号机器插入到现有的两台机器之间,于是变成了这样:

  • 0 号机器: 0~160
  • 1 号机器: 161~320
  • 2 号机器: 320~499

图示如下
数据库sharding和一致性哈希算法
可以看到现在全部机器的压力都降低了。

这么做的缺点是什么呢?

  • 依然需要做一部分的数据迁移
  • 每加入一台只能降低邻居两台机器的压力,这样很容易导致压力分布不均局。比如如果有5台机器,增加一台之后依然有三台机器压力不变。

如何解决这个问题呢?

我们把底数再扩大,这次直接扩大到 2^64 - 1,但是实现的方式和上面的就不一样了,这次我们不是把这么多的空间平均分配,而是引入了一个 Virtual Node 的概念。现在我们把 0 ~ 2^64 - 1 这么大的空间看做一个环。
我们引入一个虚拟节点的概念,假设我们每一台机器有1000个虚拟节点,我们在这个环上随机选择1000个点作为这个机器的虚拟节点。初识时候我们有两台机器,那么这个环上有2000个点对应在这两台机器上。

然后问题来了,剩下的绝大部分没有映射到机器上的点怎么办?我们规定,对一个key,通过hash算法计算出一个 2^64 以内的数,然后就可以对应到环上的一个点,然后顺时针找到第一个虚拟节点,这个虚拟节点对应的机器就是存储这个数据的机器。

这样我们加一个机器的时候就再随机选择1000个虚拟节点。然后这些节点分别逆时针找到第一个虚拟节点,并把它的数据的一半迁移到自己的机器上。
数据库sharding和一致性哈希算法
这种虚拟节点的做法,实际就是通过统计学的方式来保证了每次增加一台机器都可以比较均匀地降低其他所有机器的负载。这样我们可以解决前一种做法的负载不均匀的问题。但是,数据依然是需要迁移的,这是做sharding无法避免的事情(不然新的机器没数据有毛用)。