概述

  • Redis集群是Redis实现分布式数据库的解决方案,通过数据分片将一个完整数据库的数据分散到集群中的各个节点,即整个集群构成一个完整的数据库,集群中各个节点负责处理其中一部分数据。这样可以通过增加集群节点的方式来支持存储更多的数据,解决单机模式的Redis在存储海量数据时的瓶颈和性能问题。
  • Redis集群是在Redis的基础上实现的集群,即对于集群的每个节点分为两层:集群层+Redis层,其中Redis层就是一个普通Redis,进行数据存取;而集群层是负责槽和键的映射管理,与集群其他节点的协作通信共同提供一个完整的数据。
  • 在设计层面主要围绕“槽”来展开,即每个槽存放部分键值对数据,所有槽构成所有的键值对数据;每个集群节点包含0个或多个槽,每个槽可以重分片,即从一个节点转移到另外一个节点;集群中的每个处理槽的节点可以包含从节点,实现高可用。

数据库

  • 在介绍Redis集群之前,需要明确的是:集群模式与单点模式不一样,集群模式只有一个数据库,即只使用0号数据库,而单点模式默认为16个数据库。故在之后的讨论中,数据库都是指0号数据库,在集群中不存在其他数据库。

集群

  • 默认情况下集群的节点都是以单机模式执行的,如果要搭建Redis集群,在首先需要将redis.conf配置文件的cluster-enabled设置为yes,如下:
# 去掉以下注释则打开了集群开关# cluster-enabled yes
  • 打开集群开关之后,则启动该Redis实例则是通过集群模式启动的。
  • 刚开始启动集群只有一个节点,如果需要增加其他节点到该集群,则首先也是修改该新节点的redis.conf打开集群模式开关,然后在启动该新节点。启动之后,这个新节点是在一个自身独立的集群中的,故需要在第一个启动的集群节点的控制台执行以下命令来将该新节点加入进来:其ip和port为这个新节点的ip和port。
CLUSTER MEET
  • 启动到加入过程如下:(图片均引自黄建宏的《Redis设计与实现》)



redis 分配用户 redis分层_客户端


  • 在内部实现中是通过第一个加入集群的节点通过与新节点握手来将这个新节点添加到这个集群的,过程如下:


redis 分配用户 redis分层_redis 分配用户_02


槽:数据分片

  • Redis集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分成16384个槽。集群中的每个键属于这16384个槽中的其中一个,每个集群节点可以包含0个到最多16384个槽。

槽的分配与集群上线

  • 槽的分配是指将这16384个槽分配给集群中的节点处理,即明确集群中的每个节点分别处理哪几个槽。

集群上线

  • 集群上线使用:只有这16384个槽都分配给了节点处理,整个集群才能上线使用,不能存在某个槽没有节点在处理的情况发生,因为这样会造成针对该槽对应的键值对数据的请求无法处理。

槽的分配

  • 槽的分配需要在节点的控制台通过REDIS ADDSLOTS命令来为每个节点添加处理的槽:如在某个节点的控制台执行:
127.0.0.1:6379> REDIS ADDSLOTS 0 1 2 3 4 ... 500
  • 表示将0到500的槽分给这个节点处理。之后可以通过CLUSTER INFO来查看整个集群的信息。
  • 在每个集群节点内部会维护一个大小为16384的二进制数组来记录当前节点处理哪些槽,下标为n的值为1表示第n个槽由该节点处理,0表示不是由该节点处理,复杂度为O(1),如下:


redis 分配用户 redis分层_数据_03


集群节点的槽的相互通知

  • 每个集群节点会通过消息的方式告知其他节点自己负责的槽,然后接收到这个信息的节点会从自己内部维护的其他节点集合中,即clusterNode.nodes集合,找到这个节点,然后将该槽信息关联到这个节点,从而每个节点在内部都可以获取其他节点的槽信息。过程如下:


redis 分配用户 redis分层_客户端_04


