Redis高可用

Redis的高可用实现方案有哪些,为什么要选择官方的哨兵模式,自己开发自动化故障转移脚本有什么问题。redis cluster重定向有几种?redis数据分区...

主从复制的问题

一旦主节点发生故障,那么系统的写能力就会崩溃,这个时候需要手动的切换主节点,手动操作的显然不是一个明智的选择,所以redis官方提供了哨兵模式。

试想一下如果发生故障,人工操作的流程应该是怎么样的?

  1. 在多个从节点中选择一个节点作为新的主节点
  2. 对新的主节点执行slaveof no one
  3. 其他从节点切换到新的主节点并开始进行数据同步
  4. 主从节点重启后在同步新节点的数据

大多数手动操作流程都是以上几个步骤,需要人工介入的方案并不是一个高可用的方案。即使使用脚本代替人工操作的方案,但是还是需要考虑以下几个问题:

  • 主节点故障,需要手动切换主节点,同步应用方的主节点地址更新,以及其他从节点同步新主节点数据
  • 主节点的写能力受到单机的限制
  • 主节点的存储能力受到单机的限制

最后两个问题应该归类为分布式类别,但是由于主从复制模式也存在这种问题,所以也列举出来。主从复制最主要的问题还是第一个问题,手动切换如果不及时的话,会给应用方数据带来一定的错误,其次故障转义在实时性以及准确性上也无法得到保障。

Redis Sentinel

主节点发生故障时,Redis Sentinel能自动完成故障发现和转移。哨兵模式解决了哪些问题,为什么不直接使用脚本代替人工操作,要单独使用哨兵模式呢?

建议使用Redis 2.8以上的版本

自动化脚本仍然无法解决的问题?

  • 一、判断节点不可达的机制是否健全和标准(为什么哨兵模式中会有多个sentinel节点)
  • 二、怎么保证只有一个从节点晋升为主节点(多从节点的情况下)
  • 三、通知客户端更新主节点ip是否健壮

哨兵模式拓扑图

哨兵模式是为了解决主从复制一系列问题产生的,本质上是不会修改主从复制的结构,只是在其基础上进行扩展,也就是多了一些sentinel节点,其实这里的sentinel节点也是一种特殊的redis服务几点。

故障转移步骤

  1. 主节点出现故障,从节点与主节点的连接断开,主从复制失败。
  2. 每个sentinel节点通过定期监控查看主节点是否出现故障。
  3. 如果有多个sentinel节点都检测出主节点出现故障,则会选举出一个leader sentinel来负责故障转移的主要任务。
  4. 开始进行故障转移

第四步将之前的主节点变为从节点,是在该节点可以重启之后进行的操作。

  1. 完成故障转移

Sentinel实现的功能

要想完成以上的故障转移任务,sentinel节点必须要具有以下几个功能才行

  1. 监控:Sentinel节点会定期检测redis主节点以及其余的sentinel节点
  2. 通知:通知应用方修改最新的mater节点信息
  3. 主节点故障转移:实现从节点晋升为主节点
  4. 配置提供者:Redis Sentinel客户端中可以获取到主节点相关的信息

为什么sentinel节点要有多个?

  • 防止对主节点故障产生误判
  • 防止sentinel节点的单点故障

部署

先配置一个主从结构的集群,在此基础上配置哨兵节点,配置文件可以使用src目录下默认的sentinel.conf文件,不同节点的配置文件将端口修改一下。
启动方式:

  1. 使用redis-sentinel命令
redis-sentinel redis-sentinel-23679.conf
  1. redis-server 命令外加--sentinel 参数
redis-server redis-sentinel-23679.cconf --sentinel
  1. 使用info Sentinel查看配置详情

部署成功之后sentinel对应节点的配置文件会出现一些变化,parallel-syncs、failover-timeout配置参数,其次还会新增sentinel known-replica(从节点)、sentinel known-sentinel(其它sentinel节点)等。

配置解析

sentinel monitor <master-name> <ip> <port> <quorum>

