redis作为数据存储系统,无论数据存储在内存中还是持久化到本地,作为单实例节点,在实际应用中会面临如下挑战:1.数据量伸缩单实例redis存储的key-value对的数量受限于单机的内存和磁盘容量。长期运行的生产环境中,随着数据不断的加入,存储容量会达到瓶颈。虽然redis提供了key的过期机制,在作为缓存使用时通过淘汰过期的数据可以达到控制容量的目的。但当redis作为NoSQl数据库时,业务数据长期有效使得淘汰机制不再适用。2.访问量伸缩单实例redis单线程的运行,吞吐量受限于单次请求处理的平均耗时。当业务数据集面临超过单实例处理能力的高吞吐量需求时,如何提升处理能力成为难点。3.单点故障redis持久化机制一定程度上缓解了宕机/重启带来的业务数据丢失问题,但当单实例所在的物理节点发生不可恢复故障时,如何保证业务数据不丢以及如何在故障期间迅速的恢复对应业务数据的可用性也成为单点结构的挑战。上述问题对于数据存储系统而言是通用的,基于分布式的解决方案如下:1.水平拆分分布式环境下,节点分为不同的分组,每个分组处理业务数据的一个子集,分组之间的数据无交集。数据无交集的特性使得水平拆分解决了数据量瓶颈,随着分组的增加,单个分组承载的数据子集更小,即通过增加分组来伸缩数据量。同时水平拆分也解决了访问量瓶颈,业务数据全集的请求被分摊到了不同的分组,随着分组的增加数据全集的总吞吐量也增加,访问量的伸缩性得意实现。2.主备复制同一份业务数据存在多个副本,对数据的每次访问根据一定的规则分发到某一个或者多个副本上执行。通过W+R>N的读写配置做到读数据内容的实时性。随着N的增加,当读写访问量差不多时,业务的吞吐量相比单个实例会提升到逼近2倍。但实际中,读的访问量常常远高于写的量,W=N,R=1,吞吐量会随着读写比例的增加而提升。3.故障转移当业务数据所在的节点故障时,这部分业务数据转移到其他节点上进行,使得故障节点在恢复期间,对应的业务数据仍然可用。显然,为了支持故障转移,业务数据需要保持多个副本,位于不同的节点上。1.水平拆分(sharding)为了解决数据量和访问量增加后对单个节点造成的性能压力,通常引入水平拆分机制,将数据存储和对数据的访问分散到不同节点上分别处理。水平拆分后的每个节点存储和处理数据原则上没有交集,使得节点间互相独立。但内部的拆分和多节点通常对外部服务透明,通过数据分布和路由请求的配合,可以做到数据存放和数据访问对水平拆分的适配。1.1 数据分布分布式环境下,有多个redis实例I[i](i=0,...,N),同时业务数据的key全集为{k[0],k[1]...,k[M]}。数据分布指的是一种映射关系f,每个业务数据key都能通过这种映射确定唯一的实例I,即f(k)=i,其中k对应的业务数据存放于I[i]。如何确定这个映射关系的算法?这其实主要取决于redis的客户端。常用的映射有3种:1.hash映射为了解决业务数据key的值域不确定这个问题,引入hash运算,将不可控的业务值域key映射到可控的有限值域(hash值)上,且映射做到均匀,再将有限的均匀分布的hash值枚举的映射到redis实例上。例如,crc16(key)%16384这个hash算法,将业务key映射到了0~16383 这一万多个确定的有限整数集合上,再根据一定规则将这个整数集合的不同子集不相交的划分到不同redis实例上,以此实现数据分布。2.范围映射和hash映射不同,范围映射通常选择key本身而不是key的某个函数运算值(如hash运算)作为数据分布的条件,且每个数据节点存放的key的值域是连续的一段范围。例如,当0<=key<100时,数据存放到实例1上;100<=key<200,数据存放在实例2上,;以此类推。key的值域是业务层决定的,业务层需要清除每个区间的范围和redis实例数量,才能完整的描述数据分布。这使得业务域的信息(key的值域)和系统域的信息(实例的数量)耦合,数据分布无法再纯系统层面实现,从系统层面看来,业务域的key值域不确定,不可控。3.hash和范围结合典型的方式就是一致性hash,首先对key进行hash运算,得到值域有限的hash值,再对hash值做范围映射,确定该key对应的业务数据存放的具体实例。这种方式的优点是节点新增或者退出时,涉及的数据迁移量少 --- 变更节点上的数据只需和相邻节点发生迁移关系;缺点是节点不是成倍变更(数量变成原有的N倍或者1/N)时,会造成数据的分布不均匀。1.2 请求路由确定了业务数据如何分布到redis的不同实例之后,实际数据访问时,根据请求中涉及的key,用对应的数据分布算法得出数据位于哪个实例,再将请求路由到该实例,这个过程叫做请求路由。需要关注数据跨实例问题:1.只读的跨实例请求:需要将请求中的多个key分别分发到对应的实例上执行,再合并结果。其中涉及语句的拆分和重生成。2.跨实例的原子读写请求:事务,集合型数据的转存操作(如zunionstore),向实例b的写入操作依赖于对实例a的读取。单实例情况下,redis单线程特性保证此类读写依赖的安全性。然后跨实例情况下,这个前提被打破,因此存在跨节点读写依赖的原子请求是不支持的。在redis cluster 之前,通常是通过 proxy 代理层处理 sharding 逻辑的。代理层可以位于客户端本身(如Predis),也可以是独立的实例(如Twemproxy)。2.主备复制(replication)当一份数据落在了多个不同的节点上时,如何保证节点间数据的一致性将是关键问题,在不同的存储系统架构下方案不同,有的采用客户端双写,有的采用存储层复制。redis采用主备复制的方式保证一致性,即所有节点中,有一个节点为主节点(master)它对外提供写入服务,所有的数据变更由外界对master的写入触发,之后redis内部异步的将数据从主节点复制到其他节点(slave)上。2.1 主备复制流程redis包含master和slave两种节点:master节点对外提供读写服务;slave节点作为master的数据备份,拥有master的全量数据,对外不提供写服务。主备复制由slave触发,流程如下:1.首先slave向master发起sync命令。这一步在slave启动后触发,master被动的将新进slave节点加入自己的主备复制集群。2.master收到sync后,开启bgsave操作。bgsave是redis的一种全量复制模式的持久化机制。3.bgsave完成后,master将快照信息发送给slave.4.发送期间,master收到来自用户客户端的新的写命令,除了正常响应之外,都再存一份到backlog队列。5.快照信息发送完成后,master继续发送backlog队列信息6.baocklog发送完成后,后续的写操作同时发送给slave,保持实时异步的复制。在slave侧,处理逻辑如下:1.发送完sync后,继续对外提供服务2.开始接收master的快照信息,此时,将slave现有数据清空,并将master快照写入自身内存3.接收backlog内容并执行它,即回放,期间对外提供读请求4.继续接收后续来自master的命令副本并继续回放,以保持数据和master一致如果有多个slave节点并发发送sync命令给master,企图建立主备关系,只要第二个slave的sync命令发生在master完成bgsave之前,第二个slave将收到和第一个slave相同的快照和后续backlog;否则,第二个slave的sync将触发master的第二次bgsave。2.2 断点续传每次当slave通过sync和master同步数据时,master都会dump全量数据并发发送。当一个已经和master完成了同步并保持了长时间的slave网络断开很短的时间再重新连接上时,master不得不做一次全量dump的传送。然后由于slave只断开了很短的时间,重连之后master-slave的差异数据很少,全量dump的数据绝大部分,slave都已经具有,再次发送这些数据会导致大量无效的开销。最好的方式是,master-slave只同步断开期间的少量数据。redis的 psync(partial sync)可以用于替代sync,做到master-slave基于断点续传的主备同步协议。master-slave两端通过维护一个offset记录当前已经同步过的命令,slave断开期间,master的客户端命令会保持在缓存中,在slave重连之后,告知master断开时最新的offset,master则将缓存中大于offset的数据发送给slave,而断开之前已经同步过的数据,则不再重新同步,这样减少了数据传输的开销。3.故障转移(failover)当两台以上的redis实例形成了主备关系,它们组成的集群就具备了一定的高可用性。当master故障时,slave可以成为master,对外继续提供读写服务,这种运营机制被称为failover。剩下的问题在于:谁去发现master的故障做failover的决策?一种方式是,保持一个daemon进程,监控所有的master-slave节点。这种方式的问题在于:daemon作为单点,它本身的可用性无法保证。因此需要引入多个daemon。为了解决一个daemon的单点问题,我们引入了2个daemon进程,同时监控着三个redis节点。但是,多个daemon的引入虽然解决了可用性问题,但带来了一致性问题:多个daemon之间,如何就某个master是否可用达成一致?比如daemon1和master之间的网络不通,但master和其他节点畅通,那么daemon1和daemon2观察到的master可用状态不通,那么如此决策?redis 的 sentinel 提供了一套多daemon间的交互机制,解决故障发现,failover决策协商机制等问题。多个daemon节点组成了一个集群,称为 sentinel 集群,其中的daemon也被称为sentinel节点。这些节点互相通信,选举,协商,在master节点的故障发现,failover决策上保持一致。 3.1 sentinel间的相互感知sentinel 节点间因为共同监视了同一个master节点从而互相也关联起来,一个新加入的sentinel节点需要和有相同监视的master的其他sentinel节点互相感知,方式如下:所有需要互相感知的sentinel都向它们共同的master节点上订阅相同的channel:__sentinel__:hello,新加入的sentinel节点向这个channel发布一条信息,包含了自己的信息,该channel的订阅者们就可以发现这个新的sentinel。随后新的sentinel和已有的其他 sentinel 节点建立长连接。sentinel集群中所有的节点两两连接。新的sentinel 节点加入之后,它向master节点发布自己加入这个信息,此时现有的订阅sentinel节点将会发现这条消息从而感知到了新的sentinel节点的存在。3.2 master的故障发现sentinel 节点通过定期向 master 发送心跳包来判断其存活状态,称为PING。一旦发现master没有正确的响应,sentinel 将此 master 置为'主观不可用态',所谓主观,是因为'master不可用'这个判断尚未得到其他sentinel节点确认。随后它将'主观不可用态'发送给其他所有的sentinel节点进行确认("is-master-down-by-addr" 这条交互),当确认的sentinel 节点数 >= quorum(可配置)时,则判断该master为'客观不可用',随后进入failover流程。3.3 failover决策    当一台master真正宕机后,可能多个sentinel节点同时发现了此问题并通过交互确认了互相的'主观不可用'猜想,同时达到'客观不可用态',同时打算发起failover。但是最终只能有一个sentinel节点作为failover的发起者,此时需要开始一个leader选举的过程,谁来发起failover。redis 的 sentinel  机制采用了类似Raft协议实现这个选举算法:1.sentinelState 的 epoch 变量类似于 raft 协议中的 term(选举回合)2.每一个确认了master'客观不可用态'的sentinel节点都会向周围广播自己参选的请求3.每一个接收参选请求的sentinel节点如果还没有人向它发送过参选请求,它就将本选举回合的意向置为首个参选sentinel并回复它;如果已经在本回合表过意向的,则拒绝本回合内所有其他的参选请求,并将已有意向回复给参选的sentinel。4.每一个发送参选请求的sentinel节点如果收到了超过一半的意向同意参选sentinel(也可能是自己),则确定该sentinel为leader;如果本回合持续了足够长的时间还未选出leader,则进入下一个回合。leader sentinel 确定之后,从master所有的slave中依据一定的规则选取一个作为新的master,告知其他slave连接这个新的master。4.Redis ClusterRedis 3.0 之后,节点之间通过去中心化的方式提供了完整的sharding,replication(复制机制仍复用原有机制,只是cluster具备感知主备的能力),failover解决方案,称为rediscluster。即,将proxy/sentinel 的工作融合到了普通的redis里面。4.1 拓扑结构一个redis cluster 由多个redis节点组成。不同节点组服务的数据无交集,即每一个节点组对应数据sharding的一个分片。节点组内分为主备两类节点,对应上面的master,slave节点,两者数据准实时一致,通过异步化的主备复制机制保证。一个节点组有且仅有一个master节点,同时有0个到多个slave节点。只有master节点对用户提供些服务,读服务可以由master或者slave提供。该示例下,key-value 数据全集被分成了5份,即5个slot(实际上redis一共有16384个slot,每个节点服务一部分slot)。A和B分别为两个master节点,对外提供数据的读写服务,分别负责1/2/3三个 slot 和4/5 2个slot。A,A1作为主备关系,构成一个节点组,它们之间用8.2主备复制小结所述的方式同步数据,所以A1作为A的slave节点,仍然持有 1/2/3 三个slot的数据。同理,B,B1 作为B的 slave也构成一个节点组。上述示例中的5个节点间,两两通过 redis cluster Bus 交互,相互交换如下关键信息:1.数据分片(slot)和节点的对应关系2.集群中每个节点可用信息3.集群结构(配置)发生变更时,通过一定的协议对配置信息达成一致。数据分片的迁移,故障发生时的主备切换决策,单点master的发现和其发生主备关系的变更等场景均会导致集群结构变化。4.publish/subscribe(发布/订阅)功能在cluster版本的内部实现所需要的交互信息。redis cluster bus 通过单独的端口进行连接,由于bus是节点之间的内部通信机制,交互的是节点序列化信息,而不是client到redis服务器的字符序列化以提升交互效率。redis cluster 是一个去中心化的分布式实现方案,客户端可以和集群中的任一节点连接,通过后文所述的交互流程,逐渐的获知全集群的数据分片映射关系。4.2 配置的一致性对一个去中心化的实现,集群的拓扑结构并不保存在单独的配置节点上,后者的引入同样会带来新的一致性问题。那么各自为政的节点之间如何就集群的拓扑结构达成一致,是redis cluster配置机制解决的问题。redis cluster 通过引入两个自增的epoch 变量来使得集群配置的各个节点达成最终一致。1.配置信息数据结构redis cluster 中的每一个节点(Node)内部都保存了集群的配置信息,这些信息存储在clusterState中:1.clusterState 记录了从集群中某个节点的视角来看的集群配置状态。2.currentEpoch 表示整个集群中的最大版本号,集群信息每变更一次,该版本号都会自增以保证每个信息的版本唯一。3.nodes 是一个列表,包含了本节点所知的集群所有的节点信息(clusterNode),其中也包含本节点自身。4.clusterNode 记录了每个节点的信息,其中比较关键是包含该信息的版本epoch,该版本信息的描述:该节点对应的数据分片(slot),当该节点为master时slave节点列表,当该节点为slave时对应的master节点。5.每个节点包含一个全局唯一的nodeId.6.当集群的数据分片信息发生变更,即数据分片在节点组之间迁移的时候,redis cluster仍然保持对外服务,在变迁的过程中,通过'分片迁移相关状态'的一组变量来管控迁移过程。7.当集群中某个master出现宕机时,redis cluster会自动发现并触发故障转移的操作,将宕机master的某个slave升级为master,这个过程中同样包含一组变量来控制故障转移的一系列过程。每个节点都保存着它的视角看来的集群结构,它描述了数据的分片方式,节点主备关系,并通过epoch作为版本号实现集群结构信息(配置)的一致性,同时也控制着数据迁移和故障迁移的过程。2.信息交互由于去中心化的架构下不存在统一的配置中心,各个节点对整个集群状态的认知来自于各个节点间的交互信息。在redis cluster中,这个信息交互通过redis cluster bus 来完成,后者端口独立。clusterMsg 的type 字段指明了消息的类型,配置信息的一致性达成主要依靠ping和pong两种类型的msg,两者除了type之外,其余字段信息语义一致,其消息体即上图所示的Gossip数据。每一个节点向其他所有节点较为频繁的周期性的发送ping消息的同时接收pong回应。在这些交互消息的gossip部分,包含了发送者节点(或者响应者节点)所知的集群其他节点信息,接收节点根据这些gossip信息更新自己对集群结构的认知。对于一个规模较大的集群,其中可能包含成千个节点,对于两两频繁交互的ping/pong包,每次都包含整个集群的结构信息将造成极大的网络负担。然后集群大多数时间结构稳定,即便发送全量数据,其中的大部分和接收节点已有的数据是相同的,这部分数据其实没有实际用处。作为优化,redis cluster 在每次ping/pong 包中,只包含全集群的部分节点信息,节点随机选取,以此控制网络流量。由于交互较为频繁,短时间的几次交互之后,集群状态以这样的gossip协议方式呗扩散到了集群中的所有节点。3.一致性达成当集群结构不发生变化的时候,集群中的各个节点通过gossip协议可以在几轮交互之后得知全集群的结构信息,且达到一致的状态。然而,故障转移,分片迁移等情况的发生会导致集群结构的变更,由于无统一的配置服务器,变更的信息只能靠各个节点自行协调,优先得知变更信息的节点利用epoch变量将自己的最新消息扩散到整个集群,达到最终一致。1.配置信息的clusterNode的epoch属性描述的粒度是单个节点,即某个节点的数据分片,主备信息版本。2.配置信息的clusterState 的currentEpoch属性的粒度是整个集群,它的存在用来辅助epoch自增的生成。由于currentEpoch信息也是维护在各个节点自身的,redis cluster在结构发生变更时,通过一定的时间窗口控制和更新规则保证每个节点看到的currentEpoch都是最新的。集群信息的更新遵循以下规则:1.当某个节点率先知道了信息变更时,这个节点将currentEpoch自增使之成为集群中的最大值,再用自增后的currentEpoch作为新的epoch版本2.当某个节点收到了比自己大的currentEpoch时,更新自己的currentEpoch值使之保持最新3.当收到的redis cluster bus 消息中某个节点信息的epoch值大于接收者自己内部的配置信息存储的值时,意味着自己的信息太旧了,此时将自己的映射信息更新为消息的内容。4.当收到redis cluster bus 消息中的某个节点信息未包含在接收节点的内部配置信息时,意味着接收者尚未意识到消息所指节点的存在,此时接收者直接将消息的信息添加到自己的内部配置信息中。上述规则保证了信息的更新始终是单向的,始终朝着epoch更大的信息收敛,同时epoch也随着每次配置变更时currentEpoch的自增而单向增加,确定了各节点信息更新的方向稳定。4.3 sharding不同节点分组服务于互相无交集的数据子集(分片,sharding)。同时,因为redis cluster 不存在单独的proxy和配置信息。1.数据分片redis cluster 将所有的数据划分为 16384 个分片(slot),每个分片负责其中的一部分。每一条数据(key-value)根据key值通过数据分布算法映射到16384个slot中的一个。数据分布算法为:slotId = crc16(key)%16384客户端根据slotId决定将请求路由到哪个redis节点。cluster不支持跨节点的单命令,如sinterstore,如果涉及的2个key对应的slot分布在不同的node上,则操作失败。通常key由具备一定业务含义的多个部分组成,有的表示表名,有的表示业务模型的id值。很多的业务场景下,不同的表的业务实体间存在一定的关系,例如商品交易摘要记录和商品详情记录,即便对于同一个商品,也会在redis中以不同的key 分成两条记录存储,通常需要在同一个命令中操作这2条记录。由于数据分布算法将key的内部组成作为一个黑盒,这2条记录有极大的可能分散到不同的节点上,阻碍单条命令以原子性的方式操作这2条关联性很强的记录。为此,redis引入了HashTag概念,使得数据分布算法可以根据key的某一部分进行计算,让相关的2条记录落到同一个数据分片。例如:1.某条商品交易记录的key的值为:producct_trade_{prod123}2.这个商品的详情记录的key的值为:product_detail_{prod123}redis会根据{}之间的子字符串作为数据分布算法的输入。2.客户端的路由redis cluster 的客户端比单机redis需要具备路由语义的识别能力,且具备一定的路由缓存能力。当一个client访问的key不在对应redis节点的slots中,redis返回给client一个moved命令,告知其正确的路由信息。从client收到moved响应,到再次向moved响应中指向的节点(假设为b)发送请求期间,redis cluster的数据分布可能又发生了变化,使得b仍然不是正确的节点,此时b会继续响应moved。client根据moved响应更新其内部的路由缓存信息,以便下次请求时直接能够路由到正确的节点,降低交互次数。当cluster处在数据重分布(目前由人工触发)过程中,可以通过ask命令控制客户端的路由。slot 1 打算迁移到新节点上,迁移过程中,如果客户端访问已经完成迁移的key,节点将相应ask告知客户端向目标节点重试。ask命令和moved命令不同的语义在于,后者会更新client数据的分布,前者只是本条操作重定向到新的节点,后续的相同slot操作仍然路由到旧节点。迁移的过程可能持续一段时间,这段时间某个slot的数据同时在新旧两个节点上各分布了一部分,由于move操作会使得客户端的路由缓存变更,如果新旧两个节点对于迁移中的slot上所有不在自己节点的key都回应moved信息,客户端的路由缓存信息可能会频繁变动。故引入ask类型消息,将重定向和路有缓存更新分离。3.分片的迁移在一个稳定的redis cluster下,每一个slot对应的节点是确定的。但是在某些情况下,节点和分片的对应关系需要发生变更:1.新的节点作为master加入2.某个节点分组需要下线3.负载不均需要调整slot分布此时,需要进行分片的迁移。分片的迁移的触发和过程控制由外部系统完成,redis cluster只提供迁移过程中需要的原语提供外部系统调用。这些原语主要包括:1.节点迁移状态设置:迁移前标记源/目标节点2.key 迁移的原子化命令:迁移的具体步骤将slot 1从节点a迁移到节点b:1.向节点b发送状态变更命令,将b的对应slot状态置为importing2.向节点a发送状态变更命令,将a的对应slot状态置为migrate3.针对a的slot上的所有key,分别向a发送migrate命令,告知a将对应的key的数据迁移到b当节点a的状态置为了migrating后,表示对应的slot正在从a迁出,为保证该slot的数据一致性,a此时对slot内部数据提供读写服务的行为和通常状态下有所区别,对于某个迁移中的slot:1.如果客户端访问的key尚未迁出,则正常的处理该key2.如果key已经迁出或者根据不存在该key,则回复客户端ask信息让其跳转到b执行当节点b的状态置为了importing后,表示对应的slot正向b迁入中,即使b仍然能对外提供该slot的读写服务,但行为和通常状态下也有所区别:1.当来自客户端的正常访问不是从ask跳转而来时,说明客户端尚不知迁移正在进行,很可能操作了一个目前尚未迁移完成的正处于a上的key,如果此时这个key在a上已经被修改过了,那么b和a的修改值将在未来发生冲突。2.所以对该slot上的所有非ask跳转而来的操作,b不会进行处理,而是通过moved命令让客户端跳转至a操作。这样的状态控制保证了同一个key在迁移之前总是在源节点执行,迁移后总是在目标节点执行,杜绝了两边同时写导致值冲突的可能性。且迁移过程中新增的key总是在目标节点进行,源节点不会再由新增的key,使得迁移过程时间有界,可以在确定的某个时刻结束。剩下的问题就在于某个key的迁移过程中数据的一致性问题了。单个key的迁移过程被抽象为了原子化的migrate命令,这个命令完成了数据传输到b,等待b接收完成,在a上删除该key的动作。从前述章节得知,redis单机对于命令的处理是单线程的,同一个key在执行migrate的过程中不会处理该key的其他操作,从而保证了迁移的原子性。a和b各自的slave通过8.2主备复制小节所述的方式分别同步新老master节点的增删数据。当slot的所有key从a上迁移至b上之后,客户端通过cluster setslot命令设置b的分片信息,使之包含迁移的slot。设置的过程中会自增一个新的epoch,它大于当前集群的所有epoch值,根据后者随着前述小节的配置一致性策略,这个新的配置信息会传播到集群中的其他每一个节点,完成分片节点映射关系的更新。4.4 failover同 sentinel 一样,redis cluster 也具备一套完成的节点故障发现,故障状态一致性保证,主备切换机制。1.failover的状态变迁failover的完成过程如下:1.故障发现:当某个master宕机时,宕机事件如何被集群其他节点感知2.故障确认:多个节点就某个master是否宕机如何达成一致3.slave选举:集群确认了某个master确实宕机后,如何将它的slave升级成新的master节点;如果原master有多个slave,选择升级谁4.集群结构变更:选举成功的slave升级为新master后如何让全集群的其他节点知道以及更新它们的集群结构信息2.故障发现redis cluster的节点通过redis cluster bus 两两周期性的进行ping/pong交互,当某个节点宕机时,其他发向它的pong消息将无法及时响应,当pong的响应超过一定时间(NODE_TIMEOUT)未收到,则发送者认为接收节点故障,将其置为PFAIL状态(Possible Fail),后续通过gossip发出的ping/pong消息中,这个节点的pfail状态将会传播到集群的其他节点。redis cluster的节点两两通过tcp保持redis cluster bus连接,当对端无pong回复时,可能是对端节点故障,也可能只是tcp连接断开。如果是后者导致的响应超时,将对端节点置为pfail状态并散播出去将会产生误报,虽然误报消息同样会因为其他节点的连接正常而被忽略,但是这样的误报本可以避免的。redis cluster 通过预重试机制排除此类误报:当 NODE_TIMEOUT/2 过去了还未收到ping对应的pong消息,则重建连接重发ping消息,如果对端正常,pong会在很短的时间内抵达。3.故障确认对于网络分割的情况,某个节点(假设为b)并没有故障,但是可能和a无法连接,但是和c/d等其他节点可以正常连通,此时只会有a将b标记为pfail态,而其他节点认为b正常。此时a和c/d等其他节点信息不一致。redis cluster 通过故障确认协议达成一致。集群中的每个节点同时也是gossip的接收者,a也会收到来自其他节点的gossip消息,被告知b节点是否处于pfail状态,a持续的通过gossip收集来自不同节点的关于b的信息。当a收到来自其他master节点的b的ptail达到一定数量后,会将b的ptail状态升级为fail状态,表示b已经确认为故障态,后续发起slave选举流程。当a收到了超过一半的master节点(包括a自己,如果a也是一个master的话)报告来自b的pfail信息,那么a将会认为pfail足够了,则将b的状态置为fail,将b已经fail的消息广播到其他可达的节点。4.slave选举上例中,如果b是a的master,且b已经被集群公认为是fail状态了,那么a发起竞选,期望替代b成为新的master。如果b有多个slave a/e/f 都认识到了b处于fail态了,a/e/f 可能会同时发起竞选,当b的slave数量 >=3个时,很有可能这些slave票数太平均导致于无法选出胜者,此时不得不再次发起竞选,导致竞选轮数过多延误b的新master选出,延迟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 带入到FAILOVER_AUTH_REQUEST消息中,master收到FAILOVER_AUTH_REQUEST消息后,如果发现对于本轮(本epoch)自己尚未投过票,则回复同意,否则拒绝。5.结构变更通知当slave收到超过半数的master的同意回复时,该slave顺利的替代b成为新的master,此时它会以最新的epoch通过pong消息广播自己成为master的消息,让集群中其他的节点尽快的更新拓扑信息。当b恢复可用后,它首先仍然认为自己是master,但逐渐的通过gossip协议得知a已经替代自己的事实之后降级为a的slave。4.5 可用性和性能1.redis cluster 的读写分离对于有着读写分离需求的场景,应用对于某些读的请求允许舍弃一定的数据一致性,以换取跟高的读吞吐量。此时希望将读的请求交由slave处理以分担master的压力。默认情况下,数据分片映射关系中,某个slot对应的节点一定是master节点,客户端通过moved消息得知集群的拓扑也只会将请求路由到各个master中,即便客户端将读请求直接发送到slave上,后者也会回复moved到master的响应。为此,redis cluster 引入了readonly命令。客户端向slave发送该命令后,slave对于读操作,将不再moved回master而是直接处理,这称为slave的readonly模式。通过readwrite命令,可将slave的readonly模式重置。2.master单点保护假设a,b这2个master分别有1,2个自己的slave,假设a1发生宕机了,此时a成为单点,一旦a再次宕机,将造成不可用。此时redis cluster会将b的某个slave(假设是b1)副本进行迁移,让其变成a的slave。这使得集群中的每个master至少有一个slave,即高可用状态。这样一来,就能只需要保持 2*master+1 个节点,就可以在任一节点宕机后仍然能够自动的维持高可用状态,称为master单点保护。如果不具备此功能,则需要维持 3*master 个节点,而其中 master-1 个slave节点在可用性视角看来都是浪费的。

