Redis 高可用


高可用方案

主从复制

redis支持主从复制,可以通过执行slaveof(redis5 以后改为replicaof)或者在配置文件中设置slaveof(replicaof)来开启复制功能。 住对外从对内,主可写从不可写,主挂了,从不可以为主

主从配置

主redis 配置

无需特殊配置

从Redis 配置

修改从服务器上的redis.conf 文件: # slaveof replicaof 127.0.0.1 6379

作用

读写分离

  1. 一主多从,主从同步
  2. 主负责写,从负责读
  3. 提升redis的性能和吞吐量
  4. 主从的数据一致性问题

数据容灾

  1. 从机是主机的备份
  2. 主机宕机,从机可读不可写
  3. 默认情况下主机宕机后,从机不可以为主
  4. 利用哨兵可以实现主从切换,做到高可用

原理与实现

复制流程

保存主节点信息

当客户端向从服务器发送slaveof(replicaof)主机地址和port 端口时,从服务器将主机ip和端口保存到redisServer和masterhost和masterport中。

struct redisServer {
    char * masterhost;
    int masterport;
}

从服务器将向发送slaveof 命令的客户端返回ok,表示复制指令已经被接受,而实际上复制工作是在ok之后进行的

建立socket 连接

  1. slaver与master 建立socket 连接
  2. slaver 关联文件事件处理器
  3. 该处理器接受RDB文件(全量复制),接收master 传播来的写命令(增量复制)
  4. 主服务器accept 从服务器socket连接后,创建相应的客户端状态,相当于从服务器是主服务器的client端。

发送ping

Slaver 向 master 发送 ping命令

  1. 检测socket 的读写状态
  2. 检测master 能否正常处理

master的响应

  1. 发送pong 说明正常
  2. 返回错误,说明master 不正常
  3. timeout 说明网络超时

权限验证

  1. 主从正常连接后,进行权限验证
  2. 主未设置密码(requirepass=""),从也不用设置密码(masterauth="")
  3. 主设置密码(requirepass=""),从也要设置密码(masterauth=主的requirepass的值)
  4. 或者从通过auth命令向主发送密码

发送端口信息

在身份验证步骤之后,从服务器将执行命令replconf listening-port 向主发送从的监听端口号

同步数据

2.8 之后分为全量同步和增量同步

命令传播

当同步数据完成后,主进入命令传播阶段,主只要将自己执行的写命令发送给从服务器,而从服务器只要一直执行并接受主服务器发送的写命令。

同步数据集

主-》从 1.同步数据(第一次) RDB 文件 2. 命令传播(增量) set 主服务器命令,会在从服务器再执行一次 另外 2.8 之前使用SYNC命令同步复制,2.8 以后采用PSYNC 命令 替代sync

旧版本

2.8 以前

实现方式

redis 的同步功能分为同步(sync)和命令传播(command propagate)。 同步操作: 通过从服务器发送到sync命令给主服务器。 主服务器生成RDB 文件并发送给从服务器,同时发送保存所有写命令给从服务器 从服务器清空之前数据并执行解释RDB文件 保持数据一致(还需要命令传播过程才能保持一致) 主服务器-》从服务器 发sync命令,发送RDB文件,发送缓冲区保存的所有写命令。 命令传播操作: 同步操作完成后,主服务器执行写命令,该命令发送给从服务器并执行,是主从保存一致。 缺点: 没有全量同步和增量同步的概念,从服务器在同步时,会清空所有数据, 主从服务器断线后重复制,主服务器会重新生成 RDB文件和重新记录缓冲区的所有命令,并全量同步到从服务器上。

新版

redis 2.8 之后

实现方式

在redis 2.8 之后使用psync 命令,具备完整同步和部分同步模式

  1. redis 的主从同步,分为全量同步和增量同步
  2. 只有从机第一次连接上主机是全量同步。
  3. 断线重连后有可能触发全量同步也有可能是增量同步 通过master判断runid 是否一致。
  4. 除此之外都是增量同步

全量同步

  1. 同步快照阶段: master创建并发送快照RDB 给slave ,slave 载入并解析快照,master 同时将此阶段所产生的新命令存储到缓冲区。
  2. 同步写缓冲阶段: master 向slave 同步存储在缓冲区的写操作命令。
  3. 同步增量阶段: master向slave 同步写操作命令。