ip和port表示主节点的地址,quorum和sentinel选举有关,在进行故障转移时会选举出一个leader进行故障转移,选举的算法为raft,quorum代表的是节点得到的票数只要大于quorum就会被选举为leader,但是quorum一般要大于半数。

sentinel down-after-milliseconds <master-name> <times>

这个代表的是sentinel leader节点发送给follower节点以及redis主从节点的超时时间,如果超过这个时间则判断该节点不可达。times设置越大应用的延迟越高,设置的过小有可能会造成节点不可达误判。

sentinel parallel-syncs <master-name> <nums>

表示选出新的主节点之后,允许多少个从节点同时进行复制操作,虽然复制操作时会fork出一个子进行程不会阻塞redis服务,但是会主节点所在机器的IO以及网络资源。

sentinel failover-timeout

有些复杂,暂时不做了解

sentinel auth-pass <master-name> <password>

如果主节点设置了requirepass参数配置,那么在部署哨兵服务的时候也要在配置上加上相关的权限校验配置。

sentinel notification <master-name> <script-path>
// 脚本demo
#!/bin/sh
#获取所有参数
msg=$*
# 报警脚本或者接口,将msg作为参数
exit 0

在故障转移期间,当出现警告级别的事时(客官下线,主管下线)会触发相应的脚本,执行脚本时会传递相应的参数信息,这些参数信息可以用来发送邮件通知开发人员。

sentinel client-reconfig-script <master-name> <script-path>
// 脚本demo
#!/bin/sh
#获取所有参数
msg=$*
# 报警脚本或者接口,将msg作为参数
exit 0

和notification脚本触发时间有些区别,该脚本是在故障转移成功之后触发。

关于notification和reconfig脚本限制条件

  • 可执行权限,即使用chmod 777修改权限
  • 必须是shell脚本,即包含shell脚本头(#!/bin/sh)
  • 脚本最大执行不可以超过60s,如果超过了就会被kill
  • 如果redis sentinel节点过多,不建议使用脚本的方式进行通知
  • exit 0正常退出,exit 1脚本稍后重试,exit 2(>=2)不会重试

多节点监控

这里是指如何使用sentinel监控多个redis主从复制集群,在介绍上面配置的时候,所有的配置都有一个master-name参数,如果想要监控多个集群,那么只需要将所有监控命令重新在一个配置文件中再写一遍,用master-name区分。

客户端

如果使用了哨兵模式的集群,那么对应的客户端也需要做调整,传统的jedis使用方式就已经不支持了需要被替换。具体的使用方式不会介绍,内容太多了不方便记忆,而且使用方式内容和redis的原理性的内容稍微有点不一样,后面单独记录在项目中怎么使用redis-sentinel集群模式。

sentinel实现原理

定时监控

  1. 10秒
    每隔10秒,每个sentinel节点会向主节点和从节点发送info命令获取最新的拓扑结构。

这就是为什么在配置sentinel节点的时候不需要添加从节点的信息了,对主节点使用info命令会返回主从复制的结构信息。

  1. 2秒
    每个sentinel节点(不包含redis服务节点)每隔两秒向redis数据节点上的_sentinel_:hello频道发送该sentinel节点对于主节点的判断以及当前sentinel节点的信息,并且每个sentinel还会订阅该频道来了解其他节点对于主节点的判断。主要作用:
  • 发现新的sentinel节点,将自己的信息保存在_sentinel_:hello频道上,共享给其他sentinel节点
  • sentinel交换主节点的状态,作为后面客官下线以及领导者选举的依据,从而使节点不可达的健全性得到保障。

这段完全摘自《Redis开发与运维》第九章5小节,自己总结的实在是无法直视,索性直接搬过来。之前在实践redis sentinel集群模式的时候,发现sentinel配置文件中并没有配置几个节点关联起来监控一个redis集群,但是在启动之后,却发现redis集群可以被一个sentinel集群节点监控,当时自己就比较懵,我想此处应该就可以解决当时的困扰。

  1. 1秒
    1秒的定时监控主要是用来做心跳检测的,sentinel节点每隔一秒钟会向所有节点(主从节点,其他sentinel节点)发送一次心跳。

redis sentinel监控图

本来准备摘自《Redis开发与运维》上的图,但是发现三张图有些占地方,所以就自己画了张图,由于画图软件的原因有两个地方画不出来就没有弄了,不过不影响整体。虽然有些复杂但是如果把这个屡清楚了三个定时任务具体的功能也就清楚了。

主/客观下线

主观下线
当sentinel节点向redis服务节点发送ping心跳时,在down-after-milliseconds内服务没有回复的话就会被当前sentinel节点作失败判定,这个过程称为主观下线。
客观下线
当被判定的节点是主节点的时候,那么该sentinel节点就会向其他节点询问主节点的状态,如果超过quorum个节点都判定为下线,那么就会做出客观下线的决定。

向其他sentinel节点询问master节点的状况使用的是sentine is-master-down-by-addr命令,这里不做介绍。

领导选举

当对master节点做出客观下线后,不会立马进行故障转移操作,首先会在所有的sentinel节点中选出一个leader来进行故障转移操作。通过raft算法选举出leader节点,关于raft算法书中介绍的不是很详细,这里提供两个资料帮助理解raft算法。

故障转移

在选出leader节点之后就会进行故障转移操作,故障转移操作也很简单,在进行转移的时候对从节点的信息做一些简单的判断,过滤出最优从节点晋升。具体过程如下:

  1. 选择出新的主节点
    1). 过滤出所有不健康的节点(主观下线、断线),5秒内没有回复过sentinel节点,以及与主节点失联超过down-after-milliseconds * 10秒。
    2). 判断有没有优先级高的从节点,即在配置文件中配置slave-priority属性的节点。
    3). 选择复制偏移量最大的节点,即数据最接近主节点的从节点。
    4). 选择runid最小的从节点(?)
  2. 对选择出来的从节点执行slaveof no one命令,和之前的手动操作类似。
  3. sentinel节点会向其余的从节点发送命令,成为新主节点的从节点。
  4. 持续关注已经挂掉的主节点,一旦联系上,就将他变为新的主节点的从节点。

