文章目录
1. 单机架构
单机架构模式中只有一个Redis服务器,它既负责数据的存储,也负责数据的读取。架构示意如下所示:
但是在实际的应用场景中并不会使用单机架构模式,它无法有效的解决如下的问题:
- 数据量伸缩问题:如果将Redis作为缓存数据库使用,我们可以通过设置缓存失效时间,以及不同的淘汰策略来清除失效的数据,避免数据常占Redis的内存空间。但是,当Redis作为一个NoSQL数据库使用时,随着数据量的增大,单机模式显然无法满足要求
- 访问量伸缩问题:Redis是基于单线程运行的,当读写访问请求很多时,Redis节点无法满足正常的响应时间要求
- 节点故障问题:当Redis节点发生宕机时,无论是读请求还是写请求,节点都无法正常提供服务
2. 主从架构
主从架构示意图如下所示:
2.1 配置
下面我们同样通过docker-compsoe来搭建Redis主从架构模式的服务器。首先,编写对应的docker-compose.yml文件,内容如下:
version: '3.1'
services:
# master
redis1:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis1
environment:
- TZ=Asia/Shanghai
ports:
- 7001:6379
volumes:
- ./conf/redis1.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
# slave1
redis2:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis2
environment:
- TZ=Asia/Shanghai
ports:
- 7002:6379
volumes:
- ./conf/redis2.conf:/usr/local/redis/redis.conf
# 通过links指定它的master是谁
links:
- redis1:master
command: ["redis-server", "/usr/local/redis/redis.conf"]
# slave2
redis3:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis3
environment:
- TZ=Asia/Shanghai
ports:
- 7003:6379
volumes:
- ./conf/redis3.conf:/usr/local/redis/redis.conf
links:
- redis1:master
command: ["redis-server", "/usr/local/redis/redis.conf"]
相应的需要为每一个节点准备一个配置文件,并且在redis2.conf和redis3.conf中添加salve节点相应的配置。
# 从节点配置
replicaof master 6379
最后启动容器,通过命令进入到mater节点后,使用info命令查看节点信息可以看到,当前节点为master,而且它有两个slave,对应的ip和port也可以看到。
# Replication
role:master
connected_slaves:2
slave0:ip=172.23.0.3,port=6379,state=online,offset=56,lag=0
slave1:ip=172.23.0.4,port=6379,state=online,offset=56,lag=0
然后进入一个slave节点,同样使用info命令查看节点信息:
# Replication
role:slave
master_host:master
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
2.2 原理
主从复制是指将一个Redis节点上的数据,复制到其他的 Redis节点上。前者称为 主节点(master),后者称为 从节点(slave)。而且数据的复制是 单向 的,只能由主节点到从节点。Redis 主从复制支持 主从同步 和 从从同步 两种。主从复制的优势在于:
- 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复 :当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复
- 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务。在写少读多的场景下,通过多个从节点分担读请求,可以大大提高 Redis节点的并发量
- 高可用基石 :主从复制还是哨兵模式和集群模式能够实施的基础
整个主从复制的原理如下所示:
source
整个过程可以分为准备阶段、数据同步阶段和命令传播三个阶段。其中同步操作有SYNC
和PSYNC
两个命令可以选择使用,其中SYNC
命令消耗资源非常多,每次执行命令,master节点都需要执行如下的操作:
- master生成RDB快照,消耗大量的CPU、内存和磁盘IO资源
- master节点需要将生成的RDB文件发送给slave节点,这又会消耗大量的网络资源,并且会影响master节点的响应时间
- slave节点在收到RDB文件后需要载入,载入期间会因为阻塞而没办法处理读命令请求
PSYNC
命令有两种模式:
- 全量复制 :用于初次复制或者无法部分复制的场景,它会将master节点的数据全部发送给slave节点
- 部分复制:用于网络中断后的复制,只将中断期间master节点执行的写命令发送给slave节点。如果中断时间太长,导致master无法完整的保存中断期间执行的写命令,那么仍会使用部分复制
sync
负责把缓存区上的东西排到写队列中(缓冲区->写队列),在由守护进程负责把队列里的东西写到磁盘上,而sync函数在把缓存区上的东西排到写队列后,不管写队列中的内容是否写到磁盘上都立即返回。fsync
函数则是对指定文件的操作,而且必须等到写队列中的内容都写到磁盘后才返回,并且更新文件inode结点里的内容。
下面着重看一下PSYNC
的主要流程,如下所示:
master节点端的操作如下:
- 首先,slave节点向自己的master节点发起
sync
命令,master将新进的slave节点加入到自己的slave列表中,,然后执行bgsave
来进行持久化操作 - 持久化操作结束后,将得到的快照发送给slave节点。发送期间,如果master节点收到来自客户端的写命令,除了正常的响应外,还会存一份到back-log中
- 当快照发送结束后,还将back-log队列中的信息发送给salve节点。全部发送结束后,master节点后续的写操作同时发送给slave节点,保持实时的异步复制
slave节点端的操作如下:
- 当slave节点发送
sync
命令后,继续对外提供读服务 - 当slave节点收到快照后,将slave现有的数据清空,再将快照写入到自己的内存空间。同时接收back-log,执行重放操作,期间继续执行读服务
- 重放完成后,slave节点将继续接收master节点的命令副本并继续重放,保证主从之间的数据一致性
如果master节点对应的多个slave节点发送了多个
sync
命令,只要在master节点bgsave
操作执行结束之前到达,那么这些salve节点收到的快照是一样的。如果master节点在执行了一次主从复制后,收到了新的sync
命令,那么将再执行一次复制操作。
3. 哨兵架构
哨兵(sentinel)的主要作用为:当master节点发生故障时,众多slave节点中的一个可晋升为新的master节点来提供写服务,其余的slave节点仍然提供读服务,这种机制也别称为故障转移(failover)。主从复制+哨兵的架构模式如下所示:
3.1 配置
下面通过docker-compose
演示如何在主从架构的基础上启用哨兵机制。修改docker-compose.yml文件内容,在volume中多加一个数据卷的映射,对应的配置文件为哨兵所需的.conf
配置文件。
version: '3.1'
services:
redis1:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis1
environment:
- TZ=Asia/Shanghai
ports:
- 7001:6379
volumes:
- ./conf/redis1.conf:/usr/local/redis/redis.conf
- ./conf/sentinel1.conf:/data/sentinel.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis2:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis2
environment:
- TZ=Asia/Shanghai
ports:
- 7002:6379
volumes:
- ./conf/redis2.conf:/usr/local/redis/redis.conf
- ./conf/sentinel2.conf:/data/sentinel.conf
links:
- redis1:master
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis3:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis3
environment:
- TZ=Asia/Shanghai
ports:
- 7003:6379
volumes:
- ./conf/redis3.conf:/usr/local/redis/redis.conf
- ./conf/sentinel3.conf:/data/sentinel.conf
links:
- redis1:master
command: ["redis-server", "/usr/local/redis/redis.conf"]
并且在宿主机的conf目录下新建三个.conf配置文件。其中master对应的sentinel1.conf文件内容为:
# 哨兵需要后台启动
daemonize yes
# 指定master节点的ip和端口
sentinel monitor master 127.0.0.1 6379 2
# 哨兵每隔多久监听一次Redis架构
sentinel down-after-milliseconds master 10000
在slave节点的哨兵配置文件中填写如下内容:
# 哨兵需要后台启动
daemonize yes
# 指定master节点的ip和端口
sentinel monitor master master 6379 2
# 哨兵每隔多久监听一次Redis架构
sentinel down-after-milliseconds master 10000
然后使用docker-compose up -d
启动容器,可以看到此时redis都已经正常启动,但是哨兵并没有启动。
接着需要分别进入到三个Redis容器内部,使用redis-sentinel sentinel1.conf
、redis-sentinel sentinel2.conf
、redis-sentinel sentinel2.conf
分别启动哨兵。退出容器后,再次查看宿主机的sentinel1.conf文件可以看到如下内容:
至此,带有哨兵机制的主从架构就启动完毕。
3.2 原理
哨兵具有如下的功能:
- 监控:哨兵会不断的检查master节点和slave节点是否正常运作
- 自动故障转移:当哨兵发现master节点无法正常工作,它会将对应的slave节点中的一个晋升为master节点。即使出错的master节点后续恢复正常,它也只能作为新master节点的slave
- 配置提供者:客户端初始化时,通过哨兵来获取当前Redis服务的master节点配置
- 通知:哨兵可以将故障转移的结果发送给客户端
其中,当master节点发生故障时,新的master节点的选择原则如下:
- 在失效主服务器属下的从服务器当中, 那些被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的从服务器都会被 淘汰
- 在失效主服务器属下的从服务器当中, 那些与失效主服务器连接断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被 淘汰
- 在 经历了以上两轮淘汰之后 剩下来的从服务器中, 我们选出 复制偏移量(replication offset)最大 的那个 从服务器 作为新的主服务器;如果复制偏移量不可用,或者从服务器的复制偏移量相同,那么 带有最小运行 ID 的那个从服务器成为新的主服务器
那么,哨兵是如何感知到master节点出现故障了呢? 哨兵节点会定期的向master节点发送心跳包判断其是否存活,称为PING
。当某个哨兵发现master发生了故障,它将其标记为主观不可用状态,然后将其发送给其他的哨兵节点。如果确认master故障的哨兵节点个数超过了设置的阈值,则认为该master客观不可用,进行故障转移的流程。
3. 集群架构
集群机构模式如下所示,它有多个Redis节点组组成,每个节点组所服务的数据之间不存在交集。其中,每个节点组都有且仅有一个master节点,如果使用主从复制,那么可以有0到多个slave节点。master节点提供写服务和读服务,而slave节点只提供读服务。
3.1 配置
下面通过docker-compose来搭建集群。首先编写docker-compose.yml文件,内容如下:
version: '3.1'
services:
redis1:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis1
environment:
- TZ=Asia/Shanghai
ports:
- 7001:7001
- 17001:17001
volumes:
- ./conf/redis1.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis2:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis2
environment:
- TZ=Asia/Shanghai
ports:
- 7002:7002
- 17002:17002
volumes:
- ./conf/redis2.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis3:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis3
environment:
- TZ=Asia/Shanghai
ports:
- 7003:7003
- 17003:17003
volumes:
- ./conf/redis3.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis4:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis4
environment:
- TZ=Asia/Shanghai
ports:
- 7004:7004
- 17004:17004
volumes:
- ./conf/redis4.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis5:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis5
environment:
- TZ=Asia/Shanghai
ports:
- 7005:7005
- 17005:17005
volumes:
- ./conf/redis5.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
redis6:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis6
environment:
- TZ=Asia/Shanghai
ports:
- 7006:7006
- 17006:17006
volumes:
- ./conf/redis6.conf:/usr/local/redis/redis.conf
command: ["redis-server", "/usr/local/redis/redis.conf"]
每个节点的配置文件内容格式如下:
# 指定Redis的端口号
port 7001
# 开启集群架构模式
cluster-enabled yes
# 集群信息的文件
cluster-config-file nodes-7001.conf
# 集群对外ip
cluster-announce-ip 121.199.75.6
# 集群对外端口号
cluster-announce-port 7001
# 集群的总线端口
cluster-announce-bus-port 17001
编写好相应的配置文件后,使用doker-compose up -d
启动全部的Redis容器。容器启动后,随便进入到一个容器中,使用如下命令进行集群节点之间的互连:
redis-cli --cluster create 121.199.75.6:7001 121.199.75.6:7002 121.199.75.6:7003 121.199.75.6:7004 121.199.75.6:7005 121.199.75.6:7006 --cluster-replicas 1
等待一段时间,Redis的集群模式就搭建完毕了。
3.2 原理
Redis集群架构可以保证主从+哨兵的基本功能之外,继续提升Redis存储数据和并发响应的能力。Redis集群模式无中心,集群中每个Redis节点之间都是互相两两连接,客户端只需要随机的连接到其中任意一个节点上,就可以对集群中其他的Redis节点执行读写操作。
source
不同的节点之间通过Redis Cluster Bus进行交互,交换的信息有:
- slot槽和节点之间的对应关系
- 集群中每个节点的可用状态
- 当集群结构发生改变时,需要一定的协议对集群内所有的节点的配置信息达成一致
- 发布/订阅功能需要传递的信息
- …
Redis集群中内置了16384个哈希槽,客户端连接到 Redis 集群之后,会同时得到一份关于这个 集群的配置信息。当客户端具体对某一个 key
值进行操作时,会使用CRC16校验后计算出它的Hash值,然后把哈希值对 16384
求余数,这样每个 key
都会对应一个编号在 0-16383
之间的哈希槽。
Redis 会根据节点数量 大致均等 的将哈希槽映射到不同的节点,再结合集群的配置信息就能够知道这个 key
值应该存储在哪一个具体的 Redis 节点中。如果不属于自己管,那么就会使用一个特殊的 MOVED
命令来进行一个跳转,告诉客户端去连接指定的节点执行读写请求。
cluster不支持跨节点的命令,如果一个请求中涉及到两个节点的key,那么请求将操作失败。Redis为了解决这个问题,引入了HashTag的概念,允许只使用key的某一部分计算哈希值,进而划分到相应的哈希槽。如果两个key某些部分是一致的,那么它们将会落到同一个槽中。
集群相对于前两种模式具有如下的优点:
- 数据分区:集群将数据分散到多个节点,一方面突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面,每个master节点都可以对外提供读写服务,大大提高了集群的响应能力
- 高可用:集群支持主从复制和主节点的自动故障转移(与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务
3.3 分区方案
常用的分区方案有如下三种:
-
哈希值 % 节点数:这是最简单也是最直接的一种方式,简单的取模进行节点的选择。但是当新增或是删减节点导致节点的数量发生变化时,系统中所有的数据都需要重新计算映射关系,使得数据迁移工作繁重
-
一致性哈希分区:将整个哈希值空间组织为一个虚拟的圆环,范围是 [ 0 − 2 32 − 1 ] [0 - 2^{32-1}] [0−232−1]。对于每一个数据来说,首先根据key计算得到哈希值后,明确了它在环上的位置,然后从该位置顺时针往下走,找到的第一个节点就是它需要映射的节点。
source当节点的数量发生变化时,对于数据的影响仅限于附近的数据。以上图为例,如果在
node1
和node2
之间增加node5
,则只有node2
中的一部分数据会迁移到node5
;如果去掉node2
,则原node2
中的数据只会迁移到node4
中,只有node4
会受影响。但是,当节点的数量比较少时,增加或删除节点可能会导致数据倾斜,造成严重的数据不均衡。例如,如果去掉
node2
,node4
中的数据由总数据的1/4
左右变为1/2
左右,与其他节点相比负载过高。 -
带有虚拟节点的一致性哈希分区:即上面所介绍的Redis中哈希槽的思想,每个实际的节点都包含一定数量的哈希槽,每个槽包含的数据都在一定的范围内,它是数据管理和迁移的基本单位,此时节点数量的变化对于系统的影响就很小。槽解耦了数据和实际节点之间的关系,增加或删除节点对系统的影响很小。
问:当集群中增删节点,哈希槽的分配是如何变化的呢?
答:仍以上图为例,系统中有4
个实际节点,假设为其分配16
个槽(0-15
),槽0-3
位于node1
;4-7
位于node2
;以此类推….如果此时删除node2
,只需要将槽4-7
重新分配即可,例如槽4-5
分配给node1
,槽 6 分配给node3
,槽 7 分配给node4
;可以看出删除node2
后,数据在其他节点的分布仍然较为均衡。
3.4 slot迁移
当新增节点、节点下线或者因负载不均需要充分调整slot分布时,就会触发slot迁移操作。slot的迁移操作由外部的系统完成,Redis只是提供了过程中所需的原语供调用。这些原语主要包含两种:
- 节点迁移状态设置:源节点或是目标节点
- key迁移的原子命令:执行具体的迁移工作
slot迁移的过程示意图如下所示:
假设需要将A节点中的id为1的slot迁移到B节点,那么整个迁移的过程为:
- 向B节点发送状态变更命令,将B节点对应的slot设置为
IMPORTING
- 向A节点发送状态变更命令,将A节点对应的slot设置为
MIGERTAING
- 针对于A的slot中所有的key,分别向A发送
MIGRATE
命令,告诉A将对应的key迁移到B上
由于两个节点在迁移的过程中仍然会相应客户端的请求,因此,面对请求两个节点的操作和正常状态下是有区别的:
- 对于A节点来说,如果客户端访问的key仍在A上,则正常处理该请求;如果key已经或者正在迁移到B,那么A会回复客户端
ASK
消息,让它跳转到B上执行 - 对于B节点来说,所有非
ASK
请求跳转而来的操作都不进行处理,而是回复MOVED
命令,让客户端跳转到A执行
这样保证了同一个key在迁移之前总是在源节点执行操作,迁移之后总是在目标节点上执行操作。当slot中所有的key都成功的迁移到了B上,客户端使用CLUSTER SETSLOT
命令,这是B的slot信息,让B感知到迁移的slot。
3.5 failover
集群的故障迁移需要解决如下的几个问题:
- 如果某个节点发生了故障,它如何让集群中其他的节点感知到
- 如果多个节点感知到了故障信息,那么如何确定感知到的节点一定就是发生了故障
- 如果确定发生了故障,如果将故障节点的一个slave晋升为新的master节点
- 当升级完成后,如何让集群所有的节点感知到变更的发生
集群中节点两两之间通过Redis Cluster Bus来进行PING/PONG
通信,当节点A向节点B通信时,如果发送的PING
消息没有正常得到PONG
消息,那么节点A将节点B置为PFAIL
(possible fail)状态。之后,节点A与集群中其他的节点使用gossip协议进行通信时,PFAIL
状态信息就会传递到其他的节点。
节点A之后也会收到其他节点的PONG
信息,如果其他节点认为节点B确实发生了故障的数量超过了一定值,那么节点A就将节点B设置为FAIL
状态。
当节点B被设置为FAIL
状态后,需要从它的多个slave节点中选举一个升级为新的master节点。slave节点在晋升前会进行协商优先级,优先级的重要决定因素就是slave节点最后一次同步master信息的时间,越近表示这个slave节点的数据越新,相应的优先级就越高。优先级越高的slave节点更有可能早发起选举,成为新的master节点的可能性越高。
参加选举的slave节点会向其他的master节点发送消息,如果master节点本轮还没有投票,则回复同意,否则拒绝。当slave节点收到超过半数的master节点同意的消息,则它升级为新的master节点。然后它通过PONG
消息将最新的epoch进行广播,使得集群中其他的节点可以及时更新拓扑结构,直到集群收敛。
3.6 优化手段
如果集群中的节点设置了主从复制,那么客户端对于master节点的读请求会被直接响应,而对于slave节点的读请求,会被MOVED
消息转移到master节点,由master节点响应。为了更好的满足读写分离的需求,Redis提供了READONLY
命令,如果slave节点收到的是该命令,则直接处理请求,无需再转换到master节点处理。
另外,Redis集群还提供了单点保护功能。假设master节点A只有一个slave节点,当它的slave发生故障时,集群为了继续保证高可用性,会将节点B的slave进行副本迁移后,让它重新成为节点A的slave使用。为了满足这种需求,集群中节点的数量应该保持为2 * master + 1
个节点。迁移的示意过程如下所示:
4. 节点间的通信
4.1 端口
集群中所有的节点都存储数据,并且参与集群状态的维护。为此,每个节点都提供了两个TCP端口:
- 普通端口:主要用于为客户端提供服务,以及节点之间数据的迁移,通常定义为7000、70001等
- 集群端口:通常定义为
普通端口+10000
,它主要用于节点之间的通信,如集群搭建、节点增减和数据迁移等操作时,节点之间的通信
4.2 协议
节点之间的通信协议通常可以分为点对点、广播、Gossip协议等类型,其中:
- 点对点:两个节点之间的通信,其他的节点不参与它们之间的通信
- 广播:向集群内所有的节点发送信息,优点在于集群收敛速度快,但每条消息需要发送给集群内所有的节点,消耗CPU、带宽等资源较多
- Goosip协议:指在节点数量有限的网络中,每个节点都按照某种规则随机的与部分节点进行通信,经过一段时间的通信,每个节点的状态很快会达到一致。它的优点在于负载比广播方式低、去中心化、容错性高等,当时相比于广播方式,它的收敛速度要慢一些
集群收敛指集群内所有的节点获取到的信息是一致的。
4.3 消息
集群的节点采用每秒10次的定时任务进行通信工作,例如判断是否需要发送消息及消息的类型、确定接收节点、发送消息等。如果集群状态发生了变化,如果节点数量改变、槽状态改变等,通过节点之间的通信可以很快的让所有节点都收到消息,使集群快速收敛。
节点之间发送的消息主要分为如下5种,不同的消息类型、通信协议、发送的频率和时间、接收节点的选择等是不同的:
消息类型 | 描述 |
---|---|
MEET | 在节点握手阶段,当节点收到客户端的 CLUSTER MEET 命令时,会向新加入的节点发送 MEET 消息,请求新节点加入到当前集群;新节点收到 MEET 消息后会回复一个 PONG 消息 |
PING | 集群里每个节点每秒钟会选择部分节点发送 PING 消息,接收者收到消息后会回复一个 PONG 消息。PING 消息的内容是自身节点和部分其他节点的状态信息,作用是彼此交换信息,以及检测节点是否在线。PING 消息使用 Gossip 协议发送,接收节点的选择兼顾了收敛速度和带宽成本,具体规则如下:随机找 5 个节点,在其中选择最久没有通信的 1 个节点;扫描节点列表,选择最近一次收到 PONG 消息时间大于 cluster_node_timeout / 2 的所有节点,防止这些节点长时间未更新 |
PONG | PONG 消息封装了自身状态数据。可以分为两种:第一种 是在接到 MEET/PING 消息后回复的 PONG 消息;第二种 是指节点向集群广播 PONG 消息,这样其他节点可以获知该节点的最新信息,例如故障恢复后新的主节点会广播 PONG 消息 |
FAIL | 当一个主节点判断另一个主节点进入 FAIL 状态时,会向集群广播这一 FAIL 消息;接收节点会将这一 FAIL 消息保存起来,便于后续的判断 |
PUBLISH | 节点收到 PUBLISH 命令后,会先执行该命令,然后向集群广播这一消息,接收节点也会执行该 PUBLISH 命令 |
4.4 数据结构
节点为了存储集群状态而提供的数据结构中,最关键的是 clusterNode
和 clusterState
结构:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。
-
clusterNode :保存了 一个节点的当前状态,包括创建时间、节点 id、ip 和端口号等。每个节点都会用一个
clusterNode
结构记录自己的状态,并为集群内所有其他节点都创建一个clusterNode
结构来记录节点状态typedef struct clusterNode { //节点创建时间 mstime_t ctime; //节点id char name[REDIS_CLUSTER_NAMELEN]; //节点的ip和端口号 char ip[REDIS_IP_STR_LEN]; int port; //节点标识:整型,每个bit都代表了不同状态,如节点的主从状态、是否在线、是否在握手等 int flags; //配置纪元:故障转移时起作用,类似于哨兵的配置纪元 uint64_t configEpoch; //槽在该节点中的分布:占用16384/8个字节,16384个比特;每个比特对应一个槽:比特值为1,则该比特对应的槽在节点中;比特值为0,则该比特对应的槽不在节点中 unsigned char slots[16384/8]; //节点中槽的数量 int numslots; ………… } clusterNode;
除了上述字段,
clusterNode
还包含节点连接、主从复制、故障发现和转移需要的信息等。 -
clusterState :保存了在当前节点视角下,集群所处的状态。主要字段包括:
typedef struct clusterState { //自身节点 clusterNode *myself; //配置纪元 uint64_t currentEpoch; //集群状态:在线还是下线 int state; //集群中至少包含一个槽的节点数量 int size; //哈希表,节点名称->clusterNode节点指针 dict *nodes; //槽分布信息:数组的每个元素都是一个指向clusterNode结构的指针;如果槽还没有分配给任何节点,则为NULL clusterNode *slots[16384]; ………… } clusterState;
除此之外,
clusterState
还包括故障转移、槽迁移等需要的信息。
5. 淘汰机制
当key的生存时间到时,并不会立即删除,而是采用下面的两种方式进行删除:
- 定期删除:Redis每隔一段时间就会查看设置了过期时间的key,一般会在100ms的间隔中默认查看3个key
- 惰性删除:当用户去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,如果生存时间已到,直接删除key,并且返回给用户一个空值
在Redis已经没有存储空间的情况下,添加一个新数据,Redis就会执行淘汰机制。Redis支持的淘汰机制有如下几种:
volatile-lru
:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个最近最少使用的keyallkeys-lru
:当内存不足时,Redis会在全部的key中淘汰掉一个最近最少使用的keyvolatile-lfu
:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个最近最少频次使用的keyallkeys-lfu
:当内存不足时,Redis会在全部的key中淘汰掉一个最近最少频次使用的keyvolatile-random
:当内存不足时,Redis会在过了生存时间的key中随机淘汰掉一个keyallkeys-random
:当内存不足时,Redis会在全部的key中随机淘汰掉一个key- `volatile-ttl:当内存不足时,Redis会在过了生存时间的key中淘汰掉一个剩余生存时间最少的key
noeviction(默认)
:当内存不足时,直接报错
通过maxmemory-policy
策略的命令来执行具体使用的淘汰机制。同时,还可以通过maxmemory
字节大小来设置redis的最大内存。