Redis - Cluster与集群

Posted on By Guanzhou Song

Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。

Redis 集群不支持那些需要同时处理多个键的 Redis 命令

因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的错误。

Redis 集群通过分区(partition)来提供一定程度的可用性(availability)

即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

Redis 集群提供了以下两个好处:

  • 将数据自动切分(split)到多个节点的能力。

  • 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。

但是Redis仍存在两类问题。

1.单机性能不足问题

  • 每个redis实例存有全部数据,当数据量特别大,超过了单机内存容量的时候无法处理。

  • 虽然sentinal能将读操作分给从机处理实现部分负载均衡功能,若并发写压力特别高,主服务器也会承受不住。

  • 实现sentinal后,客户端不再直接连接特定redis实例,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现。存在流量瓶颈问题。

为了解决这三个问题,Redis cluster提供在多个Redis间节点间共享数据的程序集。

redis cluster提供了数据分片功能,自动分割数据到不同的节点上。每个redis实例不再储存全部数据。

需要注意的是,Redis cluster并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.

2.可用性问题。

虽然sentinel有效的解决了故障转移的问题,也解决了主节点下线客户端无法识别新的可用节点的问题

但是如果是从节点下线了,sentinel是不会对其进行故障转移的,并且连接从节点的客户端也无法获取到新的可用从节点。

与之不同的是,Redis cluster根据客户端请求的不同,直接请求储存了对应数据的redis实例,而不是经由一个统一的负载均衡器实现负载均衡。

Sentinal的监控和自动Failover能力由cluster承担。

数据分片

Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现

一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:

  • 节点 A 负责处理 0 号至 5500 号哈希槽。

  • 节点 B 负责处理 5501 号至 11000 号哈希槽。

  • 节点 C 负责处理 11001 号至 16384 号哈希槽。

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

现在想设置一个key,叫my_name:

set my_name zhangguoji

按照Redis Cluster的哈希槽算法,CRC16('my_name')%16384 = 2412

那么这个key就被分配到了节点A上

同样的,当连接(A,B,C)的任意一个节点想获取my_name这个key,都会转到节点A上

如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。

增加一个D节点的结果可能如下:

  • 节点A覆盖1365-5460

  • 节点B覆盖6827-10922

  • 节点C覆盖12288-16383

  • 节点D覆盖0-1364,5461-6826,10923-1228

如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C

然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

Redis Cluster的模型大概是这样的形状

Redis 主从复制模型

为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作

Redis 集群对节点使用了主从复制功能:

集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。

在之前列举的节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000号的哈希槽。

另一方面, 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。

不过如果节点 B 和 B1 都下线的话, Redis 集群还是会停止运作。

持久化保证了即使 Redis 服务重启也不会丢失数据,因为 Redis 服务重启后会将硬盘上持久化的数据恢复到内存中

但是当 Redis 服务器的硬盘损坏了可能会导致数据丢失,不过通过Redis 的主从复制机制就可以避免这种单点故障,如下图:

1)主 Redis 中的数据有两个副本(replication)即从 Redis1 和从 Redis2,即使一台 Redis 服务器宕机其它两台 Redis 服务也可以继续提供服务。

2)主 Redis 中的数据和从 Redis 上的数据保持实时同步,当主 Redis 写入数据时通过主从复制机制会复制到两个从 Redis 服务上。

3)只有一个主 Redis,可以有多个从 Redis。

4)主从复制不会阻塞 master,在同步数据时,master 可以继续处理 client 请求。

5)一个 Redis 可以即是主又是从,如下图:

实现原理

Redis的主从同步,分为全量同步和增量同步。

只有从机第一次连接上主机是全量同步,断线重连有可能触发全量同步也有可能是增量同步(master 判断 runid 是否一致),除此之外的情况都是增量同步。

全量同步

Redis 的全量同步过程主要分三个阶段:

(1)同步快照阶段:Master 创建并发送快照给 Slave,Slave 载入并解析快照。Master 同时将此阶段所产生的新的写命令存储到缓冲区。

(2)同步写缓冲阶段:Master 向 Slave 同步存储在缓冲区的写操作命令。

