两种redis集群解决方案:codis和cluster
1、Codis
codis是一个代理中间件,当客户端向codis发送指令时,codis负责将指令转发到后面的redis来执行,并将结果返回给客户端。
一个codis实例可以连接多个redis实例,也可以启动多个codis实例来支撑,每个codis节点都是对等的,这样可以增加整体的QPS需求,还能起到容灾功能。
槽位关系
codis根据key直接hash到1024个槽位,每个槽位对应后面的一个redis。计算出key的槽位就能将key转发到正确的redis实例。
槽位关系一旦变化,就会涉及所有redis实例,codis用zookeeper来存储槽位关系(集群配置中心),还提供了一个dashboard模块来观察和修改槽位关系。
槽位关系发生变化时,codis会用一个新的命令来查找指定槽位中的所有key,然后迁移每个key到正确的槽位。当请求打在正在迁移的槽位上时,codis会强制对该key进行迁移。
codis有自动均衡功能,在系统空闲时观察每个redis实例对应的槽位数量,不均衡时就会自动迁移。
codis命令
对于批量获取多个key的命令,codis会向每一个redis发送命令,然后汇总。
codis损失了redis的一些特性,因为原来存在一个redis里的key现在分散了,所以事务不支持了,rename这种涉及两个key的命令也不支持了。
codis优缺点
codis增加了一层代理,网络开销变大。
codis优点:分布式交给了第三方处理,dashboard功能强大,但是调优参数多。
2、Cluster
cluster是redis官方提供的集群化方案,它与codis不同,它是去中心化的,集群的每个节点负责一部分数据,相互连接形成一个对等的集群,它们之间通过一种特殊的二进制协议相互交互集群信息。
cluster的槽位划分更细,槽位的信息存储于每个节点上,不需要另外的分布式存储来存节点的信息。客户端也存有关于槽位的信息,它可以直接定位到目标节点。
槽位和迁移
cluster允许用户强制将某个key挂在特定槽位上。
当客户端向一个节点发送指令,但是节点已经没有这个key了,此时就会返回-moved,告知客户端应该去这个节点查找,同时客户端修改本地槽位映射表。
迁移时按槽迁移,一次性获取槽位所有key,然后一个key一个key迁移,每个key的迁移过程都是以原节点作为目标节点的客户端,key是先序列化发送,然后接收端再反序列化存入内存,然后返回ok,最后原节点删除该key。
迁移的过程是同步的,原节点的线程一直阻塞,直到迁移成功。
在迁移时请求,客户端会先访问旧节点,如果不在,旧节点返回重定向指令,然后客户端发送asking命令给目标节点,然后再向目标节点请求。因为目标节点在迁移完成之前不属于该槽位,所以直接访问会被拒绝,发出moved命令,所以先发送asking告知下一条命令必须处理才行。
这样迁移时的访问效率由一次ttl变为了三次。
如果在迁移时访问旧节点得到一个moved,然后如果此时新节点也要进行迁移,那么访问新节点还是会得到一个moved,这就是多次重试,可以控制这个次数,如果超过一定次数直接认为失败抛出异常。
( 无论是哪种集群方案,大key总是会大大降低迁移速度影响性能。)
gossip协议和节点失联
redis集群使用gossip协议来广播自己的状态以及接受其他实例的信息。一个节点发现另一个节点失联会把这条信息广播出去,其他节点也收到该信息,如果发现该节点失联的信息发生了很多,就标记该节点下线,然后进行主从切换,这就是一个节点失联不代表失效,必须经过一个集群协商的过程。
有时发生网络抖动,会导致节点失联,频繁的主从切换,此时可以设置timeout时间,还可以设置关于该时间的放大系数。以确保网络抖动时主从切换不那么明显。
如果服务器节点变更,客户端访问挂掉的节点,会抛出一个错误,此时会随机访问某个节点以确定原来槽位对应的位置。修改集群信息后,客户端访问会被告知集群不可用,此时就会重新初始化节点信息。