三种常用的数据分片方式:Hash分片,一致性Hash分片和按照数据范围分片
数据分片就是按照一定的规则,将数据集划分成相互独立正交的数据子集。然后将数据子集分布到不同的节点上,通过设计合理的数据分片规则,可将系统中的数据分布在不同的物理数据库中,达到提升应用系统数据处理速度的目的。
因为单一的节点受到机器内存、网卡带宽和单节点请求量的限制,不能承担比较高的并发,因此我们考虑将数据分片,依照分片算法将数据打散到多个不同的节点上,每个节点上存储部分数据。
这样在某个节点故障的情况下,其他节点也可以提供服务,保证了一定的可用性。这就好比不要把鸡蛋放在同一个篮子里,这样一旦一个篮子掉在地上,摔碎了,别的篮子里还有没摔碎的鸡蛋,不至于一个不剩。
一般来讲,分片算法常见的就是 Hash
分片、一致性 Hash
分片和按照范围数据分片三种。我们以缓存为例子
Hash分片
Hash
分片的算法就是对缓存的 Key
做哈希计算,然后对总的缓存节点个数取余。你可以这么理解:
比如说,我们部署了三个缓存节点组成一个缓存的集群,当有新的数据要写入时,我们先对这个缓存的 Key
做比如 crc32
等 Hash
算法生成 Hash
值,然后对 Hash
值模 3,得出的结果就是要存入缓存节点的序号。
这个算法最大的优点就是简单易理解,缺点是当增加或者减少缓存节点时,缓存总的节点个数变化造成计算出来的节点发生变化,从而造成缓存失效不可用。所以我建议你,如果采用这种方法,最好建立在你对于这组缓存命中率下降不敏感,比如下面还有另外一层缓存来兜底的情况下。那有没有更好的方案能解决这个问题呢?那就是一致性 Hash
分片算法。
一致性Hash分片
用一致性 Hash
算法可以很好地解决增加和删减节点时,命中率下降的问题。在这个算法中,我们将整个 Hash
值空间组织成一个虚拟的圆环,然后将缓存节点的 IP
地址或者主机名做 Hash
取值后,放置在这个圆环上。当我们需要确定某一个 Key
需要存取到哪个节点上的时候,先对这个 Key
做同样的 Hash 取值,确定在环上的位置,然后按照顺时针方向在环上“行走”,遇到的第一个缓存节点就是要访问的节点。比方说下面这张图里面,Key 1
和 Key 2
会落入到 Node 1
中,Key 3
、Key 4
会落入到 Node 2
中,Key 5
落入到 Node 3
中,Key 6
落入到 Node 4
中。
这时如果在 Node 1
和 Node 2
之间增加一个 Node 5
,你可以看到原本命中 Node 2
的 Key 3
现在命中到 Node 5
,而其它的 Key
都没有变化;同样的道理,如果我们把 Node 3
从集群中移除,那么只会影响到 Key 5
。所以你看,在增加和删除节点时,只有少量的 Key
会 漂移 到其它节点上,而大部分的 Key
命中的节点还是会保持不变,从而可以保证命中率不会大幅下降。
不过,事物总有两面性。虽然这个算法对命中率的影响比较小,但它还是存在问题:
- 缓存节点在圆环上分布不平均,会造成部分缓存节点的压力较大;
- 当某个节点故障时,这个节点所要承担的所有访问都会被顺移到另一个节点上,会对后面这个节点造成压力。
一致性 Hash 算法的脏数据问题
极端情况下,比如一个有三个节点 A、B、C
承担整体的访问,每个节点的访问量平均,A
故障后,B
将承担双倍的压力(A 和 B 的全部请求),当 B
承担不了流量 Crash
后,C
也将因为要承担原先三倍的流量而 Crash
,这就造成了整体缓存系统的雪崩。
说到这儿,你可能觉得很可怕,但也不要太担心,我们程序员就是要能够创造性地解决各种问题,所以你可以在一致性 Hash
算法中引入虚拟节点的概念。
它将一个缓存节点计算多个 Hash
值分散到圆环的不同位置,这样既实现了数据的平均,而且当某一个节点故障或者退出的时候,它原先承担的 Key
将以更加平均的方式分配到其他节点上,从而避免雪崩的发生。
其次,就是一致性 Hash
算法的脏数据问题。为什么会产生脏数据呢? 比方说,在集群中有两个节点 A
和 B
,客户端初始写入一个 Key
为 k,值为 3
的缓存数据到 Cache A
中。这时如果要更新 k
的值为 4
,但是缓存 A
恰好和客户端连接出现了问题,那这次写入请求会写入到 Cache B
中。接下来缓存 A
和客户端的连接恢复,当客户端要获取 k
的值时,就会获取到存在 Cache A
中的脏数据 3,而不是 Cache B
中的 4
。
所以,在使用一致性 Hash
算法时一定要设置缓存的过期时间,这样当发生漂移时,之前存储的脏数据可能已经过期,就可以减少存在脏数据的几率。
很显然,数据分片最大的优势就是缓解缓存节点的存储和访问压力,但同时它也让缓存的使用更加复杂。在 MultiGet(批量获取)
场景下,单个节点的访问量并没有减少,同时节点数太多会造成缓存访问的 SLA
(即“服务等级协议”,SLA
代表了网站服务可用性)得不到很好的保证,因为根据木桶原则,SLA
取决于最慢、最坏的节点的情况,节点数过多也会增加出问题的概率,因此我推荐 4
到 6
个节点为佳。
按照数据范围分片
常见场景就是按照 时间区间
或 ID区间
来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将 userId
为 1~9999
的记录分到第一个库, 10000~20000
的分到第二个库,以此类推。某种意义上,某些系统中使用的 "冷热数据分离" ,将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。
这样的优点在于:
- 单表大小可控
- 天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移
- 使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。
缺点:
- 热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询
这次的总结也是被迫来源某计算机考试的论文,哭了,又被虐了。单机的资源终究是有限的,所以这些数据分片方式是经常使用的,实际上这些都是分布式系统的基础问题,希望大家也好好学习!