Hash 的一些经典应用场景

1,164 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 5 天,点击查看活动详情

1 前言

在前文中已经讲述了 hash 冲突是怎么回事儿,在本文中将继续讲解 hash 的一些知识,讲述 hash 的一些经典使用场景。 hash 取模分片、一致性 hash、 hash槽都会在本文中进行讲述。

2 hash 取模分片

Hash 取模分片是为了将请求流量、数据分散开来,分散数据热点,并且能够快速的识别数据所在的分片,优点很明显,均衡了数据和请求,但是对于数据存储来说,在范围查询方面效率比较低,采用 hash 取模分片会带来扩容的不变,取模强依赖分片数。在扩容或者缩容时,需要进行数据的迁移操作。

# key 为分片键, count 为节点数, cnt 为处理的节点
int cnt = hash(key) % count

# 在实际操作中,一般 count 为 2的幂次方-1,这样可以通过 & 运算进行处理,运算结果和取余的结果时一致的,但是对于计算机来说加快了运算速度。
int cnt = hash(key) & count

3 一致性hash

使用 hash 算法时,当节点数发生变化时,会导致部分请求无法正确处理,一致性 hash 可以解决由于系统伸缩带来的问题,在新增或者删除节点的情况下,可以尽可能多的命中其他的节点。一致性hash是一个0-2^32的闭合圆,机器的节点按照顺序排列在其hash 环上,通过 hash(key) 按照顺时针来寻找服务的节点进行处理,当节点增加或者删除时,原来的请求或者数据会顺时针转移到下一个节点上。一致性hash环在服务节点数少的时候,宕机、增加、删除节点时会导致数据倾斜的问题,那么怎么处理这样的问题呢?

那就需要增加虚拟节点来处理,这样就可以均匀地分配访问,不至于出现数据倾斜的问题。

4 hash slot 槽

虽然一致性hash 是一个很好的方案,但是在redis cluster 中并没有采用这样的实现,而是采用了 hash slot 来实现,在一个 redis 集群中,会包含 16384(0-16383)个哈希槽, 集群中的每个键都属于 hash 槽中的一个,每个物理节点都会按照顺序分配一定范围的槽位,当新增节点时,需要将原来节点的槽位分配给新的节点,当节点删除或者宕机时,会将该节点的槽位分配到其他的节点上。

# 槽位的分配是使用 crc16 循环冗余算法对 16384 取余
slot=CRC16(key)/16384

用 Hash Slot 的 16384(2^14),而不使用一致性hash的2^16 CRC16(key) mod 16384 效果已经不错,虽然没有一致性hash 灵活,但是实现起来简单,节点增删也相对方便。

redis 的一个节点心跳信息需要携带节点的所有配置信息,16384个槽的数量花费的内存空间为 2k, 这是使用了 bitmap 进行了数据压缩,如果是 65535 则需要 8k,大多数都认为 2k 和 8k 差不太多,但是当节点多的时候,差距就非常大了,Redis 的心跳信息压力就增加了,在实际中redis 的节点不可能超过1000个, 所以使用 16384 是合适的。 CRC16 会输出一个 16bit 的结果,可以看成是分布在0-65535 之间的一个数,这个数对 2^14 次方取模分布也是很均匀,所以16384 是合适的。

5 总结

在本文中,主要是从理论讲解 hash 的一些经典应用场景,hash 取模到一致性hash 算法,再到 hash 槽的算法,主要是在以后的工作中方便理解其基本使用方式,在面对分布式等复杂场景时会有用武之地。