Redis Cluster 功能特性
Redis 集群是分布式的redis 实现,具有以下特性:
1. 高可用性与可线性扩张到1000个节点 2. 数据自动路由到多个节点 3. 节点间数据共享 4. 可动态添加或者删除节点 5. 部分节点不可达时,集群仍可用 6. 数据通过异步复制,不保证数据的强一致性 7. 可动态调整数据分布
Redis 集群架构图
其中 一: Redis 集群协议 1、Redis 集群,节点负责存储数据、记录集群状态,集群节点能自动发现其他节点,检测出节点的状态,并在需要的时候,推选中主节点 2、Redis 集群节点中通过TCP连接和一个二级制协议(cluster bus) 建立通信。发现新的节点、发送PING包、特定的情况下发送集群消息。集群连接能够发布与订阅消息 3、Redis 集群节点不能代理请求,客户端发起请求后,接收到重定向(MOVED\ASK)错误,会自动重定向到其他节点。理论上来说,客户端是可以自由地向集群中的所有节点发送请求,在需要的时候把请求重定向到其他节点,所以客户端是不需要保存集群状态。 不过客户端可以缓存键值和节点之间的映射关系,这样能明显提高命令执行的效率。 二: 安全写入 Redis 集群节点之间使用异步复制,在分区过程中存在窗口,容易导致丢失写入数据,Redis集群即使努力尝试所有写入,但是以下两种情况可能丢失数据: 1、命令操作到达主节点后,但在主节点回复的时候,此时写入可能还没有通过主节点复制到从节点那里。如果这时候主库宕机了,这条命令永久丢失。以防主节点长时间不可达而它的一个从节点已经被提升为主节点。 2、分区导致一个主节点不可达,然而集群发送故障转移(failover),提升从节点为主节点,原来的主节点再次恢复。一个没有更新路由表(routing table)的客户端或许会在集群把这个主节点变成一个从节点(新主节点的从节点)之前对它进行写入操作。导致数据彻底丢失 三: 可用性 Redis 集群少数节点不可用后,在经过cluster-node-timeout时间后,集群根据自动故障机制,将从节点提升为主节点。这事集群恢复可用 举个例子,一个由 N 个主节点组成的集群,每个主节点都只有一个从节点。当有一个节点(因为故障)被分割出去后,集群的多数节点这边仍然是可访问的。当有两个节点(因故障)被分割出去后集群仍可用的概率是 1-(1/(N*2-1))(在第一个节点故障出错后总共剩下 N*2-1 个节点,那么失去冗余备份(即失去从节点)的那个主节点也故障出错的概率是 1/(N*2-1)))。 比如一个拥有6个节点的集群,每个节点都只有一个从节点,那么在两个节点从多数节点这边分割出去后集群不再可用的概率是 1/(6*2-1) = 0.0909,即有大约 9% 的概率。
Redis 集群数据分布
Redis 集群没有使用一致性hash,引入了哈希槽(HASH SLOT). Redis 集群中所有的主节点都负责 16384 个哈希槽中的一部分。当集群处于稳定状态时,集群中没有在执行重配置(reconfiguration)操作,每个哈希槽都只由一个节点进行处理(不过主节点可以有一个或多个从节点,可以在网络断线或节点失效时替换掉主节点) slot = CRC16(KEY) / 16384
Redis 集群键HASH标签
目标: HASH 标签是确保两个KEY 都能在同一个HASH槽的一种方式。 实现方式: HASH 槽是用另一种不同的计算方式计算的。基本来说,如果KEY包含一个"{...}"这样的模式,只有“{” 和 “}” 之间的字符串会被用来做HASH以获取HAS槽。如果同时出现多个“{}” 计算方式如下: * 如果KEY 包含一个 “{” 字符 * 那么在 “{”的右边就会字符 "}" * 在字符 "{" 和 "}"直接会有一个或多个字符。但是第一个"}" 一定会出现在第一个"{"之后 * 只有在第一个 { 和它右边第一个 } 之间的内容会被用来计算哈希值 例子: 1、比如这两个键 user:{1000}.following 和user:{1000}.followers 会被哈希到同一个哈希槽里,因为只有 "1000" 这个子串会被用来计算哈希值。 2、对于 user{}{list} 这个键,整个键都会被用来计算哈希值,因为第一个出现的 { 和它右边第一个出现的 } 之间没有任何字符。 3、对于 user{{momoid}}following 这个键,用来计算哈希值的是 "{momoid" 这个子串,因为它是第一个 { 及其右边第一个 } 之间的内容。 4、对于 user{momoid}{following} 这个键,用来计算哈希值的是 "momoid" 这个子串,因为算法会在第一次有效或无效(比如中间没有任何字节)地匹配到 { 和 } 的时候停止。 5、按照这个算法,如果一个键是以 {} 开头的话,那么就当作整个键会被用来计算哈希值。当使用二进制数据做为键名称的时候,这是非常有用的。
Redis 集群相关命令
集群 1、CLUSTER INFO 打印集群的信息 2、CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。 3、CLUSTER FAILOVER 手动故障转移,需要在转移的主节点的从节点上执行 节点 1、CLUSTER MEET 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。 2、CLUSTER FORGET从集群中移除 node_id 指定的节点。 3、CLUSTER REPLICATE 将当前节点设置为 node_id 指定的节点的从节点。 4、CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。 槽(slot) 1、CLUSTER ADDSLOTS
Redis 集群配置
redis 集群需要运行在 集群模式的redis实例,不是普通的redis实例。集群模式需要添加集群相关的配置。开启集群模式的redis实例,可以使用集群特有的命令和特性
其中相关配置如下:
必须配置: cluster-enabled yes --> 开启集群模式 cluster-config-file nodes-30000.conf --> 集群相关的信息 cluster-node-timeout 15000 --> 节点超时时间,用来failover的操作 可选配置: cluster-slave-validity-factor 10 cluster-migration-barrier 1 cluster-require-full-coverage yes
Redis Cluster 主从搭建
启动集群模式的实例(与普通启动方式一致),不需搭建主从关系
搭建集群: 在上述启动的6个redis实例中,搭建集群。通过redis自带的集群命令行工具 redis-trib.rb 。 redis-trib.rb 位于redis源码包中src文件中。它可以完成创建集群、检查集群、集群reshard、添加节点、删除节点等操作
创建集群 redis-trib.rb create [–replicas [N]] host:ip [host:ip]
redis-trib.rb create --replicas 1 127.0.0.1:30000 127.0.0.1:30001 127.0.0.1:30001 127.0.0.1:31000 127.0.0.1:31001 127.0.0.1:31002 其中 --replicas N 选项表明集群中的每个节点带几个从节点 输出日志: [OK] All 16384 slots covered
集群状态检查 redis-trib.rb check host:ip
redis-trib.rb check 127.0.0.1:30000 输出日志: Connecting to node 127.0.0.1:30000: OK Connecting to node 127.0.0.1:30002: OK Connecting to node 127.0.0.1:31000: OK Connecting to node 127.0.0.1:31001: OK Connecting to node 127.0.0.1:30001: OK Connecting to node 127.0.0.1:31002: OK >>> Performing Cluster Check (using node 127.0.0.1:30000) M: 36801ef9849f12526be1e954f9e6f6fa24c50d46 127.0.0.1:30000 slots:0-5961,10923-11421 (6461 slots) master 1 additional replica(s) M: 98c4c66ee189569dec47a9600b057f90626cc6a7 127.0.0.1:30002 slots:11422-16383 (4962 slots) master 1 additional replica(s) S: 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 127.0.0.1:31000 slots: (0 slots) slave replicates 36801ef9849f12526be1e954f9e6f6fa24c50d46 S: 3f5ae989b9b1b6617c53e77ed4853b618408bbe6 127.0.0.1:31001 slots: (0 slots) slave replicates 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 M: 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 127.0.0.1:30001 slots:5962-10922 (4961 slots) master 1 additional replica(s) S: 81a3a70ce2fbb8bcce2e9be9ed77e34d9d4d5b21 127.0.0.1:31002 slots: (0 slots) slave replicates 98c4c66ee189569dec47a9600b057f90626cc6a7 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
添加节点: 启动一个新的集群模式的redis实例。使用 redis-trib.rb add-node host:ip host:ip
redis-trib.rb add-node 127.0.0.1:30004 127.0.0.1:3000 其中 127.0.0.1:30004 为新节点 127.0.0.1:30000 为集群中任意节点 查看集群节点: redis-cli -c -p 30000 127.0.0.1:30000> CLUSTER NODES 98c4c66ee189569dec47a9600b057f90626cc6a7 127.0.0.1:30002 master - 0 1429686483614 3 connected 11422-16383 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 127.0.0.1:31000 slave 36801ef9849f12526be1e954f9e6f6fa24c50d46 0 1429686485615 7 connected 3f5ae989b9b1b6617c53e77ed4853b618408bbe6 127.0.0.1:31001 slave 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 0 1429686484614 5 connected 2eb135bf03dbdbc57e704578b2833cc3fb860b6e 127.0.0.1:30004 master - 0 1429686479607 0 connected --> 新节点 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 127.0.0.1:30001 master - 0 1429686481612 2 connected 5962-10922 81a3a70ce2fbb8bcce2e9be9ed77e34d9d4d5b21 127.0.0.1:31002 slave 98c4c66ee189569dec47a9600b057f90626cc6a7 0 1429686482613 6 connected 36801ef9849f12526be1e954f9e6f6fa24c50d46 127.0.0.1:30000 myself,master - 0 0 7 connected 0-5961 10923-11421 输出信息解析: 1、节点ID 2、IP:PORT 3、节点状态标识: master、slave、myself、fail?、fail 4、如果是从节点,表示主节点的ID。如果是主节点,为 '-' 5、集群最近一次向各个节点发送PING命令后,过去多长时间还没有接到回复 6、节点最近一次返回PONG的时间戳 7、节点的配置纪元 8、本节点的网络连接情况: connected、disconnected 9、如果是主节点,表示节点包含的曹
添加从节点: CLUSTER REPLICATE ID
127.0.0.1:31004> CLUSTER REPLICATE 2eb135bf03dbdbc57e704578b2833cc3fb860b6e 其中 2eb135bf03dbdbc57e704578b2833cc3fb860b6e 为主库的集群ID
集群reshard: 为新节点分片slots redis-trib.rb reshard host:port
redis-trib.rb reshard 127.0.0.1:30004 日志输出: Shell# redis-trib.rb reshard 127.0.0.1:30004 Connecting to node 127.0.0.1:30004: OK Connecting to node 127.0.0.1:30000: OK Connecting to node 127.0.0.1:31001: OK Connecting to node 127.0.0.1:30001: OK Connecting to node 127.0.0.1:31000: OK Connecting to node 127.0.0.1:30002: OK Connecting to node 127.0.0.1:31002: OK >>> Performing Cluster Check (using node 127.0.0.1:30004) M: 2eb135bf03dbdbc57e704578b2833cc3fb860b6e 127.0.0.1:30004 --> 新节点信息 slots: (0 slots) master 0 additional replica(s) M: 36801ef9849f12526be1e954f9e6f6fa24c50d46 127.0.0.1:30000 slots:0-5961,10923-11421 (6461 slots) master 1 additional replica(s) S: 3f5ae989b9b1b6617c53e77ed4853b618408bbe6 127.0.0.1:31001 slots: (0 slots) slave replicates 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 M: 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 127.0.0.1:30001 slots:5962-10922 (4961 slots) master 1 additional replica(s) S: 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 127.0.0.1:31000 slots: (0 slots) slave replicates 36801ef9849f12526be1e954f9e6f6fa24c50d46 M: 98c4c66ee189569dec47a9600b057f90626cc6a7 127.0.0.1:30002 slots:11422-16383 (4962 slots) master 1 additional replica(s) S: 81a3a70ce2fbb8bcce2e9be9ed77e34d9d4d5b21 127.0.0.1:31002 slots: (0 slots) slave replicates 98c4c66ee189569dec47a9600b057f90626cc6a7 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 1000 --> slot数量 What is the receiving node ID? 2eb135bf03dbdbc57e704578b2833cc3fb860b6e --> 接收集群NODE ID Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1:36801ef9849f12526be1e954f9e6f6fa24c50d46 --> 来源NODE ID Source node #2:done ......
Redis Cluster failover机制
节点心跳
Redis 集群在运行的过程中,每个节点每秒会随机ping几个节点,不过每个节点都会保证去PING满足这个条件的其他节点:在过去的一半"cluster-node-timeout"时间里都没有发送PING包过去或者没有接收从那节点发来的PONG包的节点。在"cluster-node-timeout"时间过去之前,若TCP连接有问题,节点会尝试去重试连接,确保自己不会被当做不可达的节点。 如果 “cluster-node-time” 被设为一个很小的数而节点数量(N)非常大,那么消息交流数量会比 O(N) 更大,因为每个节点都会尝试去 ping 每一个在过去一半 NODE_TIMEOUT 时间里都没更新信息的节点。
失效检测
Redis 集群失效检测是用来识别出大多数节点何时无法访问某一个主节点或从节点。当这个事件发生时,就提升一个从节点来做主节点;若如果无法提升从节点来做主节点的话,那么整个集群就置为错误状态并停止接收客户端的查询 每个节点都有一份跟其他已知节点相关的标识列表。其中有两个标识是用于失效检测,分别是 PFAIL 和 FAIL. PFAIL 标识: 表示可能失效(Possible failure),这是一个非公认的(non acknowledged)失效类型。 当一个节点在超过 "cluster-node-timeout" 时间后仍无法访问某个节点,那么它会用 PFAIL 来标识这个不可达的节点。无论节点类型是什么,主节点和从节点都能标识其他的节点为 PFAIL FAIL 表示一个节点已经失效,而且这个情况已经被大多数主节点在某段固定时间内确认过的了。 满足以下条件,PFAIL 状态升级为 FAIL 状态:(设定集群含有 A B C AS BS CS 六个节点) 1、节点A正常,节点C 状态为 PFAIL 2、节点A 通过gossip字段收集集群中大部分节点标识节点C的状态信息 3、如果大部分节点标识节点C为 PFAIL 状态,或者 cluster-node-timeout * FAIL_REPORT_VALIDITY_MULT 这段时间内处于 PFAIL状态 此时节点A 会标记 节点C 为 FAIL 状态,并向所有的节点发送关于节点C的 FAIL 信息。 FAIL 信息会强制接收的节点把节点C 标识为 FAIL 状态 NOTE: FAIL 标识基本都是单向的,也就是说,一个节点能从 PFAIL 状态升级到 FAIL 状态. 清除FAIL状态的方法: 1、节点已经恢复可达的,并且它是一个从节点。在这种情况下,FAIL 标识可以清除掉,因为从节点并没有被故障转移。 2、节点已经恢复可达的,而且它是一个主节点,但经过了很长时间(N * NODE_TIMEOUT)后也没有检测到任何从节点被提升了。
从选举与提升
从节点的选举与提升都是由从节点处理的,主节点会投票要提升哪个从节点。当满足以下条件,一个节点可以发起选举: 1、该从节点的主节点处理 FALI 状态 2、这个主节点负责的HASH曹个数不为O 3、从节点和主节点之间的重复连接(replication link)断线不超过一段给定的时间,这是为了确保从节点的数据是可靠 一旦从节点被推选出来,就会想主节点请求投票,一旦从节点赢得投票,它会响所有其他节点发送PING 和 PONG 数据包,宣布自己已经成为主节点,并且提供它的HASH槽信息,并配置 currentEpoch 信息 为了加速其他节点的重新配置,该节点会广播一个 pong 包 给集群里的所有节点(那些现在访问不到的节点最终也会收到一个 ping 包或 pong 包,并且进行重新配置)。其他节点会检测到有一个新的主节点(带着更大的configEpoch)在负责处理之前一个旧的主节点负责的哈希槽,然后就升级自己的配置信息。 旧主节点的从节点,或者是经过故障转移后重新加入集群的该旧主节点,不仅会升级配置信息,还会配置新主节点的备份。
模拟宕机(实现故障转移)
集群当期转态:
127.0.0.1:30000> CLUSTER NODES 98c4c66ee189569dec47a9600b057f90626cc6a7 127.0.0.1:30002 master - 0 1429696352375 3 connected 11422-16383 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 127.0.0.1:31000 slave 36801ef9849f12526be1e954f9e6f6fa24c50d46 0 1429696349869 9 connected 3f5ae989b9b1b6617c53e77ed4853b618408bbe6 127.0.0.1:31001 slave 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 0 1429696346364 5 connected 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 127.0.0.1:30001 master - 0 1429696351373 2 connected 5962-10922 81a3a70ce2fbb8bcce2e9be9ed77e34d9d4d5b21 127.0.0.1:31002 slave 98c4c66ee189569dec47a9600b057f90626cc6a7 0 1429696350370 6 connected 36801ef9849f12526be1e954f9e6f6fa24c50d46 127.0.0.1:30000 myself,master - 0 0 9 connected 0-5961 10923-11421 其中 127.0.0.1:31000 节点为 127.0.0.1:30000 从节点
关闭其中的一个节点后(127.0.0.1:30000)的集群状态:
127.0.0.1:30001> CLUSTER NODES 81a3a70ce2fbb8bcce2e9be9ed77e34d9d4d5b21 127.0.0.1:31002 slave 98c4c66ee189569dec47a9600b057f90626cc6a7 0 1429696448146 6 connected 98c4c66ee189569dec47a9600b057f90626cc6a7 127.0.0.1:30002 master - 0 1429696447143 3 connected 11422-16383 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 127.0.0.1:30001 myself,master - 0 0 2 connected 5962-10922 36801ef9849f12526be1e954f9e6f6fa24c50d46 127.0.0.1:30000 master,fail? - 1429696434521 1429696430116 9 disconnected 0-5961 10923-11421 3f5ae989b9b1b6617c53e77ed4853b618408bbe6 127.0.0.1:31001 slave 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 0 1429696445139 5 connected 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 127.0.0.1:31000 slave 36801ef9849f12526be1e954f9e6f6fa24c50d46 0 1429696449148 9 connected 其中 127.0.0.1:30000 的状态fail? 表示正在判断是否失败 127.0.0.1:30001> CLUSTER NODES 81a3a70ce2fbb8bcce2e9be9ed77e34d9d4d5b21 127.0.0.1:31002 slave 98c4c66ee189569dec47a9600b057f90626cc6a7 0 1429696473317 6 connected 98c4c66ee189569dec47a9600b057f90626cc6a7 127.0.0.1:30002 master - 0 1429696474317 3 connected 11422-16383 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 127.0.0.1:30001 myself,master - 0 0 2 connected 5962-10922 36801ef9849f12526be1e954f9e6f6fa24c50d46 127.0.0.1:30000 master,fail - 1429696434521 1429696430116 9 disconnected 3f5ae989b9b1b6617c53e77ed4853b618408bbe6 127.0.0.1:31001 slave 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 0 1429696472315 5 connected 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 127.0.0.1:31000 master - 0 1429696471313 10 connected 0-5961 10923-11421 其中 127.0.0.1:30000 的状态 fail 表示节点失败,127.0.0.1:30000 节点提升为主库。
恢复关闭的实例
127.0.0.1:30001> CLUSTER NODES 81a3a70ce2fbb8bcce2e9be9ed77e34d9d4d5b21 127.0.0.1:31002 slave 98c4c66ee189569dec47a9600b057f90626cc6a7 0 1429696545465 6 connected 98c4c66ee189569dec47a9600b057f90626cc6a7 127.0.0.1:30002 master - 0 1429696542960 3 connected 11422-16383 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 127.0.0.1:30001 myself,master - 0 0 2 connected 5962-10922 36801ef9849f12526be1e954f9e6f6fa24c50d46 127.0.0.1:30000 slave 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 0 1429696542458 10 connected 3f5ae989b9b1b6617c53e77ed4853b618408bbe6 127.0.0.1:31001 slave 6b880ae14f8c9dbfd54f8c4811cf0c039d523216 0 1429696546467 5 connected 54d7d1241b1d9c24f76d99e9814d8cf8d8db474e 127.0.0.1:31000 master - 0 1429696547470 10 connected 0-5961 10923-11421 其中 127.0.0.1:30000 变成 127.0.0.1:31000的从库
总结:
优点: 1、redis 在主节点下线后,从节点会自动提升为主节点,提供服务 2、redis 宕机节点恢复后,自动会添加到集群中,变成从节点 缺点: 1、由于redis的复制使用异步机制,在自动故障转移的过程中,集群可能会丢失写命令。然而 redis 几乎是同时执行(将命令恢复发送给客户端,以及将命令复制到从节点)这两个操作,所以实际中,命令丢失的窗口非常小。
集群订阅、发布
Redis 集群中,客户端能订阅任何一个节点,也能发布消息给任何一个节点。集群会确保发布的消息都会按需进行转发