8.深入分布式缓存:从原理到实践 --- 分布式Redis相关推荐

  1. 蚂蚁京东新浪10位架构师424页佳作深入分布式缓存从原理到实践

    前言 最近刷到了一句耐人寻味的话,"解决雪崩问题的最好办法是不发生雪崩". 不论是在硅谷互联网公司里还是在国内的互联网平台上,曾多次遇到过海量规模的交易瞬间吞噬平台的悲惨故事. 核 ...

  2. 分布式经典书籍--深入分布式缓存 从原理到实践

  3. 40张图看懂分布式追踪系统原理及实践

    前言 在微服务架构中,一次请求往往涉及到多个模块,多个中间件,多台机器的相互协作才能完成.这一系列调用请求中,有些是串行的,有些是并行的,那么如何确定这个请求背后调用了哪些应用,哪些模块,哪些节点及调 ...

  4. 传递给系统调用的数据区域太小怎么解决_40张图看懂分布式追踪系统原理及实践...

    作 者:码海 原文链接:https://mp.weixin.qq.com/s/U-8ttlVCfYtjEPOWKBHONA 前言 在微服务架构中,一次请求往往涉及到多个模块,多个中间件,多台机器的相互 ...

  5. 厉害!40 张图看懂分布式追踪系统原理及实践

    作者 | 码海 来源 | 码海 在微服务架构中,一次请求往往涉及到多个模块,多个中间件,多台机器的相互协作才能完成. 这一系列调用请求中,有些是串行的,有些是并行的,那么如何确定这个请求背后调用了哪些 ...

  6. 分布式缓存存储算法与实践思考

    最近遇到一个问题,可能很多人也遇到过:由于业务量的增长,缓存节点个数不够用了.现在的Redis-Cluster直接就加个节点就解决了,但是之前Redis-Cluster不稳定时,我们并不敢用这个,而是 ...

  7. 技术交流:分布式缓存的原理及应用

    文章目录 分布式缓存的原理及应用 缓存(进程级缓存与分布式缓存) 分布式缓存 Ehcache的原理及应用 Ehcache的原理 Ehcache的特点 Ehcache的架构 Ehcache的存储方式 E ...

  8. 这些图让你看懂分布式追踪系统原理及实践

    前言 在微服务架构中,一次请求往往涉及到多个模块,多个中间件,多台机器的相互协作才能完成.这一系列调用请求中,有些是串行的,有些是并行的,那么如何确定这个请求背后调用了哪些应用,哪些模块,哪些节点及调 ...

  9. [读书笔记]大型分布式网站架构设计与实践.分布式缓存

    前言:本书是对分布式系统架构涉及到的相关技术的一本科普书籍.由于很难作为开发参考,只能但求了解.所以通篇浅读,对分布式系统进行大致的了解.因为写的非常好,感觉非常有意思,自己也做不出总结.所谓的读书笔 ...

  10. 高性能分布式缓存Redis--- 缓存原理和设计 --- 持续更新

    高性能分布式缓存Redis全系列文章主目录(进不去说明还没写完)https://blog.csdn.net/grd_java/article/details/124192973 本文只是整个系列笔记的 ...

