Redis Cluster and Consistent Hashing

同步发表于:http://blog.lanjingdejia.com/articles/2018/08/05/1533440489704.html

Redis有没有使用一致性哈希?

很多人包括之前的我在内,一直认为reids集群的数据存储用的是一致性hash算法,后来读了亚马逊的《Dynamo: Amazon’s Highly Available Key-value Store》论文,感觉Redis不至于实现的这么复杂,带着一些疑问,翻了redis官网和客户端代码,发现Redis并没有使用一致性hash算法。
Redis集群使用的是一种哈希槽(hash slot)的方式,将key通过hash后存储在16384个槽中,然后将这些槽平均分配给集群中的各个节点,举个例子,比如一个集群有4个节点,那么分配可以是这样的:

0~5000  NodeA
5001~10000 NodeB
10001 ~ 15000 NodeC
15001 ~ 16384 NodeD

这样当集群中节点的数量发生变化时,只需要调整槽的分配就可以了:
当有节点加入时,每个节点把自己的槽分一些给新节点。
当有节点退出时,将退出节点的槽分配给其他节点。
仔细想想,其实这定没有违背一致性hash定义中当有一个节点发生变化时,只影响K/N个key的缓存这个定义。
anyway,不管一致性hash也好,hash slot也好,不要执着于Redis用没用一致性hash,搞清楚每一种解决问题的算法的思想才是最重要的。

一致性hash的局限和改进:cache or storage

如果你只是用来做缓存,那自然没问题,一致性hash天生就是来做这个的。当hash环中添加或者移除节点时,平均只有K/N个key受到影响,因为是缓存系统,缓存无法命中时,再从数据源重新加载一遍就是了。
可是,如果我们要做的不仅仅是一个缓存系统呢?如果我们要做的是一个存储系统呢?K/N个key受到影响时,你本身就是数据源了,你还能从哪里加载数据呢?那大概直接就是事故了。
为了解决一致性hash的存储问题,方法大概有两种:

1. hash环上的replication

正如Dynamo论文图中所示,AB区间内的key,不再只由节点B存储,而是扩展到三个节点,B、C、D都存储,这样当B节点故障时,AB之间的key仍然可以从C、D节点中得到,然后把A~B区间的key再复制到E节点中一份,保证每份数据都有两个replication,所以说,大多数事情都是殊途同归的,为了解决节点故障导致的可用性问题,不管怎样,RAID也好,ZK也好,GFS也好,Mysql主从复制也好,说到底都是通过数据的冗余备份(replication)来做的,一个节点可能故障,那我就多弄几个候补的,如果遇到极端情况,所有候补节点同时挂了,那算我倒霉,我认了。毕竟再厉害的容灾,你能跨机器、跨机架、跨机房、跨城市、跨国家,可是你能夸星球吗?来个小行星撞地球,啥都没意义了,所有的东西都有个限度,都需要权衡和考虑性价比的
整个论文读完,发现Dynamo这种把replication做到hash环上的方法虽然解决了可用性的问题,考虑到虚拟节点的存在,这种方法其实是大大增加了系统的复杂度的。

2. 另一种replication

这种replication比上面那种要清晰不少,这种也是redis采用的:
db7e7ca083724df798587ecc2ea68bbf-image.png

如上图所示,hash环(redis使用的是hash槽)上的节点都当做是master节点,每个master节点配备若干个slave节点,当master节点故障时,其中一个slave节点会被选举为新的master节点,选举的过程是由Redis的sentinel组件(你可以认为跟zookeeper做的事情差不多)实现的。虽然多了一个额外的sentinel组件,但是整个结构上清晰了许多。而且,Redis集群采用hash槽的概念替换了一致性hash的虚拟节点,hash槽的分配可以保证每个节点的负载是均衡的。
无论哪种实现方式,slave节点都是可以提供读服务的,也就是读写分离。其实,在redis的适用场景中,大部分都是读多写少的。

Redis集群中节点变动的三种情况

1. 向集群中添加一个节点

当然这个节点还附带着它的slave节点,这里暂且略过,向集群中添加节点时,先把其他节点“管辖”的槽位都拉一部分过来到自己的节点上,使各个节点的负载接近平衡,然后更新集群上所有节点中记录的各个节点和slot的映射关系,新的请求进来后,刚才加入的节点就可以正常工作了。

2. 手动从集群中去除一个节点

当你觉得集群的利用率比较低,向去除一个节点时,redis会先把要去除的节点管辖的slot平均分配到其他节点上,分完之后,更新集群中各个节点中存储的节点和slot的映射关系,最后删除这个节点及其slave节点就可以了。

3. 集群中的一个节点因为故障宕机了

对于这种突发情况,是没有时间让我们预先将节点的slot转移到其他节点的,这种情况就靠我们上面提到的master-slave机制应对了,master挂了,slave会顶替它成为新的master,并不影响整个集群的可用性。当然,如果一个节点的master和它所有的slave同时挂了,那就只能自认倒霉了。

两种路由方式

现在思考一下,你用客户端向集群发起请求,get/set一个key的时候,整个流程是怎么样运转的呢?你是怎样找到应该存放这个key的slot的呢?又是怎样找到“管辖”这个slot的节点的呢?
下面以get为例,描述这个步骤:
 1. 将key % 16384找到slot的编号
 2. 找到管辖这个slot的节点
  这一步有两种实现方式:
  a. 客户端实现
  你的redis cluster client会缓存集群中各个节点与slot的映射关系,当因为节点变动引起映射关系的变动时,client会在发现后及时获取新的映射关系。这样客户端帮你找到“管辖”这个slot的节点后,直接向这个节点发起get请求,节点将结果返回,流程就结束了。
  b. 服务端实现
  你的redis cluster client并不会缓存任何节点和slot的映射关系,而是直接将请求发送到随便一个节点,因为每个节点中都存储着各个节点和slot的映射关系,那么接收到请求的节点会查询该slot的管辖节点是谁,如果不是自己的话,该节点会将请求转发到正确的节点上,然后该节点将数据返回给客户端。

两种方式各有利弊吧,客户端实现的方式需要客户端维护节点和slot的映射关系,服务端实现的方式需要服务端多做一次转发。redis的java客户端JedisCluster倒是两种方式都实现了,都有实现类,默认使用的是“客户端实现”的方式。
有很多地方没提到,比如redis主从复制的最终一致性,sentinel的工作流程等,redis集群中的事务以及包含多个key的请求时的问题等等,大家有兴趣可以去redis的官网看看,文档很详细。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值