增量同步

  1. redis 增量同步主要指slave 完成初始化后开始正常工作时,master发生的写操作同步到slave 的过程。
  2. 通常情况下: master 每执行一个写命令就会向slave 发送相同的写命令,然后slave 接受并执行。

心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率向主服务器发送命令: replconf ack 主要作用有三个:

  1. 检测主从的链接状态 检测主从服务器的网络连接状态 通过向主服务器发送info replication 命令,可以列出从服务器列表,可以看出从最后一次向主发送命令距离现在过来多少秒,lag的值应该在0或者1 之间跳动,如果超过1 则说明主从之间的链接有故障。
  2. 辅助实现min-slaves redis 可以通过配置防止主服务器在不安全的情况下执行写命令 min-slaves-to-write 3 (min-replicas-to-write 3) min-slaves-max-lag 10 (min-replicas-max-lag 10) 上述配置表示,从服务器的数量少于3个,或者三个从服务器的延迟值 都大于或等于10s,主服务器将拒绝执行写命令,这里的延迟值就是上面的INFOreplication命令的lag值
  3. 检测命令丢失 如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么当从服务器向主服务器发送,replconf ack 命令时,主服务器将发觉 从服务器当前的复制偏移量少于自己的复制偏移量,然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到从服务器缺少的数据,并将这些数据 重新发送给从服务器。(补发) 网络不断 增量同步:网断了,再次接连时

哨兵模式

哨兵(sentinel)是redis 的高可用性(High Availability) 的解决方案: 由一个或多个sentinel实例组成sentinel 集群可以监视一个或多个主服务器和多个从服务器。 当主服务器进入下线状态时,sentinel 可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证REDIS的高可用性。

部署方案

采用安装方式,正常安装和配置

```
    mdkir 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
    //修改redis.conf 
    demonize yes
    protected-mode no

```

redis-salver1 127.0.0.1 6380

    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

执行流程

启动并初始化sentinel

sentinel 是一个特殊的redis服务器,不会进行持久化,sentinel 实例启动后,每个sentinal会创建2个连向主服务器的网络连接 命令连接:用于向主服务器发送命令,并接受响应,订阅链接: 用于订阅主服务器的sentinel hello 频道

获取主服务器信息

Sentinel 默认10s 一次,向被监控的主服务器发送info命令,获取主服务器和其下属从服务器的信息

获取从服务器信息

当sentinel 发现主服务器有新的从服务器出现时,sentinel 还会向从服务器建立命令连接和订阅链接,在定义链接后,sentinel还是默认10s 一次,向从服务器发送info命令,并记录从服务器的信息。

向主服务器和从服务器发送信息(已订阅的方式)

默认情况下,sentinel每2s一次,向所有被监视的主服务器和从服务器锁订阅的-sentinel-:hello 频道上发送消息, 消息中会携带sentinel自身的信息和主服务器的信息。 publish sentinel:hello : "< s_ip > "

接受来自主服务器和从服务器的频道信息

当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 is-master-down-by-addr 其他sentinel 回复: 判断他们是否也认为主服务器下线,如果达到Sentinel 配置中的quorum数量的sentinel 实例都判断主服务器为主观下线,则主服务器就会被判定为客观下线(ODown)

选举 leader sentinel

当一个服务器被判定为客观下线后,监视这个主服务器的所有sentinel 会通过选举算法(raft) 选出一个 leader Sentinel 去执行failover(故障转移)操作。

哨兵leader 选举

raft

raft协议使用来解决分布式系统一致性问题的协议。raft协议描述的结点共有三种状态:leader,follower,candidate. term:raft协议将时间切分为一个个的Term(任期) 可以认为是一种“逻辑时间” ### 选举流程 raft 采用心跳机制触发Leader 选举,系统启动后,全部结点初始化为Follower,term 为0.结点如果收到了requestVote 或者 AppendEntries 就会保持自己的follower 身份,结点如果一段时间内没收到AppendEntries 消息,在该节点的超时时间内还没 发现leader,Follower 就会转换成Candidate,自己开始竞选Leader. 一旦转为Candidate ,该节点立即开始下面几件事件:

  1. 增加自己的term
  2. 启动一个新的定时器
  3. 给自己投一票
  4. 向所有其他节点发送requestVote ,并等待其他节点的回复。