Redis Cluster

redis cluster可以理解为是一种水平扩展的方式,将数据分散在多个节点上,解决单机内存瓶颈限制。

数据分布

在分布式缓存中,需要怎样将数据存储在多个节点中,并且读取时还能正常获取到对应的值?一般有以下方法来解决该问题,节点取余算法、一致性哈希算法以及redis使用的虚拟槽分区算法。

算法介绍

  1. 节点取余分区
    节点取余算法就是对其key进行hash,得到hash值后对其进行服务节点数取模,得出的值便是对应的服务器位置。

优点:

  • 易理解、便实现

缺点:

  • 无法支持节点伸缩,即加机器和减机器
  1. 一致性哈希分区
    首先抽象出一个哈希环,就是将一个环N(0~232)份,将key哈希取值后的值顺时针映射到哈希环上的点。

为什么要顺时针映射,hash(key)的值不一定会刚好落在哈希环的节点上,这里的节点即数据服务节点,这时需要找到最近的一个数据节点,其实不论顺时针还是逆时针都可以,但是必须要统一,否则在查询的时候就会获取不到数据。

优点:

  • 在只影响少量数据的情况下支持节点伸缩

缺点:

  • 实现起来稍微麻烦一点
  • 当节点数过少时,进行节点伸缩还是会影响到大部分数据
  1. 虚拟槽分区
    将数据和节点通过槽进行解耦,抽象出一个具有16384个槽位的点,并且每个数据服务节点均分这些槽的管理权,hash(key)的值先通过虚拟槽的映射,然后找到对应的存储数据节点。

优点:

  • 支持节点伸缩
  • 通过解耦的方法降低了节点伸缩的难度

缺点:

  • 不支持部分redis命令

搭建集群

使用redis命令手动搭建一下集群,体会一下吐血的感受😫,搭建一个简单的小集群,只需要启动6个数据服务节点,不需要太多,不过有几点要注意。下面将展示搭建集群中用到的命令以及注意点的地方:
命令:

cluster info

在redis客户端中使用这个命令可以查看到当前集群的信息,该命令只能查看到客户端连接集群节点信息,其他节点不能查看