(3)同步增量阶段:Master 向 Slave 同步写操作命令。

增量同步

(1)Redis 增量同步主要指 Slave 完成初始化后开始正常工作时,Master 发生的写操作同步到 Slave 的过程。

(2)通常情况下,Master 每执行一个写命令就会向 Slave 发送相同的写命令,然后 Slave接收并执行。

启动一台slave 的时候,他会发送一个psync命令给master

如果是这个slave第一次连接到master,他会触发一个全量复制。

master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中

RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件事情就是写进本地的磁盘,然后加载进内存

然后master会把内存里面缓存的那些新命令都发给slave。

Redis 一致性保证

Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作:

第一个原因是因为集群是用了异步复制. 写操作过程:

  1. 客户端向主节点B写入一条命令.

  2. 主节点B向客户端回复命令状态.

  3. 主节点将写操作复制给他得从节点 B1, B2 和 B3

主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低

必须在性能和一致性之间做出权衡。

注意:Redis 集群可能会在将来提供同步写的方法。

Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。

举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点,还有一个客户端 Z1

假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .

Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.

注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项

Redis Sentinel 哨兵机制

其实 Redis 主从复制有一个很大的缺点就是没有办法对 master 进行动态选举

(当 master 挂掉后,会通过一定的机制,从 slave 中选举出一个新的 master),需要使用 Sentinel 机制完成动态选举

1)Sentinel(哨兵) 进程是用于监控 Redis 集群中 Master 主服务器工作的状态

2)在 Master 主服务器发生故障的时候,可以实现 Master 和 Slave 服务器的切换,保证系统的高可用(High Availability)

哨兵的作用

1)监控(Monitoring):哨兵(sentinel) 会不断地检查你的 Master 和 Slave 是否运作正常。

2)提醒(Notification):当被监控的某个Redis节点出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。(使用较少)

3)自动故障迁移(Automatic failover):当一个 Master 不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作。具体操作如下:

(1)它会将失效 Master 的其中一个 Slave 升级为新的 Master, 并让失效 Master 的其他Slave 改为复制新的 Master。

(2)当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使用现在的 Master 替换失效 Master。

(3)Master 和 Slave 服务器切换后,Master 的 redis.conf、Slave 的 redis.conf 和sentinel.conf 的配置文件的内容都会发生相应的改变,即 Master 主服务器的 redis.conf 配置文件中会多一行 slaveof 的配置,sentinel.conf 的监控目标会随之调换。

哨兵的工作方式

哨兵进程的工作方式

  1. 每个 Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器,Slave 从服务器以及其他 Sentinel(哨兵)进程发送一个 PING命令。(此处我们还没有讲到集群,下一章节就会讲到,这一点并不影响我们模拟哨兵机制)

  2. 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。

  3. 如果一个 Master 主服务器被标记为主观下线(SDOWN),则正在监视这个 Master 主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。

  4. 当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了主观下线状态(SDOWN), 则 Master 主服务器会被标记为客观下线(ODOWN)。

  5. 在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master 主服务器、Slave 从服务器发送 INFO 命令。

  6. 当 Master 主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。

  7. 若没有足够数量的 Sentinel(哨兵)进程同意 Master 主服务器下线, Master 主服务器的客观下线状态就会被移除。若 Master 主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master 主服务器的主观下线状态就会被移除。

哨兵必须用三个实例去保证自己的健壮性的,哨兵+主从并不能保证数据不丢失,但是可以保证集群的高可用。

M1所在的机器挂了,哨兵还有两个,两个人一看他不是挂了嘛,那我们就选举一个出来执行故障转移不就好了。

哨兵组件的主要功能:

  • 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。

  • 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。

  • 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。

  • 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

1.主从复制解决了读请求的分担,从节点下线,会使得读请求能力有所下降

2.Master只有一个,写请求是一个单点问题

3.Sentinel会在Master下线后自动执行Failover操作,提升一台slave为Master,并让其他Slaves重新成为新的Master的Slaves

4.主从复制+哨兵Sentinel只解决了 读性能 和 高可用 问题,但是没有解决写性能问题(一个主节点存在瓶颈问题)