如果在超时器超时前,结点收到多数结点的同意投票,就转换成leader,同时向所有其他节点发送AppendEntries,告知自己成为了Leader

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 的监控目标会随之调换。

## 主服务器的选择 哨兵Leader根据以下规则从客观下线的主服务器的从服务器中选择出心得主服务器。

  1. 过滤掉主观下线的结点
  2. 选择slave-priority 最高的节点,如果有,则返回,没有就继续选择
  3. 选择出复制偏移量最大的系结点。因为复制偏移量越大则数据复制的越完整,如果有就返回了,没有就继续
  4. 选择run_id 最小的节点,因为run_id 越小说明重启次数越少。

## 集群和分区 分区是将数据分布在多个redis实例(redis 主机)上,以至于每个实例只包含一部分数据。 ### 分区的意义 性能的提升 单机redis的网络I/O能力和计算资源是有限的,将请求分散到多台机器,充分利用多台机器的计算能力和网络带宽,有助于提供redis总体的服务能力。 存储能力的横向拓展 即使redis的服务能力能够满足应用需求,但是随着存储数据的增加,单台机器受限于机器本身的存储容量,将数据分散到多台机器上存储使得redis服务可以横向扩展。

分区的方式

根据分区键(id)进行分区:

范围分区

根据id数字的范围比如1--10000,10001-20000,......900001-100000,每个范围分到不同的redis 实例中 好处 实现简单,方便迁移和扩展 缺陷 热点数据分布不均,性能损失 非数字型key,比如uuid无法使用(可以采用雪花算法代替) 分布式环境 主键 雪花算法 是数字 能排序

Hash 分区

利用简单的hash 算法即可:
redis 实例 = hash(key) % N
key:要进行分区的键,比如user_id
N:Redis 实例个数(redis主机)
好处
    支持任何类型key
    热点分布较均匀,性能较好
缺点:
    迁移复杂,需要重新计算,扩展性比较差(利用一致性hash 环)

client 端分区

对于一个给定的key,客户端直接选择正确的节点来进行读写,许多redis 客户端都实现了客户端的分区,也可以自行编程。

客户选择算法

普通hash,hash(key) % N,hash :可以采用hash 算法,比如 crc32,crc16等 N 是redis 主机个数 比如: user_id :u001 hash(u001) :1844213068 redis 实例=1844213068%3 余数为2 ,选择redis3

普通hash的优势

实现简单,热点数据分布均匀

普通hash 的缺陷

节点数固定,扩展的话需要重新计算,查询时必须用分片的key 来查,一旦 key改变,数据就查不出了,所以要使用不易改变的key 进行分片

一致性hash

普通hash 是对主机数量取模,一致性hash是对2^32取模,我们把2^32 想象成一个圆,就像钟表一样,由2^32 个点组成。 hash(服务器IP地址) % 2^32 hash(key) % 2^32 现在服务器与数据都被映射到了hash 环上,从数据位置开始,沿着顺时针方向遇到的第一个服务器就是A服务,所以数据被缓存到服务器A上 , 将缓存服务器与被缓存服务对象都映射到了hash 环上,从被缓存对象的位置出发,沿着顺时针方向遇到的第一个服务器就是当前对象将要缓存的服务器 由于被缓存与服务器hash 后的值是固定的,所以在服务器不变的情况下,数据必定会被缓存到固定的额服务器上,那么当下次要访问这个数据时,只要再次 使用相同的算法进行计算,即可算出这个数据被缓存在哪个服务器上。

 优点
    添加或者移除节点时,只需要做部分的迁移,比如把C 服务器移除,则相应的数据会迁移到重新hash 的服务器中,而其他数据保持不变,添加效果是一致的。
缺点
    hash 偏移。大量的数据在某个服务器中
    理论上增加服务器解决
    实际上使用虚拟节点,在hash 环上的复制品,一个实际节点对应多个虚拟节点
