Redis高可用方案
“高可用性”(High Availability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性, CAP的AP模型
1. 主从复制
Redis支持主从复制功能,可以通过执行slaveof(Redis5以后改成replicaof)或者在配置文件中设置slaveof(Redis5以后改成replicaof)来开启复制功能
一主一从, 一主多从, 传递复制(也是一主多从,但slave有slave)
1.1 主从配置
-
主redis 无需配置
-
从redis redis.conf
# slaveof <masterip> <masterport> # 表示当前【从服务器】对应的【主服务器】的IP是192.168.10.135,端口是6379。 replicaof 127.0.0.1 6379
1.2 作用
-
读写分离: 提升吞吐量和性能
-
数据容灾
从机是主机的备份
默认情况下主机宕机后, 从机不可为主机, 但利用哨兵可以实现主从切换, 做到高可用
1.3 原理与实现
1.3.1 复制流程
-
保存主节点信息
当客户端向从服务器发送slaveof(replicaof) 主机地址(127.0.0.1) 端口(6379)时:从服务器将主机ip(127.0.0.1)和端口(6379)保存到redisServer的masterhost和masterport中。
从服务器将向发送SLAVEOF命令的客户端返回OK,表示复制指令已经被接收,而实际上复制工作是在OK返回之后进行
-
建立socket连接
slaver与master建立socket连接, slaver关联文件事件处理器, 该处理器接收RDB文件(全量复制)、接收Master传播来的写命令(增量复制)
主服务器accept从服务器Socket连接后,创建相应的客户端状态。相当于从服务器是主服务器的Client端
-
发送Ping命令
slaver向master发送ping命令, master响应pong
-
权限验证
最好保持从和主密码一致
-
发送端口信息
在身份验证步骤之后,从服务器将执行命令REPLCONF listening-port ,向主服务器发送从服务器的监听端口号
-
同步数据
-
命令传播
当同步数据完成后,主从服务器就会进入命令传播阶段,主服务器只要将自己执行的写命令发送给从服务器,而从服务器只要一直执行并接收主服务器发来的写命令
1.3.2 同步数据集
-
Redis 2.8以前使用SYNC命令同步复制
实现方式: Redis的同步功能分为同步(sync)和命令传播(command propagate)。
同步操作 1. 通过从服务器发送到SYNC命令给主服务器 2. 主服务器生成RDB文件并发送给从服务器,同时发送保存所有写命令给从服务器 3. 从服务器清空之前数据并执行解释RDB文件 4. 保持数据一致(还需要命令传播过程才能保持一致) 命令传播 同步操作完成后,主服务器执行写命令,该命令发送给从服务器并执行,使主从保存一致。
缺点: 没有全量同步和增量同步的概念,从服务器在同步时,会清空所有数据。主从服务器断线后重复制,主服务器会重新生成RDB文件和重新记录缓冲区的所有命令,并全量同步到从服务器上。
-
Redis 2.8之后采用PSYNC命令替代SYNC
实现方式 - Redis 的主从同步,分为全量同步和增量同步。 - 只有从机第一次连接上主机是全量同步。 - 断线重连有可能触发全量同步也有可能是增量同步( master 判断 runid 是否一致)。
除此之外的情况都是增量同步。
Redis 的全量同步过程主要分三个阶段:
- 同步快照阶段: Master 创建并发送快照RDB给 Slave , Slave 载入并解析快照。 Master 同时将 此阶段所产生的新的写命令存储到缓冲区。 - 同步写缓冲阶段: Master 向 Slave 同步存储在缓冲区的写操作命令。 - 同步增量阶段: Master 向 Slave 同步写操作命令。
心跳检测: 在命令传播阶段,从服务器默认会以每秒一次的频率向主服务器发送命令
replconf ack <replication_offset> #ack :应答 #replication_offset:从服务器当前的复制偏移量 作用: 1.检测主从状态 2.辅助实现min-slaves 3.检测命令丢失
2. 哨兵模式
哨兵(sentinel)是Redis的高可用性(High Availability)的解决方案:由一个或多个sentinel实例组成sentinel集群可以监视一个或多个主服务器和多个从服务器。
当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证redis的高可用性。
2.1 部署方案
2.2 哨兵集群搭建
在一台机器上采用伪分布式的方式部署: 不同端口实现
-
Redis-master: 6379
#1 安装redis5.0 mkdir redis-master cd /var/redis-5.0.5/src/ make install PREFIX=/var/redis-ms/redis-master cp /var/redis-5.0.5/redis.conf /var/redis-ms/redis-master/bin #2 修改redis.conf # 将`daemonize`由`no`改为`yes` daemonize yes # 默认绑定的是回环地址,默认不能被其他机器访问 # bind 127.0.0.1 # 是否开启保护模式,由yes该为no protected-mode no
-
Redis-Slaver1:127.0.0.1 6380
#安装redis-slaver1 mkdir redis-slaver1 cp -r /var/redis-ms/redis-master/* /var/redis-ms/redis-slaver1 #修改配置文件 vim /var/redis-ms/redis-slaver1/redis.conf port 6380 replicaof 127.0.0.1 6379
-
Redis-Slaver2:127.0.0.1 6381
#安装redis-slaver2 mkdir redis-slaver2 cp -r /var/redis-ms/redis-master/* /var/redis-ms/redis-slaver2 #修改配置文件 vim /var/redis-ms/redis-slaver2/redis.conf port 6381 replicaof 127.0.0.1 6379
-
Redis-Sentinel1:127.0.0.1 26379
#安装redis-sentinel1 mkdir redis-sentinel1 cp -r /var/redis-ms/redis-master/* /var/redis-ms/redis-sentinel1 #拷贝sentinel.conf 配置文件并修改 cp /var/redis-5.0.5/sentinel.conf /var/redis-ms/redis-sentinel1 vim sentinel.conf # 哨兵sentinel实例运行的端口 默认26379 port 26379 # 将`daemonize`由`no`改为`yes` daemonize yes # 哨兵sentinel监控的redis主节点的 ip port # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。 # quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了 # sentinel monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127.0.0.1 6379 2 # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码 # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒,改成3 秒 # sentinel down-after-milliseconds <master-name> <milliseconds> sentinel down-after-milliseconds mymaster 3000 # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步, #这个数字越小,完成failover所需的时间就越长, #但是如果这个数字越大,就意味着越多的slave因为replication而不可用。 #可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。 # sentinel parallel-syncs <master-name> <numslaves> sentinel parallel-syncs mymaster 1 # 故障转移的超时时间 failover-timeout 可以用在以下这些方面: #1. 同一个sentinel对同一个master两次failover之间的间隔时间。 #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的 master那里同步数据时。 #3.当想要取消一个正在进行的failover所需要的时间。 #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时, # slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了 # 默认三分钟 # sentinel failover-timeout <master-name> <milliseconds> sentinel failover-timeout mymaster 180000
-
Redis-Sentinel2:127.0.0.1 26380
#安装redis-sentinel2 mkdir redis-sentinel2 cp -r /var/redis-ms/redis-sentinel1/* /var/redis-ms/redis-sentinel2 #修改sentinel.conf vim /var/redis-ms/redis-sentinel2/sentinel.conf port 26380
-
Redis-Sentinel3:127.0.0.1 26381
#安装redis-sentinel3 mkdir redis-sentinel3 cp -r /var/redis-ms/redis-sentinel1/* /var/redis-ms/redis-sentinel3 #修改sentinel.conf vim /var/redis-ms/redis-sentinel3/sentinel.conf port 26381
-
配置好后依次执行 redis-master、redis-slaver1、redis-slaver2、redis-sentinel1、redis-sentinel2、redis-sentinel3
#启动redis-master和redis-slaver 在redis-master目录下 ./redis-server redis.conf 在redis-slaver1目录下 ./redis-server redis.conf 在redis-slaver2目录下 ./redis-server redis.conf #启动redis-sentinel 在redis-sentinel1目录下./redis-sentinel sentinel.conf 在redis-sentinel2目录下./redis-sentinel sentinel.conf 在redis-sentinel3目录下./redis-sentinel sentinel.conf
2.3 执行流程
-
启动并初始化sentinel
Sentinel是一个特殊的Redis服务器, 不会进行持久化, 每个Sentinel会创建2个连向主服务器的网络连接,
命令连接: 用于向主服务器发送命令,并接收响应
订阅连接:用于订阅主服务器的—sentinel—:hello频道
-
获取主服务器信息
Sentinel默认每10s一次,向被监控的主服务器发送info命令,获取主服务器和其下属从服务器的信息
-
获取从服务器信息
当Sentinel发现主服务器有新的从服务器出现时,Sentinel还会向从服务器建立命令连接和订阅连接。
在命令连接建立之后,Sentinel还是默认10s一次,向从服务器发送info命令,并记录从服务器的信息
-
向主服务器和从服务器发送消息(以订阅的方式)
默认情况下,Sentinel每2s一次,向所有被监视的主服务器和从服务器所订阅的—sentinel—:hello频道上发送消息,消息中会携带Sentinel自身的信息和主服务器的信息
-
接收来着主服务器和从服务器的频道信息
当Sentinel与主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令
subscribe —sentinel—:hello
Sentinel彼此之间只创建命令连接,而不创建订阅连接,因为Sentinel通过订阅主服务器或从服务器,就可以感知到新的Sentinel的加入,而一旦新Sentinel加入后,相互感知的Sentinel通过命令连接来通信就可以了
-
检测主观下线状态
Sentinel每秒一次向所有与它建立了命令连接的实例(主服务器、从服务器和其他Sentinel)发送PING命令
实例在down-after-milliseconds毫秒内返回无效回复(除了+PONG、-LOADING、-MASTERDOWN外)
实例在down-after-milliseconds毫秒内无回复(超时)
Sentinel就会认为该实例主观下线(SDown)
-
检测客观下线状态
当一个Sentinel将一个主服务器判断为主观下线后, Sentinel会向同时监控这个主服务器的所有其他Sentinel发送查询命令
判断它们是否也认为主服务器下线。如果达到Sentinel配置中的quorum数量的Sentinel实例都判断主服务器为主观下线,则该主服务器就会被判定为客观下线(ODown)。
-
选举leader sentinel
当一个主服务器被判定为客观下线后,监视这个主服务器的所有Sentinel会通过选举算法(raft),选出一个Leader Sentinel去执行failover(故障转移)操作
2.4 哨兵leader选举
-
raft
Raft协议是用来解决分布式系统一致性问题的协议。
Raft协议描述的节点共有三种状态:Leader, Follower, Candidate。
term:Raft协议将时间切分为一个个的Term(任期),可以认为是一种“逻辑时间”。
选举流程: Raft采用心跳机制触发Leader选举 系统启动后,全部节点初始化为Follower,term为0。 节点如果收到了RequestVote或者AppendEntries,就会保持自己的Follower身份 节点如果一段时间内没收到AppendEntries消息,在该节点的超时时间内还没发现Leader,Follower就 会转换成Candidate,自己开始竞选Leader。 一旦转化为Candidate,该节点立即开始下面几件事情: 增加自己的term。 启动一个新的定时器。 给自己投一票。 向所有其他节点发送RequestVote,并等待其他节点的回复。 如果在计时器超时前,节点收到多数节点的同意投票,就转换成Leader。同时向所有其他节点发送 AppendEntries,告知自己成为了Leader。 每个节点在一个term内只能投一票,采取先到先得的策略,Candidate前面说到已经投给了自己, Follower会投给第一个收到RequestVote的节点。 Raft协议的定时器采取随机超时时间,这是选举Leader的关键。 在同一个term内,先转为Candidate的节点会先发起投票,从而获得多数票。
-
sentinel的leader选举流程
1、某Sentinel认定master客观下线后,该Sentinel会先看看自己有没有投过票,如果自己已经投过票 给其他Sentinel了,在一定时间内自己就不会成为Leader。 2、如果该Sentinel还没投过票,那么它就成为Candidate。 3、Sentinel需要完成几件事情: 更新故障转移状态为start 当前epoch加1,相当于进入一个新term,在Sentinel中epoch就是Raft协议中的term。 向其他节点发送 is-master-down-by-addr 命令请求投票。命令会带上自己的epoch。 给自己投一票(leader、leader_epoch) 4、当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;(通过判断epoch) 5、Candidate会不断的统计自己的票数,直到他发现认同他成为Leader的票数超过一半而且超过它配 置的quorum,这时它就成为了Leader。 6、其他Sentinel等待Leader从slave选出master后,检测到新的master正常工作后,就会去掉客观下 线的标识
-
故障转移
当选举出Leader Sentinel后,Leader 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 配置文件中会多一行 replicaof 的配置, sentinel.conf 的监控目标会随之调换。
2.5 主服务器的选择
哨兵leader根据以下规则从客观下线的主服务器的从服务器中选择出新的主服务器。
-
过滤掉主观下线的节点
-
选择slave-priority最高的节点,如果有则返回没有就继续选择
-
选择出复制偏移量最大的系节点,因为复制偏移量越大则数据复制的越完整,如果由就返回了,没有就继续
-
选择run_id最小的节点,因为run_id越小说明重启次数越少
3. 集群与分区
分区是将数据分布在多个Redis实例(Redis主机)上,以至于每个实例只包含一部分数据
3.1 分区的意义
- 性能的提升
- 存储能力的横向扩展
3.2 分区的方式
根据分区键(id)进行分区
-
范围分区
根据id数字的范围比如1–10000、100001–20000…..90001-100000,每个范围分到不同的Redis实例中
好处: 实现简单,方便迁移和扩展
缺点: 数据分布不均,性能损失, 非数字型key无法使用
-
hash分区
好处: 支持任何类型的key, 热点分布均匀, 性能好
缺点: 迁移复杂, 需重新计算,扩展性差
3.3 client端分区
对于一个给定的key,客户端直接选择正确的节点来进行读写。许多Redis客户端都实现了客户端分区(JedisPool),也可以自行编程实现。
客户端选择算法: hash, 普通hash算法, 一致性hash算法
tips: hash算法可参考本站[集群场景化方案]
3.4 proxy端分区
在客户端和服务器端引入一个代理或代理集群,客户端将命令发送到代理上,由代理根据算法,将命令路由到相应的服务器上。常见的代理有Codis(豌豆荚)和TwemProxy(Twitter)
Codis3.x由以下组件组成:
- CodisServer:基于redis-3.2.8分支开发。增加了额外的数据结构,以支持slot有关的操作以及数据迁移指令。
- CodisProxy:客户端连接的Redis代理服务,实现了Redis协议。除部分命令不支持以外,表现的和原生的Redis没有区别(就像Twemproxy)。
- 对于同一个业务集群而言,可以同时部署多个codis-proxy实例;
- 不同codis-proxy之间由codis-dashboard保证状态同步。
- CodisDashboard:集群管理工具,支持codis-proxy、codis-server的添加、删除,以及据迁移等操作。在集群状态发生改变时,codis-dashboard维护集群下所有codis-proxy的状态的一致性。
- 对于同一个业务集群而言,同一个时刻codis-dashboard只能有0个或者1个;
- 所有对集群的修改都必须通过codis-dashboard完成。
- CodisAdmin:集群管理的命令行工具。
- 可用于控制codis-proxy、codis-dashboard状态以及访问外部存储。
- CodisFE:集群管理界面。
- 多个集群实例共享可以共享同一个前端展示页面;
- 通过配置文件管理后端codis-dashboard列表,配置文件可自动更新。
- Storage:为集群状态提供外部存储。
- 提供Namespace概念,不同集群的会按照不同productname进行组织;
- 目前仅提供了Zookeeper、Etcd、Fs三种实现,但是提供了抽象的interface可自行扩展。
分片原理:
Codis将所有的key默认划分为1024个槽位(slot),它首先对客户端传过来的key进行crc32运算计算哈希值,再将hash后的整数值对1024这个整数进行取模得到一个余数,这个余数就是对应key的槽位。
Codis的槽位和分组的映射关系就保存在codisproxy当中
优点:
- 对客户端透明,与codis交互方式和redis本身交互一样
- 支持在线数据迁移,迁移过程对客户端透明有简单的管理和监控界面
- 支持高可用,无论是redis数据存储还是代理节点
- 自动进行数据的均衡分配
- 最大支持1024个redis实例,存储容量海量
- 高性能
缺点:
- 采用自有的redis分支,不能与原版的redis保持同步
- 如果codis的proxy只有一个的情况下,redis的性能会下降20%左右
- 某些命令不支持
3.5 官方Cluster分区
Redis3.0之后,Redis官方提供了完整的集群解决方案。
方案采用去中心化的方式,包括:sharding(分区)、replication(复制)、failover(故障转移)。称为RedisCluster。 Redis5.0前采用redis-trib进行集群的创建和管理,需要ruby支持
Redis5.0可以直接使用Redis-cli进行集群的创建和管理
部署架构:
-
去中心化
RedisCluster由多个Redis节点组构成,是一个P2P无中心节点的集群架构,依靠Gossip协议传播的集群
-
Gossip协议
Gossip协议是一个通信协议,一种传播消息的方式。起源于:病毒传播
Gossip协议基本思想就是:一个节点周期性(每秒)随机选择一些节点,并把信息传递给这些节点。这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点。信息会周期性的传递给N个目标节点。这个N被称为fanout(扇出)
gossip协议包含多种消息,包括meet、ping、pong、fail、publish等
通过gossip协议,cluster可以提供集群间状态同步更新、选举自助failover等重要的集群功能。
-
slot
redis-cluster把所有的物理节点映射到[0-16383]个slot上,基本上采用平均分配和连续分配的方式
cluster负责维护节点和slot槽的对应关系value——>slot——–>节点
当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。
比如: set name zhaoyun
hash(“name”)采用crc16算法,得到值:1324203551%16384=15903
根据上表15903在13088-16383之间,所以name被存储在Redis5节点。
slot槽必须在节点上连续分配,如果出现不连续的情况,则RedisCluster不能工作,详见容错。
-
RedisCluster的优势
-
高性能
RedisCluster的性能与单节点部署是同级别的。
多主节点、负载均衡、读写分离
-
高可用
RedisCluster支持标准的主从复制配置来保障高可用和高可靠。
failover
RedisCluster也实现了一个类似Raft的共识方式,来保障整个集群的可用性。
-
易扩展
向RedisCluster中添加新节点,或者移除节点,都是透明的,不需要停机。
水平、垂直方向都非常容易扩展。
数据分区,海量数据,数据存储
-
原生
部署RedisCluster不需要其他的代理或者工具,而且RedisCluster和单机Redis几乎完全兼容。
-
Redis Cluster 集群搭建:
RedisCluster最少需要三台主服务器,三台从服务器。
端口号分别为:7001~7006
mkdir redis-cluster/7001
make install PREFIX=/var/redis-cluster/7001
cp /var/redis-5.0.5/redis.conf /var/redis-cluster/7001/bin
-
第一步:创建7001实例,并编辑redis.conf文件,修改port为7001。
-
第二步:修改redis.conf配置文件,打开
cluster-enable yes
-
第三步:复制7001,创建7002~7006实例,注意端口修改
cp -r /var/redis-cluster/7001/* /var/redis-cluster/7002 cp -r /var/redis-cluster/7001/* /var/redis-cluster/7003 cp -r /var/redis-cluster/7001/* /var/redis-cluster/7004 cp -r /var/redis-cluster/7001/* /var/redis-cluster/7005 cp -r /var/redis-cluster/7001/* /var/redis-cluster/7006
-
第四步:创建start.sh,启动所有的实例
-
第五步:创建Redis集群(创建时Redis里不要有数据)
# cluster-replicas : 1 1从机 前三个为主 [root@localhost bin]# ./redis-cli --cluster create 192.168.72.128:7001 192.168.72.128:7002 192.168.72.128:7003 192.168.72.128:7004 192.168.72.128:7005 192.168.72.128:7006 --cluster-replicas 1
-
客户端连接
# 随便连一台 ./redis-cli -h 127.0.0.1 -p 7001 -c #注意:-c 表示是以redis集群方式进行连接
# 查看集群状态 cluster info # 查看集群中的节点 cluster nodes # 以下节点信息示例: 包含唯一id, master, slave, slot等信息 d277cd2984639747a17ca79428602480b28ef070 127.0.0.1:7003@17003 myself,master - 0 1570457306000 3 connected 10923-16383 af559fc6c82c83dc39d07e2dfe59046d16b6a429 127.0.0.1:7001@17001 master - 0 1570457307597 1 connected 0-5460 e7b1f1962de2a1ffef2bf1ac5d94574b2e4d67d8 127.0.0.1:7005@17005 slave 068b678923ad0858002e906040b0fef6fff8dda4 0 1570457308605 5 connected 068b678923ad0858002e906040b0fef6fff8dda4 127.0.0.1:7002@17002 master - 0 1570457309614 2 connected 5461-10922 51c3ebdd0911dd6564040c7e20b9ae69cabb0425 127.0.0.1:7004@17004 slave af559fc6c82c83dc39d07e2dfe59046d16b6a429 0 1570457307000 4 connected 78dfe773eaa817fb69a405a3863f5b8fcf3e172f 127.0.0.1:7006@17006 slave d277cd2984639747a17ca79428602480b28ef070 0 1570457309000 6
分片:
不同节点分组服务于相互无交集的分片(sharding),RedisCluster不存在单独的proxy或配置服务器,所以需要将客户端路由到目标的分片。
-
客户端路由
RedisCluster的客户端相比单机Redis需要具备路由语义的识别能力,且具备一定的路由缓存能力
-
moved重定向
1.每个节点通过通信都会共享RedisCluster中槽和集群中对应节点的关系 2.客户端向RedisCluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与 16384取余,计算自己的槽和对应节点 3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端 4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常 5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息 6.客户端向目标节点发送命令,获取命令执行结果
-
ask重定向
在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移 当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的 节点信息如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时, 槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制 1.客户端向目标节点发送命令,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端 2.客户端向新的节点发送Asking命令给新的节点,然后再次向新节点发送命令 3.新节点执行命令,把命令执行结果返回给客户端
moved和ask的区别: 1、moved:槽已确认转移; 2、ask:槽还在转移过程中
-
Smart智能客户端 JedisCluster
JedisCluster是Jedis根据RedisCluster的特性提供的集群智能客户端
JedisCluster为每个节点创建连接池,并跟节点建立映射关系缓存(Clusterslots)
JedisCluster将每个主节点负责的槽位一一与主节点连接池建立映射缓存
JedisCluster启动时,已经知道key,slot和node之间的关系,可以找到目标节点
JedisCluster对目标节点发送命令,目标节点直接响应给JedisCluster
如果JedisCluster与目标节点连接出错,则JedisCluster会知道连接的节点是一个错误的节点,此时节点返回moved异常给JedisCluster , JedisCluster会重新初始化slot与node节点的缓存关系,然后向新的目标节点发送命令,目标命令执行命令并向JedisCluster响应, 如果命令发送次数超过5次,则抛出异常"Toomanyclusterredirection!"
JedisPoolConfig config = new JedisPoolConfig(); Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>(); jedisClusterNode.add(new HostAndPort("192.168.127.128", 7001)); jedisClusterNode.add(new HostAndPort("192.168.127.128", 7002)); jedisClusterNode.add(new HostAndPort("192.168.127.128", 7003)); jedisClusterNode.add(new HostAndPort("192.168.127.128", 7004)); jedisClusterNode.add(new HostAndPort("192.168.127.128", 7005)); jedisClusterNode.add(new HostAndPort("192.168.127.128", 7006)); JedisCluster jcd = new JedisCluster(jedisClusterNode, config); jcd.set("name:001","zhangfei"); String value = jcd.get("name:001");
-
-
迁移
在RedisCluster中每个slot对应的节点在初始化后就是确定的。在某些情况下,节点和分片需要变更
1 新的节点作为master加入; 2 某个节点分组需要下线; 3 负载不均衡需要调整slot分布。
此时需要进行分片的迁移,迁移的触发和过程控制由外部系统完成。包含下面2种:
- 节点迁移状态设置:迁移前标记源/目标节点。
- key迁移的原子化命令:迁移的具体步骤。
1、向节点B发送状态变更命令,将B的对应slot状态置为importing。 2、向节点A发送状态变更命令,将A对应的slot状态置为migrating。 3、向A发送migrate命令,告知A将要迁移的slot对应的key迁移到B。 4、当所有key迁移完成后,clustersetslot重新设置槽位。
-
扩容
添加主节点示例:
-
先创建7007节点 (无数据)
mkdir redis-cluster/7007 make install PREFIX=/var/redis-cluster/7007
-
添加7007结点作为新节点,并启动
[root@localhost bin]# ./redis-cli --cluster add-node 192.168.72.128:7007 192.168.72.128:7001
-
查看集群结点发现7007已添加到集群中
cluster nodes
-
hash槽重新分配(数据迁移)
添加完主节点需要对主节点进行hash槽分配,这样该主节才可以存储数据。
# 1. 查看集群中槽占用情况 cluster nodes # 2. 给刚添加的7007结点分配槽 # 第一步:连接上集群(连接集群中任意一个可用结点都行) ./redis-cli --cluster reshard 192.168.72.128:7007 # 第二步:输入要分配的槽数量 输入:3000,表示要给目标节点分配3000个槽 How many slots do you want to move (from 1 to 16384)? 3000 # 第三步:输入接收槽的结点id What is the receiving node ID? 50b073163bc4058e89d285dc5dfc42a0d1a222f2 #PS:这里准备给7007分配槽,通过cluster nodes查看7007结点id为: 50b073163bc4058e89d285dc5dfc42a0d1a222f2 # 第四步:输入源结点id Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. #输入 all # 第五步:输入yes开始移动槽到目标结点id
添加从节点示例:
-
添加7008从结点,将7008作为7007的从结点
./redis-cli --cluster add-node 新节点的ip和端口 旧节点ip和端口 --cluster-slave -- cluster-master-id 主节点id 如下: ./redis-cli --cluster add-node 192.168.72.128:7008 192.168.72.128:7007 -- cluster-slave --cluster-master-id 6ff20bf463c954e977b213f0e36f3efc02bd53d6
注意:如果原来该结点在集群中的配置信息已经生成到cluster-config-file指定的配置文件中(如果cluster-config-file没有指定则默认为nodes.conf)这时可能会报错
解决方法是删除生成的配置文件nodes.conf,删除后再执行./redis-cli–clusteradd-node指令
-
-
缩容
./redis-cli--clusterdel-node192.168.127.128:7008 6be94480315ab0dd2276a7f70c82c578535d6666
删除已经占有hash槽的结点会失败,需要将该结点占用的hash槽分配出去。
容灾:
-
故障检测
集群中的每个节点都会定期地(每秒)向集群中的其他节点发送PING消息 如果在一定时间内(cluster-node-timeout),发送ping的节点A没有收到某节点B的pong回应,则A将B 标识为pfail。 A在后续发送ping时,会带上B的pfail信息,通知给其他节点。 如果B被标记为pfail的个数大于集群主节点个数的一半(N/2+1)时,B会被标记为fail,A向整个集群广播,该节点已经下线。 其他节点收到广播,标记B为fail。
-
从节点选举
raft,每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。 slave通过向其他master发送FAILVOER_AUTH_REQUEST消息发起竞选, master收到后回复FAILOVER_AUTH_ACK消息告知是否同意。 slave发送FAILOVER_AUTH_REQUEST前会将currentEpoch自增,并将最新的Epoch带入到 FAILOVER_AUTH_REQUEST消息中,如果自己未投过票,则回复同意,否则回复拒绝。 所有的Master开始slave选举投票,给要进行选举的slave进行投票,如果大部分masternode(N/2+ 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master。 RedisCluster失效的判定: 1、集群中半数以上的主节点都宕机(无法投票) 2、宕机的主节点的从节点也宕机了(slot槽分配不连续)
-
变更通知
当slave收到过半的master同意时,会成为新的master。此时会以最新的Epoch通过PONG消息广播自己成为master,让Cluster的其他节点尽快的更新拓扑结构(node.conf)。
-
主从切换
1) 自动切换 就是上面讲的从节点选举 2) 手动切换 人工故障切换是预期的操作,而非发生了真正的故障,目的是以一种安全的方式(数据无丢失)将当前 master节点和其中一个slave节点(执行cluster-failover的节点)交换角色 1、向从节点发送clusterfailover命令(slaveofnoone) 2、从节点告知其主节点要进行手动切换(CLUSTERMSG_TYPE_MFSTART) 3、主节点会阻塞所有客户端命令的执行(10s) 4、从节点从主节点的ping包中获得主节点的复制偏移量 5、从节点复制达到偏移量,发起选举、统计选票、赢得选举、升级为主节点并更新配置 6、切换完成后,原主节点向所有客户端发送moved指令重定向到新的主节点 以上是在主节点在线情况下。 如果主节点下线了,则采用cluster fail over force或cluster failover takeover进行强制切换
-
副本漂移
我们知道在一主一从的情况下,如果主从同时挂了,那整个集群就挂了。 为了避免这种情况我们可以做一主多从,但这样成本就增加了。 Redis提供了一种方法叫副本漂移,这种方法既能提高集群的可靠性又不用增加太多的从机。
Master1宕机,则Slaver11提升为新的Master1 集群检测到新的Master1是单点的(无从机) 集群从拥有最多的从机的节点组(Master3)中,选择节点名称字母顺序最小的从机(Slaver31)漂移到单点的主从节点组(Master1)。 具体流程如下(以上图为例): 1、将Slaver31的从机记录从Master3中删除 2、将Slaver31的的主机改为Master1 3、在Master1中添加Slaver31为从节点 4、将Slaver31的复制源改为Master1 5、通过ping包将信息同步到集群的其他节点