Redis集群不断发展,可实现在多台机器,部署多实例,每个实例存部分数据。 同时每个实例可以带上Redis从实例,保证若Redis主实例挂了,自动切换到redis从实例。
上古时代,很多用codis之类客户端支持集群。现在版本大家都用Redis cluster,即官方提供的集群模式,必须深入研究了。
从单机的一主多从复制架构到现在的分布式架构
主要有如下维度:
追求更高QPS
Scale Up已经无法满足,超过了单机极限,考虑Scale Out分布式。
Redis内存淘汰策略,保证不可能超过master节点内存阈值。master节点数据和slave节点的数据保持一致。 一个redis即可横向扩容了。若要支撑更大数据缓存,就横向扩容更多master节点,每个master节点就能存放更多数据。单台服务器32G,30台即可达1TB。
业务流量超过服务器网卡上限,考虑分布式分流
需要中间环节缓冲等需求
节点之间完成相互通信的基础,有一定的频率和规则。
CLUSTER MEET命令被用来连接不同的开启集群支持的 Redis 节点,以进入工作集群。
每个节点默认互不信任,并且被认为是未知节点,以防系统管理错误或地址被修改,而不太可能将多个不同的集群节点混成一个集群。
因此,为了使给定节点能将另一个节点接收到组成 Redis Cluster 的节点列表中,这里只有两种方法:
Redis Cluster 需要形成一个完整的网络(每个节点都连接着其他每个节点),但为创建一个集群,不需要发送形成网络所需的所有CLUSTER MEET命令。发送CLUSTER MEET消息以便每个节点能够到达其他每个节点只需通过一条已知的节点链就够了。由于在心跳包中会交换 gossip 信息,将会创建节点间缺失的链接。
所以,如果我们通过CLUSTER MEET链接节点 A 和 B ,并且 B 和 C 有链接,那么节点 A 和 C 会发现他们握手和创建链接的方法。
假设某一集群有A、B、C、D四个节点,可以只发送以下一组命令给 A :
CLUSTER MEET B-ip B-port
CLUSTER MEET C-ip C-port
CLUSTER MEET D-ip D-port
由于 A 知道及被其他所有节点知道,它将会在发送的心跳包中包含gossip部分,这将允许其他每个节点彼此都创建一个链接,即使集群很大,也能在数秒内形成一个完整网络。
CLUSTER MEET无需相互执行,即若发送命令给 A 以加入B ,那就不必也发送给 B 以加入 A。
当某一给定节点接收到一个MEET消息时,命令中指定的节点仍不知发送了该命令,所以为使节点强制将接收命令的节点将它作为信任的节点接受它,它会发送MEET包而非PING包。两个消息包有相同的格式,但是MEET强制使接收消息包的节点确认发送消息包的节点为可信任的。
把16384个槽平分给节点管理,每个节点只对自己负责的槽进行读写。
每个节点间都相互通信,所以每个节点都知道其它节点所管理槽的范围
集群的伸缩包括新节点的加入和旧节点退出。
由于Redis采用Gossip协议,所以可让新节点与任一现有集群节点握手,一段时间后整个集群都会知道加入了新节点。
向如下集群中新加入一个节点6385。由于负载均衡的要求,加入后四个节点每个节点负责4096个slots,但集群中原来的每个节点都负责5462个slots,所以6379、6380、6381节点都需要向新的节点6385迁移1366个slots。
Redis集群并没有一个自动实现负载均衡的工具,把多少slots从哪个节点迁移到哪个节点完全是由用户指定。
迁移key可以用pipeline进行批量的迁移。
对于扩容,原理已经很清晰了,至于具体操作,网上很多。 至于缩容,也是先手动完成数据迁移,再关闭redis。收缩时如果下线的节点有负责的槽需要迁移到其他节点,再通过cluster forget命令让集群内所有节点忘记被下线节点
每个节点通信共享Redis Cluster中槽和集群中对应节点的关系。
客户端不会自动找到目标节点执行命令,需要二次执行
由于集群伸缩时,需要数据迁移。
虽然MOVED意味着我们认为哈希槽由另一个节点永久提供,并且应该对指定节点尝试下一个查询,所以ASK意味着仅将下一个查询发送到指定节点。
之所以需要这样做,是因为下一个关于哈希槽的查询可能是关于仍在A中的键的,因此我们始终希望客户端尝试A,然后在需要时尝试B。由于只有16384个可用的哈希槽中有一个发生,因此群集上的性能下降是可以接受的。
都是客户端重定向:
追求性能
Cluster slots
初始化槽和节点映射mget、mset须在同一槽。
Redis Cluster 不同于 Redis 单节点,甚至和一个 Sentinel 监控的主从模式也不一样。主要因为集群自动分片,将一个key 映射到16384槽之一,这些槽分布在多节点。因此操作多 key 的命令必须保证所有的key都映射同一槽,避免跨槽执行错误。
一个单独的集群节点,只服务一组专用的keys,请求一个命令到一个Server,只能得到该Server上拥有keys的对应结果。 一个非常简单的例子是执行KEYS命令,当发布该命令到集群中某节点时,只能得到该节点上拥有key,并非集群中所有key。要得到集群中所有key,必须从集群的所有主节点上获取所有key。
对于分散在redis集群中不同节点的数据,如何比较高效地批量获取数据呢?
定义for循环,遍历所有key,分别去所有的Redis节点中获取值并进行汇总,简单,但效率不高,需n次网络时间。
优化串行的mget,在客户端本地做内聚,对每个key hash,然后取余,知道key对应槽
本地已缓存了槽与节点的对应关系,然后对key按节点进行分组,成立子集,然后使用pipeline把命令发送到对应的node,需要nodes次网络时间,大大减少了网络时间开销。
优化串行IO,分组key后,根据节点数量启动对应的线程数,根据多线程模式并行向node节点请求数据,只需1次网络时间
不做任何改变,hash后就比较均匀地散在每个节点上
是否能像单机,一次IO将所有key取出呢?hash-tag提供了这样功能:若将上述key改为如下,即大括号括起来相同的内容,保证所有的key只向一个node请求数据,这样执行类似mget命令只需要去一个节点获取数据即可,效率更高。
方案 | 优点 | 缺点 | 网络 I/O |
---|---|---|---|
串行mget | 1.编程简单 2.少量keys,性能满足要求 | 大量keys请求延迟严重 | O(keys) |
串行IO | 1.编程简单 2.少量节点,性能满足要求 | 大量 node延迟严重 | O(nodes) |
并行IO | 1.利用并行特性 2.延迟取决于最慢的节点 | 1.编程复杂 2.超时定位较难 | O(max_slow(node)) |
hash tags | 性能最高 | 1.tag-key业务维护成本较高 2.tag分布容易出现数据倾斜 | O(1) |
第一种方式,使用多线程解决批量问题,减少带宽时延,提高效率,这种做法就如上面所说简单便捷(我们目前批量操作类型比较多),有效。但问题比较明显。批量操作数量不大即可满足。 搜狐的cachecloud采用第二点,先将key获取槽点,然后分node pipeline操作。这种做法相对比第一种做法较优。
Redis Cluster通过ping/pong消息实现故障发现:不需要sentinel。ping/pong不仅能传递节点与槽的对应消息,也能传递其他状态,比如:节点主从状态,节点故障等
故障发现就是通过这种模式来实现,分为主观下线和客观下线:
某节点认为另一节点不可用,这仅代表一个节点对另一节点的判断,不代表所有节点的认知。
node-timeout
,则把节点2标识为pfail
状态当半数以上持有槽的主节点都标记某节点主观下线。可以保证判断的公平性。
集群模式下,只有主节点(master)才有读写权限和集群槽的维护权限,从节点(slave)只有复制的权限。
从节点接收到它的主节点客观下线的通知,则进行故障恢复。
使偏移量最大的从节点具备优先级成为主节点的条件。
对选举出来的多个从节点进行投票,选出新的主节点。
但是大多数业务都无法容忍,建议把
cluster-require-full-coverage
设为no
单独“走”一套redis sentinel。就是针对目标的几个节点构建redis sentinel,在这个里面实现广播。
分布式数据库存在倾斜问题是比较常见的。集群倾斜也就是各个节点使用的内存不一致
redis-trib.rb info ip:port查看节点,槽,键值分布
redis-trib.rb rebalance ip:port进行均衡(谨慎使用)
读写分离:更加复杂(成本很高,尽量不要使用)
集群不一定好
参考