Redis3.0之后,节点之间经过去中心化的方式提供了完整的sharding(数据分片)、replication(复制机制、Cluster具有感知准备的能力)、failover解决方案。node
Redis Cluster由多个Redis节点组构成。不一样节点组服务的数据无交集,每个节点组对应数据sharding的一个分片。算法
节点组内分为主备两类节点,二者数据准实时一致,经过异步化的主备复制机制。缓存
master节点对用户提供读写服务,slave节点对用户提供读服务。服务器
Redis Cluster总共有16384个slot,每个节点负责一部分slot。网络
Redis Cluster中全部的几点之间两两经过Redis Cluster Bus交互,主要交互如下关键信息:数据结构
Redis Cluster Bus经过单独的端口进行链接,bus是节点间的内部通讯机制,交互的是字节序列化信息,而不是client到Redis服务器的字符序列化以提高交互效率。架构
Redis Cluster是去中心化的分布式实现方案,客户端能够和集群中的任一节点链接。并发
去中心化意味着集群的拓扑结构并不保存在单独的配置节点上,Redis Cluster经过引入两个自增的epoch变量来使得集群配置在各个节点间达成最终一致。异步
Redis Cluster中的每个节点都保存了集群的配置信息,这些信息存储在clusterState中。分布式
Redis Cluster经过epoch做为版本号来实现集群配置的一致性。
去中心化的架构不存在统一的配置中心,各个节点对集群状态的认知来自于节点间的信息交互。在Redis Cluster中,该信息的交互经过Redis Cluster Bus来完成。
clusterMsg的type字段指明了消息的类型。配置信息的一致性主要依靠PING和PONG,二者除了type不一样,其他字段语义均相同,消息体为Gossip数据。
每个节点向其余节点较为频繁的周期性发送PING消息和接受PONG响应。在这些下拍戏的Gossip部分,包含了发送者节点(或者响应者节点)所知的集群其余节点信息,接收节点能够根据这些Gossip信息更新本身对于集群的认知。
规模较大的集群可能存在上千个节点,可是这些节点在正常状况下都是稳定的,所以每次都发送全量数据并没必要要,并且还会形成网络负担。
做为优化,Redis Cluster在每次的PING和PONG包中,只包含全集群部分节点信息,节点随机选取,以此控制网络流量。因为交互频繁,短期的几回交互以后,集群状态就会以Gossip协议的方式被扩散到了集群中的全部节点。
集群结构稳定不发生变化时,各个节点经过Gossip协议在几轮交互以后即可得知全集群的信息而且达到一致的状态。
可是,当发生故障转移、分片迁移等状况将会形成集群结构变动,变动的信息须要各个节点之间自行协调,优先得知变动信息的节点利用epoch变量将本身的最新信息扩散到整个集群,达到最终一致。
集群信息的更新规则:
不一样的节点组服务于相互无交互的数据子集(sharding,分片)。
Redis Cluster将全部的数据划分为16384个分片(slot),每一个分片负责其中一部分。每一条数据根据key值经过数据分布算法映射到16384个slot中的一个。
数据分布算法:slotId=crc(key)%16384
客户端根据slotId决定将请求路由到哪一个Redis节点。Cluster不支持跨节点的单命令。
为此,Redis引入HashTag的概念,使得数据分布算法能够根据key的某一部分进行计算,让相关的两条记录落到同一个数据分片,例如:
Redis会根据{}之间的子字符串做为数据分布算法的输入。
Redis Cluster的客户端须要具有必定的路由能力。当一个Client访问的key不在对应Redis节点的slot中,Redis返回给Client一个moved命令,告知其正确的路由信息。
从Client收到moved响应,到再次向moved响应中指向的节点发送请求期间,Redis Cluster的数据分布可能又发生了变动,此时,指向的节点会继续响应moved。Client根据moved响应更新其内部的路由缓存信息,以便下一次请求时直接路由到正确的节点,下降交互次数。
当Cluster处在数据重分布(目前由人工触发)过程当中时,能够经过ask命令控制客户端路由。
ask命令和moved命令的不一样语义在于,后者会更新路由缓存,前者只是本条操做重定向到新节点,后续的相同slot操做仍路由到旧节点。ask类型将重定向和路由缓存更新分离,避免客户端的路由缓存信息频繁更新。
在稳定的Redis Cluster下,每个slot对应的节点是肯定的。可是在某些状况下,节点和分片的对应关系要发生变动:
此时须要进行分片的迁移。分片迁移的触发和过程由外部系统完成,Redis Cluster只提供迁移过程当中须要的原语供外部系统调用。这些原语主要有两种:
当节点A的状态被设置为了MIGRATING后,表示对应的slot正在从A迁出,为保证该slot数据的一致性,A此时对slot内部数据提供读写服务的行为和一般状态下有所区别,对于某个迁移中的slot:
当节点B的状态被设置为了IMPORTING以后,表示对应的slot正在向B迁入中,即便B仍能对外提供该slot的读写服务,但行为和一般状态下也有所区别:
这样的状态控制能够保证同一个key在迁移以前老是在源节点执行,迁移后老是在目标节点执行,杜绝了两边同时写致使值冲突的可能性。且迁移过程当中新增的key老是在目标节点执行,源节点不会再有新增的key,使得迁移过程时间有界。
Redis单机对于命令的处理是单线程的,同一个key在MIGRATE的过程当中不会处理对该key的其余操做,从而保证了迁移的原子性。
当slot的全部key从A迁移至B上以后,客户端经过CLUSTER SETSLOT命令设置B的分片信息,使之包含迁移的slot。设置的过程当中会自增一个epoch,它大于当前集群中的全部epoch值,这个新的配置信息会传播到集群中的其余每个节点,完成分片节点映射关系的更新。
Redis Cluster同Sentinel同样,具有完整的节点故障发现、故障状态一致性保证、主备切换机制。
failover的过程以下:
Redis Cluster节点间经过Redis Cluster Bus两两周期性地进行PING/PONG交互,当某个节点宕机时,其余发向它的PING消息将没法及时响应,当PONG的响应超过必定时间(NODE_TIMEOUT)未收到,则发送者认为接受节点故障,将其置为PFAIL状态,后续经过Gossip发出的PING/PONG消息中,这个节点的PFAIL状态将会被转播到集群的其余节点。
Redis Cluster的节点间经过TCP保持Redis Cluster Bus链接,当对端无PONG回复时,除了节点故障外,还有多是TCP链接断开。对于TCP链接断开致使的响应超时,将会产生节点状态误报。所以Redis Cluster经过预重试机制排除此类误报:当NODE_TIMEOUT/2过去了却还未收到PING对应的PONG消息,则重建链接重发PING消息,若是对端正常,PONG会在很短期内抵达。
对于网络分割的节点,某个节点(假设叫B节点)并无故障,但可能和A没法链接,可是和C/D等其余节点能够正常联通,此时只有A会将B标记为PFAIL,其余节点扔人认为B是正常的。此时A和C/D等其余节点信息不一致。Redis Cluster经过故障确认协议达成一致。
A会受到来自其余节点的Gossip消息,被告知节点B是否处于PFAIL状态,当A受到的来自其余master节点的B的PFAIL达到必定数量后,会将B的PFAIL升级为FAIL状态,表示B已确认为故障,后续将会发起slave选举流程
上例中,若是B是A的master,且B已经被集群公认是FAIL状态,那么A将发起竞选,指望替代B成为新的master。
若是B有多个slave A/E/F都意识到B处于FAIL状态了,A/E/F可能会同时发起竞选,当B的slave数量>=3个时,颇有可能由于票数均匀没法选出胜者,延长B上的slot不可用时间。为此,slave间会在选举前协商优先级,优先级高的slave更有可能早地发起选举,优先级较低的slave发起选举的时间越靠后,避免和高优先级的slave竞争,提高一轮完成选举的可能性。
优先级最重要的决定因素是slave最后一次同步master信息的时间,越新标识这个slave的数据越新,竞选优先级越高。
slave经过向其余master节点发送FAILOVER_AUTH_REQUEST消息发起竞选,master收到以后回复FAILOVER_AUTH_ACK消息告知本身是否赞成改slave成为新的master。slave发送FAILOVER_AUTH_REQUEST前会将currentEpoch自增并将最新的epoch带入到AILOVER_AUTH_REQUEST消息中,master收到FAILOVER_AUTH_REQUEST消息后,若是发现对于本轮(本epoch)本身还没有投过票,则回复赞成,不然回复拒绝。
当slave收到超过半数的master的赞成回复时,该slave顺利的替代B成为新master,此时它会以最新的epoch经过PONG消息广播本身成为master的信息,让集群中的其余节点更快地更新拓扑信息。
当B恢复可用以后,它首先仍然认为本身是master,但逐渐得经过Gossip协议得知A已经替代本身的事实以后降级为A的slave。
Redis采用主备复制的方式保持一致性,即全部节点中,有一个节点为master,对外提供写入服务,全部的数据变动由外界对master的写入触发,以后Redis内部异步地将数据从主节点复制到其余节点上。
Redis包含master和slave节点:master节点对外提供读写服务;slave节点做为master的数据备份,拥有master的全量数据,对外不提供写服务。主备复制由slave主动触发。
slave侧的处理逻辑:
若是有多个slave节点并发发送SYNC命令给master,只要第二个slave的SYNC命令发生在master完成BGSAVE以前,第二个slave将受到和第一个slave相同的快照和后续的backlog;不然,第二个slave的SYNC将触发master的第二次BGSAVE。
slave经过SYNC命令和master进行数据同步时,master都会dump全量数据。假设master和slave断开很短的时间,数据只有不多的差别,重连后也会发送这些全量数据致使大量的无效开销。最好的方式就是,master-slave只同步断开期间的少许数据。
Redis的PSYNC可用于替代SYNC,作到master-slave基于断点续传的主备同步协议。master-slave两端经过维护一个offset记录当前已经同步过的命令,slave断开期间,master的客户端命令会保持在缓存中,在slave命令重连后,告知master断开时的最新offset,master则将缓存中大于offset的数据发送给slave,而断开前已经同步过的数据,则再也不从新同步,这样减小了数据传输开销。
对于有读写分离需求的场景,应用对于某些读的请求容许舍弃必定的数据一致性,以换取更高的读吞吐量,此时但愿将读的请求交由slave处理以分担master的压力。
默认状况下,数据分片映射关系中,某个slot对应的节点必定是一个master节点,客户端经过MOVED消息得知的集群拓扑结构也只会将请求路由到各个master中,即使客户将读请求直接发送到slave上,后者也会回复MOVED到master的响应。
Redis Cluster引入了READONLY命令。客户端向slave发送该命令后,slave对于读操做,将再也不MOVED回master而不是直接处理,这被称为slave的READONLY模式。经过READWRITE命令,可将slave的READONLY模式重置。
集群只须要保持2*master+1个节点,就能够在任一节点宕机后仍然自动地维持,称为master的单点保护。