缺点
    客户端要自己处理数据路由,高可用,故障转移等问题
    使用分区,数据的处理变得复杂,不得不对付多个redis数据库与AOF 文件,不得在多个实例和主机之间持久化数据
    不易扩展,一旦节点的增或删除操作,都会导致key 无法在 redis中命中,必须重新根据节点计算,并手动迁移全部或部分数据。
proxy 端分区
    在客户端和服务端引入一个代理或代理集群,客户端将命令发送到代理上,代理根据算法,将命令路由到相应的服务器上,常见的代理有codis 和 twemproxy

官方cluster 分区

3.0 之后,采用去中心化分区,包括 sharding 分区,replication 复制,failover 故障转移 成为rediscluster, 5.0 前采用redis-trib 进行集群的创建和管理,需要ruby支持5.0 后采用redis-cli 来处理

去中心化

RedisCluster 由多个redis节点组成,是一个p2p无中心节点的集群架构,依靠gossip协议传播的集群

Gossip 协议

Gossip 协议是一个通信协议,一种传播消息的方式,起源于病毒传播
思想:
    一个节点周期性(每秒)随机选择一些节点,并把消息传递给这些节点
    这些收到信息的节点接下来会做同样的事情,即把这些消息传递给其他一些随机选择的节点
    信息会周期性的传递给N个目标节点,这个N被称为fanout(扇出)
    gossip 协议包含多种消息 包括 meet,ping,pong,fail,publish 等等。

meet: sender 向receiver 发出,请求receiver 加入 sender 的集群
ping: 节点检测其他节点是否在线
pong:  receiver收到meet或ping后的回复信息,在failover后,新的master 也会广播pong
fail: 节点A判断节点B下线后,A节点广播B的fail信息,其他收到节点会将B节点标记下线
publish:节点A收到publish 命令,节点A执行该命令,并向集群广播publish 命令,收到publish 命令的节点都会执行相同的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 会根据节点数量大致均等的将哈希槽映射到不同的节点 slot槽必须在节点上连续分配,如果出现不连续的情况,则rediscluster 不能工作

rediscluster 优势

  1. 高性能,和单节点部署是同级别的,多主节点,负载均衡,读写分离
  2. 高可用,支持标准的主从复制配置来保障高可用和高可靠,failover 情况下,cluster 也实现一个类似raft的共识方式,来保障可用性
  3. 易拓展,添加新节点,或者移除节点都是透明的,不需要停机,水平垂直方向都非常容易拓展,数据分区,海量数据,数据存储
  4. 原生,部署不需要其他的代理或者工具,和单机redis 几乎完全兼容

集群搭建

至少三台主服务器,三台从服务器 7001-7006

分片

不同节点分组服务于相互无交集的分片, cluster不存在单独的proxy 或配置服务器,所以需要将客户端路由到目标的分片

客户端路由

redis cluster 的客户端相比单机需要具备路由语意的识别能力,且具备一定的路由缓存能力。

moved 重定向

  1. 每个节点通过通信都会共享cluster 中的槽和集群中对应节点的关系
  2. 客户端向cluster 的任意节点发送命令,接受命令的节点会根据crc16规则进行hash运算与16384取余
  3. 如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端
  4. 如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常
  5. 客户端接收到节点返回结果,如果是moved 异常,则从moved 异常中获取目标节点的信息
  6. 客户端向目标节点发送命令,获取命令执行结果

ask 重定向

在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移 当客户端向某个节点发送命令,节点向客户端返回moved 异常,告诉客户端数据对应槽的节点信息 如果此时正在进行集群扩展或缩空操作,当客户端向正确的节点发送命令时,槽以及槽中数据已经被迁移到别的节点,就会返回ask,这就是ask重定向机制

  1. 客户端向目标节点发送命令,目标节点中的槽已经迁移至别的节点上,此时目标节点会返回ask转给客户端
  2. 客户端向新的节点发送asking 命令给新的节点,然后再次向新节点发送命令
  3. 新节点执行命令,把命令执行结果返回给客户端。

moved 和ask 区别

moved 槽已经确认转移,ask 槽还在转移过程中。