最新文章

  1. C语言实现的Web服务器
  2. 直播预告 | 基于多智能体交流游戏的零资源机器翻译
  3. swt 键盘事件ctrl+c_跑Python的键盘可以很强大
  4. 笔记本中美化代码的方法
  5. 取文字_把这4个字母输入word,会得到一段神秘文字,承载着一段历史
  6. python生成文件夹并向文件夹写文件_python - 文件练习生成100个MAC地址写入文件
  7. 数据库概述 数据库入门
  8. java 覆盖文件_java复制文件(如果目标文件存在,是否覆盖)
  9. 对象存储osd以及存储分类
  10. 苹果手机升级后开不了机怎么办_iPhone8突然黑屏开不了机怎么办?西安苹果售后维修点教你这样解决...
  11. 产品沉思录精选:西方人读孔子-有关德、礼及生死
  12. 详解EC11编码器示波器波形图
  13. su:密码正确,但权限被拒绝
  14. 【GPGPU编程】GPGPU架构剖析之谓词寄存器
  15. 与新晋图灵奖得主的虚拟对话
  16. ppt版的pdf文件,被加密,知晓密码,想得到去水印后的ppt
  17. H.323-SIP信令网关的实现
  18. elasticsearch 安装拼音分词
  19. 易语言和python混合编程_关于易语言与Python的一点想法
  20. 从键盘分别输入年、月、日判断这一天是当年的第几天

热门文章

  1. python第二十二课——list函数
  2. 模拟生命_吸烟致癌?
  3. javascript 计算后 无聊的小数点处理
  4. Jquery中extend的理解以及常见用法
  5. 让 Windows 的 R 用上 CUDA
  6. 【R爬虫-1】BBC Learning English
  7. CSDN发布:AI技术人才成长路线图
  8. Tomcat部署到CentOS7
  9. Java 开发必须掌握的线上问题排查命令
  10. codevs1026 逃跑的拉尔夫