键值对请求的处理

  • Redis客户端跟普通Redis客户端一样,可以对某个key执行SET,GET等请求,处理过程主要包括首先在根据key计算对应的槽,这个也是集群层上操作,然后是根据槽定位处理这个请求的Redis,即Redis层的存取操作。具体过程如下:
  1. 客户端首先往所连上的节点发送该key的写请求,节点接收到给请求,计算key对应的槽是否在自己所分配的槽集合中,即查询clusterNode.slots,如果存在则处理该请求;
  2. 否则查询clusterState.slots获取该key对应的槽所在的节点的ip和port,然后给客户端返回:MOVED ip port,指引客户端重试该节点。对于单点模式的客户端,如redis-cli没有加-c,则只是展示该信息而不会自动重定向到该正确节点;而对于集群模式客户端,则客户端会自动重试该节点,最终成功执行该请求。
  • 可以在客户端通过以下命令计算某个键属于哪个槽:
CLUSTER KEYSLOT key

重新分片

  • 将任意数量的已经分配给某个节点的槽重新迁移分配给另外一个节点,其中迁移的数据包括槽和槽包含的键值对数据。
  • 重新分片支持在线操作,即在执行迁移过程中,集群可以继续接收客户端的请求。如果客户端请求的key对应的槽刚好在迁移,则客户端首先访问源节点,如果源节点存在,即还没有迁移该key,则源节点处理该key的请求,否则返回一个ASK错误,指引客户端重试迁移的目标节点。客户端先向目标节点发送一个ASKING请求,然后接着在发送实际的对该key的请求,ASKING的主要作用是让目标节点知道这个请求是源节点转发过来的,故破例在这个正在迁移的槽查找并处理该key的请求。关于是否自动执行,客户端对应ASK错误的处理与MOVED类似。

适用场景

  • 重新分片通常发生在对集群进行节点增删的时候,如果是新增节点,则从集群中已经存在的某个节点分离出一些槽给这个新的节点,如果是删除节点,则是将该被删除节点的槽转移到其他节点。

操作方法

  • 在操作上,主要通过CLUSTER SETSLOT < i >(第i个槽) IMPORTING < source_id > (迁出在源节点客户端执行)/ MIGRATING < target_id >(迁入在目标节点客户端执行)等命令来执行。以上操作在内部通过redis-trib脚本来完成。

集群节点的主从复制

  • 集群的从节点主要用来在主节点故障时替换主节点,从而实现集群节点的高可用。

主从复制

  • 可以为集群中的每个节点都设置一个或多个从节点,跟普通主从复制一样。当该主节点下线时,可以从从节点中选择一个从节点替代主节点进行处理其所管理的槽的key的请求,其他从节点转为同步这个新的主节点。
  • 配置从节点的方式为:首先将该从节点加入到该集群中,具体为通过在集群中的某个节点执行:CLUSTER MEET命令;然后在从节点对应的客户端中执行:
127.0.0.1:6380> CLUSTER REPLICATE < node_id >
  • 其中node_id为主节点,然后就会进行数据同步了,数据同步也是基于SLAVE OF来实现的。该从节点的集群状态也从REDIS_NODE_MASTER转为了REDIS_NODE_SLAVE。同时一个节点变成某个节点的从节点会以消息的形式通知集群中的其他节点,这样集群中的所有节点都知道该节点同步另外一个节点。

故障恢复

  • 故障检测与转移:集群中的节点都会定期(每秒一次选出五个节点,给其中最久没有发送过PING的节点发送)给其他节点发送心跳PING,以此来检测其他节点是否存活,如果在给定的时间内接收PING的节点没有响应PONG,则发送PING的节点在自身clusterState.nodes中将该节点对应的node标记为疑似下线PFAIL,同时集群中的节点会相互之间发送关于集群中节点的状态信息,故可以将该疑似下线的节点告知其他节点。如果集群中超过半数的处理槽的主节点认为某个节点疑似下线了,则会将这个节点标记为已下线FAIL。过程如下:7001发现7000下线FAIL了。


redis 分配用户 redis分层_redis 分配用户_05


  • 之后就开始从该下线主节点的多个从节点选举一个从节点来替代该下线主节点成为新的主节点。
  • 选举过程与哨兵Leader类似,都是各个从节点向集群其他主节点广播信息,要求给自己投票,每个主节点收到信息后,如果自己还没投票给任何一个从节点,则给这个最先请求的从节点投票;如果某个从节点发现自己的票数超过了集群所有主节点个数的一半,则该从节点成为新的主节点,这个新的主节点向集群其他节点广播一个PONG消息,通知其他节点自己成为新的主节点。