Smart 智能客户端 jedisCluster

  1. jedisCluster 是jedis 根据cluster的特性提供的集群只能客户端
  2. jedisCluster 为每个节点创建链接池,并跟节点建立映射关系缓存(cluster slots)
  3. jedisCluster 将每个主节点负责的槽位---与主节点连接池建立映射缓存
  4. jedisCluster 启动时,已经知道key,slot 和node 之间的关系,可以找到目标节点
  5. jedisCluster对目标节点发送命令,目标节点直接相应给jedisCluster jedisCluster如果与目标节点连接出错,则jedisCluster会知道连接的节点是一个错误的节点 此时节点返回moved异常给jedisCluster jedisCluster会重新初始化slot与node节点的缓存关系,然后向新的目标节点发送命令,目标命令执行命令并行jedisCluster响应 如果命令发送次数超过5次,则抛出异常“too many cluster redirection"

容灾(failover)

故障检测

  1. 集群中的每个节点都会定期地(每秒)向集群中的其他节点发送ping消息
  2. 如果在一定时间内(cluster-node-timeout)发送ping的节点A 没有收到节点B的pong回应,则A将B标志位pfail.
  3. A在后续发送ping时,会带上B的pfail信息,通知给其他节点。
  4. 如果B被标记为pfail 的个数大于集群主节点个数的一半(N/2+1)时,B会标记为fail,A 向整个集群广播,该节点已经下线。
  5. 其他节点收到广播,标记B为fail

从节点选举

  1. raft,每个从节点,都根据自己对master 复制数据的offset,来设置一个选举时间,offset越大的从节点,选举时间越靠前,优先进行选举。
  2. slave 通过向其他 master发送failover_auth_request 消息发起竞选。
  3. master收到后回复 failover_auth_ack 消息告知是否同意。
  4. slave 发送failover_auth_request 前会将currentEpoch 自增,并将最新的epoch 带入到failover_auth_request 消息中,如果自己微投票,则回复同意,否则回复拒绝 所有的master 开始slave 选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master

redisCluster 失效的判定: 集群中半数以上的主节点都宕机了 宕机的主节点的从节点也宕机了

变更通知

当slave收到过半的master同意时,会成为新的master此时会以最新的epoch 通过pong 消息广播自己成为master,让其他节点尽快更新拓扑结构node.conf

主从切换

自动切换:节点选举
手动切换。 
    人工故障切换是预期的操作,而非发生了真正故障,目的是用安全的方式将当前master 和其中一个slave节点交换角色
        向从节点发送cluster failover 命令 (slaveof no one)
        从节点告知其主节点要进行手动切换(clustermsg_type_mfstart)
        主节点会阻塞所有客户端命令的执行(10s)
        从节点从主节点的ping保中获取主节点的复制偏移量
        从节点复制达到偏移量,发起选举,统计选票,赢得选举,升级为主节点并更新配置
        切换完成后,原主节点向所有客户端发送moved指令重定向到新的主节点,
如果主节点下线了,则采用cluster failover force 或者 cluster failover takeover 进行强制切换

副本漂移

master1 宕机,则salver1 提升为新的master1 ,但是如果master1 是单点没有从机。 集群从拥有最多的从机的节点组中,选择节点名称字母顺序最小的从机飘逸到单点的主从节点组 将slaver21的从机记录从master2 删除,将slaver21 的主机改为master1 ,在master1 中添加slavery21为从节点, 将slaver21 复制源改为master1 ,通过ping包将信息同步到集群的其他节点。

迁移

在redisCluster 中每个slot对应的节点在初始化就是确定的,在某些情况下,节点和分片需要变更: 新的节点作为master加入 某个节点分组需要下线 负载不均衡需要调整slot分布 此时需要进行分片的迁移,迁移的触发和过程控制由外部系统完成。 节点迁移状态设置:迁移前标记源/目标节点 key迁移的原子化命令:迁移的具体步骤 向节点B发送状态变更命令,将B的对应slot状态设置为importing 向节点A发送状态变更命令,将A对应slot 状态设置为migrating 向A发送migrate命令,告知A将要迁移的slot 对应的key 迁移到B 当所有key迁移完毕后,cluster setslot 重新设置槽位