cluster meet {host} {ip}

节点握手,使用该命令可以将一个新节点添加到集群中

cluster nodes

查看整个集群的拓扑信息,可以在输出的信息中了解到整个集群的节点信息、主从角色信息以及虚拟槽位点

cluster replicate nodeId

在集群中一般每个节点都会有一个从节点,主要是防止单点故障,使用该命令可以是当前节点与指定nodeId的节点建立主从关系,当前节点为从节点。当前节点需要已经添加到集群环境中。

redis-cli -h {host} -p {port} cluster addslots {0..16838}

在建立好集群关系后,还要为每个客户端分配槽,这里只对集群中主节点进行分配,只有当分配完16383个槽位之后集群才会处于上线状态。{a..b}为批量添加,即将a~b个槽位添加到节点中,且为闭区间,删除槽位可以使用delslots命令。

cluster forget nodeId

将节点移除集群环境,一般在集群下线之后会将使用该命令退出集群。

cluster keyslot key

计算出当前key的槽位,在集群环境中添加数据只有key属于那个槽位才能添加到节点中,否则会出现MODED错误信息。

除了手动搭建节点,还可以使用官方提供的工具包搭建,但是要安装ruby环境,所以这里就不多做记录。

注意:

  1. 所有槽点都需要分配完才能进行读写
  2. 在本机搭建集群的时候最好是将所有的关于集群的配置文件烦放在一个目录下,方便管理
  3. 搭建好集群后,可以添加点数据到集群中,但是如果添加的数据,经过计算不属于当前节点是添加不进去的,会提示一个MOVED 10850 127.0.0.1:6380类似的错误,表示这个数据的槽位是10850,属于6380节点

配置:

port 6380
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dbfilename dump-6379.rdb
cluster-enabled yes
cluster-config-file nodes-6380.conf
cluster-node-timeout 15000

一般集群实在多机器上部署的,但是我在测试的时候实在本机部署的,所以会增加一写关于端口的配置

节点通信

redis分布式集群中会通过维护节点元数据信息来保证集群的可用性,常见的元数据维护方式有集中式和P2P两种,redis采用的是后者,以及基于Gossip协议实现节点通信。

集群伸缩

暂时不做学习记录,不知道要记录哪些内容,感觉都是命令操作。

请求路由

  1. 请求重定向(MOVED)
    在redis cluster集群环境中,如果将一个数据添加到集群中出现了MOVED错误提示,则表示当前节点不接受该数据,需要将数据添加到对应槽的节点中,这个过程称为请求重定向,在使用redis-cli启动客户端的时候,可以添加-c参数表示开启请求重定向,默认的客户端是不支持的。

  2. hash_tag
    槽位是根据CRC16函数计算出来的,但是计算的内容并不是整个key,只计算有效内容。有效内容是通过{}包含起来的,这部分内容叫做hash_tag,可以使用hash_tag来进行优化,比如涉及到类似mget这种命令的,一次性获取多个key,但是一般多个key是在多个节点上的,mget是不支持这种场景的,使用hash_tag可以保证将多个key映射到同一个槽中。

  3. ASK重定向
    ASK重定向是指发生在槽迁移的过程中,槽中的数据还没有迁移完成,数据一部分在源节点中,一部分在目标节点中。ASK重定向时客户端的命令流程:

  4. client根据本地slots缓存发送命令到source节点,如果存在则直接执行命令发挥结果给客户端。

  5. 如果此时键不存在source节点,则会向客户端响应ASK异常信息。

(error) ASK {slot} {targetIP}:{targetPort}

  1. client从异常提示新提取出节点关键信息,发送asking名到目标节点,如果key存在则返回数据,否则返回不存在信息

之所以命名为ASK重定向,大概就取自asking这个命令吧

目前对于客户端的重定向分为ASK重定向和MOVED重定向,但是两种重定向在slots缓存上有着本质的区别,ASK重定向不会更新本地的slots缓存,MOVED则会。ASK只是零时的重定向,不确定具体什么时候完成所以根本不需要更新slots缓存。