1集群从单主多从复制架构到现在的分布式架构的意义主要有以下几个维度:业务追求更高QPS数据量的ScaleUp已经无法满足,超过了单机限制,考虑ScaleOut分布式网络流量业务流量超过服务器网卡上限。考虑到分布式离线计算需要在中间环节进行缓冲。2.Meet节点之间相互通信的基础是有一定的频率和规则。CLUSTERMEET命令用于连接不同的开启集群支持的Redis节点进入工作集群。2.1基本思想每个节点默认互不信任,被认为是未知节点,这样在系统管理错误或地址修改的情况下,多个不同的集群节点混入一个集群的可能性很小。因此,为了让给定节点接收另一个节点进入构成RedisCluster的节点列表,只有两种方式:系统管理员发送CLUSTERMEET命令强制一个节点与另一个节点相遇已知节点发送asave八卦部分的节点列表,包含未知节点。如果接收节点已经信任发送节点作为已知节点,它会处理八卦部分并向未知节点发送握手消息。RedisCluster需要形成一个完整的网络(每个节点都连接到每个其他节点),但是为了创建集群,并不需要发送形成网络所需的所有CLUSTERMEET命令。发送CLUSTERMEET消息以便每个节点都可以到达每个其他节点只需要通过已知的节点链。由于八卦消息是在心跳包中交换的,因此将创建节点之间的丢失链接。因此,如果我们通过CLUSTERMEET连接节点A和B,并且B和C有一个链接,那么节点A和C将找到他们握手并创建链接的方式。2.2案例假设一个集群有A、B、C、D四个节点,你只能向A发送如下一组命令:CLUSTERMEETB-ipB-portCLUSTERMEETC-ipC-portCLUSTERMEETD-ipD-port因为A知道并且被所有其他节点,它会在发送的心跳包中包含gossip部分,这将允许每个其他节点相互建立链接,即使集群很大,也可以在几秒钟内形成一个完整的网络。CLUSTERMEETs不需要相互执行,即如果一个命令被发送到A加入B,那么就没有必要也向B发送一个命令加入A。2.3实现细节:MEET和PING数据包当一个给定的节点收到一个MEET消息,命令中指定的节点仍然不知道它发送了命令,所以为了让节点强制接收命令的节点接受它作为可信节点,它发送MEET数据包而不是PING数据包。两个数据包具有相同的格式,但MEET强制接收数据包的节点确认发送数据包的节点是可信的。3Assignmentslots16384个slots被节点平均分配管理,每个节点只读写自己负责的slots。每个节点相互通信,因此每个节点都知道其他节点管理的槽的范围。客户端和分配的插槽4.集群扩展集群的扩展包括新节点的添加和旧节点的退出。4.1添加新节点Redis集群中添加新节点的主要步骤如下:准备新节点以集群模式启动Redis节点加入集群通过与集群中任意节点握手加入新节点迁移slot到新节点,并分配给新节点负责slot,并将slot对应的数据迁移给它。由于Redis采用Gossip协议,新节点可以与任何现有的集群节点握手。一段时间后,整个集群就会知道添加了一个新节点。4.1.1案例添加一个新的节点6385到下面的集群中。由于负载均衡的需要,加入后的4个节点各负责4096个槽位,但集群中每个原有节点负责5462个槽位,所以节点6379、6380、6381需要将1366个槽位迁移到新节点6385上插槽。Redis集群没有自动负载均衡的工具。完全由用户指定从哪个节点迁移到哪个节点的槽数。数据迁移流程图迁移key可以通过pipeline进行批量迁移。对于扩容,原理已经很明确了。至于具体操作,网上有很多。至于缩容,先手动完成数据迁移,再关闭redis。收缩时,如果离线节点有责任槽需要迁移到其他节点,则使用clusterforget命令让集群中的所有节点忘记离线。Node5Clientrouting5.1movedredirection每个节点在RedisCluster中通信共享slot和slot集群中对应节点的关系。客户端向RedisCluster的任意节点发送命令。收到命令的节点计算自己的时隙和对应的节点。1.如果保存数据的slot分配给了当前节点,则执行slot中的命令,并将命令执行结果返回给Client2.如果保存数据的slot不在当前节点的管理范围内,向客户端返回移动的重定向异常。3、客户端接收节点返回的结果,如果是移动异常,则从移动异常中获取目标节点。4、客户端向目标节点发送命令,并获取命令执行结果。客户端不会自动寻找目标节点执行命令,需要执行两次。5.2请求重定向由于集群扩容,需要进行数据迁移。当客户端访问一个key时,节点告诉客户端key在源节点,然后访问源节点,发现key已经迁移到目标节点,返回一个ask。客户端向目标节点发送命令,目标节点中的槽已经迁移到其他节点。目标节点将向客户端返回一个请求。客户端向新节点发送Asking命令,然后向新节点发送命令。新节点执行命令,命令执行结果返回给客户端为什么不能简单的用MOVED重定向呢?因为虽然MOVED意味着我们认为hashslot是由另一个节点永久提供的,下一个查询应该针对指定的节点进行尝试,而ASK意味着只将下一个查询发送到指定的节点。这是必需的,因为对哈希槽的下一个查询可能是针对仍在A中的键,因此我们总是希望客户端先尝试A,然后在需要时尝试B。由于16384个可用哈希槽中只有一个出现,因此对集群的性能影响是可以接受的。5.3movedV.Saskisclientredirectionmoved:slothasbeendeterminedtomoveask:slotisstillmigration将Clusterslots的结果映射到本地,为每个节点创建一个JedisPool,然后就可以读写数据了。注意事项每个JedisPool都缓存了slot和node节点的关系以及key和slot的关系:将key用CRC16规则散列后用16383取余,结果就是slot。JedisCluster在启动的时候就已经知道了key、slot和node的关系,可以找到目标节点。JedisCluster向目标节点发送命令,目标节点直接响应JedisCluster。如果JedisCluster连接到目标节点时出错,那么JedisCluster就会知道连接的节点是错误的节点。此时JedisCluster会向随机节点发送命令,随机节点会返回移动异常给JedisCluster。JedisCluster会重新初始化slot和node节点之间的缓存关系,然后向新的目标节点发送命令。目标命令执行命令并响应JedisCluster。如果命令发送超过5次,将抛出异常“Toomanyclusterredirection!”基本图综合图6批量操作mget和mset必须在同一个槽中。RedisCluster不同于Redis单节点,更不同于Sentinel监控的主从模式。主要原因是集群自动分片,将一个键映射到16384个槽中的一个,这些槽分布在多个部分。因此,操作多个key的命令必须保证所有key映射到同一个slot,避免跨slot执行错误。单个集群节点只服务于一组专用密钥,如果向服务器请求命令,则只能获得服务器上密钥的对应结果。一个非常简单的例子是执行KEYS命令。向集群中的某个节点下发命令时,只能获取该节点拥有的key,不能获取集群中的所有key。要获取集群中的所有密钥,必须从集群的所有主节点获取所有密钥。对于redis集群中分散在不同节点的数据,如何更高效地批量获取数据呢?6.1Serialmget定义一个for循环,遍历所有key,从所有Redis节点获取value并汇总。简单,但效率不高,需要n倍的网络时间。6.2SerialI/O优化serialmget,在client本地做cohesion,对每个key进行hash,然后取余,知道key对应的slot已经缓存了slot和node的对应关系,然后按key的节点进行分组,设置子集,然后使用管道向对应的节点发送命令,这需要节点时间的网络时间,大大降低了网络时间开销。6.3并行I/O优化串行IO。对key进行分组后,根据节点个数启动相应个数的线程,按照多线程模式并行向node节点请求数据。只需要一个网络时间。6.4hash_tag不做任何改动,是否可以像单机一样均匀的分散在每个节点上,一次IO取出所有的key?hash-tag提供了这样一个功能:如果将上面的key改成下面的,即用花括号括起来的相同内容,保证所有key只向一个节点请求数据,这样执行类似的mget命令只需要去一个节点获取数据,效率更高。6.5选型对比第一种方法使用多线程解决批处理问题,减少带宽延迟,提高效率。这种方法如前所述简单方便(我们目前批量操作的种类很多),而且很有效。但问题更明显。少量的批处理操作就足够了。搜狐的cachecloud采用的是第二点,先获取keyslot,然后分节点pipeline操作。这种方法优于第一种方法。7Failover7.1故障发现RedisCluster通过ping/pong消息实现故障发现:不需要sentinel。Ping/pong不仅可以传输节点和槽的相应消息,还可以传输其他状态,如:节点主从状态,节点失效等故障发现都是通过这种模式实现的,分为主观下线和客观下线:7.1.1主观下线定义一个节点认为另一个节点不可用,只代表一个节点对另一个节点的判断,不代表所有节点的认知。进程node-1定期向node-2发送ping消息。如果发送成功,说明node-2运行正常。node-2会回复一个PONG报文给node-1,如果node-1和node-2的最后一次通信时间发送失败,则node-1和node-2的连接判断异常。在下一个定时任务周期,它仍然会向节点2发送ping报文,如果node-1发现与node-2的最后一次通信时间超过node-timeout,则将节点2标记为pfail状态7.1.2目标下线定义当超过半数持有槽的主节点主观上标记一个节点下线。可以保证审判的公正性。在集群模式下,只有主节点(master)有读写权限和集群槽维护权限,从节点(slave)只有复制权限。流程1.一个节点收到其他节点发送的ping报文。如果收到的ping报文中包含其他pfail节点,该节点会将主观下线报文的内容添加到自己的故障列表中,其中包含2。当前节点将主观下线报文的内容添加到自己的故障列表中后,将尝试在故障节点上进行客观的离线操作。7.2节点接收故障恢复当通知其主节点客观下线时,将进行故障恢复。资格检查检查从节点的资格,只有难于检查的从节点才能启动故障恢复。每个从节点检查与故障主节点的断开连接时间是否超过cluster-node-timeout*cluster-slave-validity-factornumber,取消资格cluster-node-timeout默认为15秒,cluster-slave-validity-factor默认为10。如果两个参数都使用默认值,则每个节点检查与故障主节点的断开连接时间,如果超过150秒,那么这个节点就没有成为主节点的可能。准备选举时间,让offset最大的slave节点优先成为master节点。Electionvotingvotesformultipleelectedslavenodestoelectanewmasternode.更换主节点当前从节点取消复制并成为分离节点。(slaveofnoone)执行clusterdelslot取消负责故障master节点的slot,执行clusteraddslot将这些slot分配给自己。向集群广播自己的pong消息,表示已经更换了故障的slave节点。8开发维护FAQ8.1集群完整性cluster-require-full-coverage默认为yes,即集群中所有节点都在服务中且有16384个槽位可用,集群将提供服务保证集群完整性。当某个节点发生故障或正在故障转移过程中时,会提示:(error)CLUSTERDOWN集群宕机,但大多数服务无法容忍。建议设置cluster-require-full-coverage为no8.2RedisCluster节点间的带宽消耗定期交换Gossip消息,并做一些心跳检测官方建议RedisCluster节点数不要超过1000个。当集群中节点过多时,会产生不可忽视的带宽消耗。当超过cluster-node-timeout/2时,将直接发送PING消息。数据量:slots槽数组(2kb空间)和整个集群的1/10状态数据(10个节点状态数据约1kb)节点部署机器规模:集群中分布的机器越多,节点数量越均匀除以每台机器,集群中的整体可用带宽就越高。Bandwidthoptimizationavoidstheuseof'big'clusters:avoidusingoneclusterformultipleservices,largeservicescanusemultipleclusterscluster-node-timeout:带宽和故障转移速度的平衡应该尽可能均匀地分配到多台机器上:保证高可用和带宽其他节点会订阅消息,所以节点的带宽开销会非常高。Publish在集群中的每个节点上广播,带宽密集型方案单独“走”一组redissentinel。就是为target的几个节点搭建redissentinel,在这里面实现广播。8.4集群偏斜分布式数据库存在偏斜问题是很常见的。集群偏斜是每个节点使用内存的不一致。数据倾斜的原因:1、节点和槽位分布不均匀。如果使用redis-trib.rb工具搭建集群,出现这种情况的机会不多。redis-trib.rbinfoip:port查看节点、slot、key值分布redis-trib.rbrebalanceip:portforbalance(慎用)2.不同slot对应的key值个数差异很大values个数3.包含bigkey:比如一个大字符串,百万元素的hash,set等从节点中:redis-cli--bigkeys优化:优化数据结构4.不一致的内存相关配置hash-max-ziplist-value:当满足一定条件时,hash可以使用ziplistset-max-intset-entries:当满足一定条件时,set可以使用intset。集群中有多个节点。当部分节点配置了以上两种优化时,另外一些节点没有配置以上两种优化。集群中保存hash或set时,节点数据会不均匀。优化:定时检查配置一致性对于重要的key,会出现热点问题。优化以避免大键热键。不要使用hash_tag。当一致性不高时,可以使用本地缓存+MQ(消息队列)9.读写分离和只读连接。在集群模式下,从节点不接受任何读写请求。当对slave节点执行读请求时,重定向到负责slot的master节点。只读命令可以读作:连接级命令。当连接断开时,需要重新执行readonlyrediscluster。默认的slave也是不可读的。如果你想读,你需要Executereadonly,就是这样。读写分离:比较复杂(成本高,尽量不要用)同问题:复制延迟,读取过期数据,修改clientfromnodefailure:clusterslaves{nodeId}10集群好坏。limited:比如mget,mset必须在一个slotkeytransaction和lua支持limited:操作的key必须在一个nodekey是数据分区的最小粒度:不支持bigkey分区不支持多数据库:inclustermodethereonlyonedb0replicationonlySupportonelayer:RedisCluster不支持树状复制结构以满足容量和性能的可扩展性。许多业务是“不需要”的,大多数时候客户端性能会“下降”。命令不能跨节点使用:mget、keys、Scan、flush、sinter等Lua和事务不能跨节点使用。客户端维护比较复杂:SDK和应用自身消耗(比如连接池多)。在很多场景下,RedisSentinel就足够了。参考https://www.slideshare.net/iammutex/redis-clusterhttps://zhuanlan.zhihu.com/p/105569485https://sunweiguo.github.io/2019/02/01/redis/Redis-Cluster%E7%90%86%E8%AE%BA%E8%AF%A6%E8%A7%A3/https://redis.io/topics/cluster-spechttp://trumandu.github.io/2016/05/09/RedisCluster%E6%9E%84%E5%BB%BA%E6%89%B9%E9%87%8F%E6%93%8D%E4%BD%9C%E6%8E%A2%E8%AE%A8/
