一致性哈希(Consistent Hashing)

笔者先拿普通哈希和redis集群举个例子。服务端通常会通过计算发送的请求的某个字段的哈希值,来决定这个请求会被转发到哪个redis实例,如图Figure1所示。

一致性哈希(Consistent Hashing)

Figure1

图中,假设我们有三台redis实例,假设这个服务通过计算发送请求的客户端的IP地址,来决定查询哪个redis实例。请求到来时,先计算出hash值,假设计算出的hash值为十,然后模三,得到的值为一,这个请求将会查询编号为2的那个redis实例。然而,此时发生了一个情况,一台新的redis实例被添加进来了,此时,依据原来的方法,计算出hash值为十,模四,得到的值为二,此时请求就会去编号为3的redis实例,当然,这个时候是无法命中缓存的,因为编号为3的redis实例,并没有这条缓存。如果在并发量极高的情况下,出现了这种case,那么后果就是缓存全部无法命中,大量的请求就会直接请求到数据库,然后就是缓存雪崩了。

真实的生产环境中,这种case是肯定不允许出现的,那么如何避免呢?此时使用的便是一致性哈希。

一致性哈希(Consistent Hashing)

Figure2

一致性哈希是一个环,环上的数字是0~2^32-1,也就是无符号整形的范围,计算出来的hash值一定会落在这个范围之内。如图Figure2所示,此处引用一张来自baidu的图片,该圆环沿着顺时针方向,数字增长。用于标识redis实例的值,均匀地分布在这个圆环上。假设对一个请求的某个字段,计算出了其hash值,该值落在了Figure2图中的节点A和节点B之间,那么,该请求就会沿着顺时针方向寻找节点,它找到了节点B,这个节点也就是它所要查询的缓存所在的节点。

那么,一致性哈希是如何避免redis实例数量改变引发缓存失效的呢?

还是Figure2那张图,我们假设,节点D因为某些原因,失效了。那么根据一致性哈希的原理,我们发现,原先落在节点C和节点D之间的请求的缓存,会全部失效,因为它们按照顺时针方向,找到了节点A,而节点A中并没有这部分请求的缓存。此时,我们再来观察一下其它的请求。我们发现,A和B之间的请求仍然会命中B节点,B和C之间的请求仍然会命中C节点,D失效前D和A之间的请求仍然会命中A节点。也就是说,除了C和D之间的请求缓存失效之外,其它部分均为受到任何影响。这种数据结构很好的帮我们解决了普通哈希因为实例数量改变引发的失效问题。采用一致性哈希,会使得系统的容错性和可扩展性变得非常好。

一致性哈希的一个关键点在于,在哈希环上,节点的分布需要均匀。如果节点分布得不均匀,就会发生数据倾斜,如图Figure3所示。

一致性哈希(Consistent Hashing)

Figure3

如果一致性哈希环上节点分布严重不均匀,假设某个承载了大量缓存的节点失效了,那么同样会导致很严重的后果。因此一致性哈希,也需要合理的使用方式,才能发挥它的作用。


分享到:


相關文章: