redis-sentinel

Sentinel

Sentinel 是 Redis高可用性的保障:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器以及这些主服务器属下的所有从服务器,并在被监视的主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

启动并初始化Sentinel

1
redis-sentinel /path/to/your/sentinel.conf

当一个Sentinel启动时,它需要执行以下步骤:

  1. 初始化服务器
  2. 将普通Redis服务器使用的代码替换成Sentinel专用代码
  3. 初始化Sentinel状态
  4. 根据给定的配置文件,初始化Sentinel的监视主服务器列表(从服务器后文会提到)
  5. 创建连向主服务器的网络连接

初始化服务器

sentinel本质上是一个Redis服务器

但是Sentinel执行的工作和普通的Redis节点而言不太一样,因此在初始化时不会像普通的服务器载入RDB文件或AOF文件来还原数据库状态。

替换Sentinel的专用代码

Sentinel的会将普通Redis服务器使用的代码进行专用代码替换,Sentinel会采用专门的Sentinelcmds作为服务器的命令表,因此在Sentinel模式下,一些SET等命令无法使用

初始化Sentinel状态

服务器的一般状态由redisServer结构保存,Sentinel服务器由sentinelState结构保存

1
2
3
4
5
6
7
8
9
10
11
12
13
struct sentinelState {
/*
核心字段,保存了所有被这个sentinel监视的主服务器
字典的键时主服务器的名字
字典的值则是一个指向sentinelRedisInstance结构的指针
*/
dict *masters;

int tilt;
mstime_t tilt_start_time;
list *scripts_queue
...
}

初始化Sentinel状态的masters属性

在上文,我们提到了一个dict结构,保存了主服务器名和对应的主服务器信息,这里介绍以下sentinel中主服务器的info结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct sentinelRedisInstance {
// 标识位
int flag;

// 实例的名字
// 主服务器的名字由用户在配置文件中设置
// 从服务器以及Sentinel的名字由Sentinel自动设置 ip:port
char *name;
...
// 自定义结构体{ip, port}
sentinelAddr* addr;
...

} sentinelRedisInstance;

这里注意sentinelRedisInstance是保存主服务器和从服务器的实例信息,但是sentinel初始化时只会连接主服务器,这里存有疑点,后面会有解答。

创建连向主服务器的网络连接

Sentinel会创建两个连向主服务器的异步网络连接:

  • 命令连接,这个连接用于向主服务器发送命令,并接收命令回复
  • 订阅连接,订阅主服务器的_sentinel_:hello频道

简单来说sentinel通过命令连接向服务器的频道发送信息,又从订阅连接,通过订阅频道接收信息,目前可以简单理解成:多个sentinel可以通过频道发布信息,使得订阅该频道的sentinel同时接收,注意到我们配置sentinel时是没有确定其他sentinel信息的,通过这种方式可以建立sentinel集群之间的通信。后文还有更加具体的用法。

获取主服务器信息

Sentinel默认会以每秒十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,主服务器会返回一些信息包括了主服务器自身的信息,还有主服务器属下所有从服务器的信息,因此sentinel无须用户提供从服务器的地址信息, 就可以自动发现这些从服务器。

获取从服务器信息

当sentinel发现主服务器有新的从服务器出现时,sentinel除了会为这个新的从服务器创建从服务器得命令连接和订阅连接,和主服务器基本一致

向主服务器和从服务器发送信息

在默认情况下,sentinel 会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送命令

1
PUBLISH _sentinel_:hello xxx xxx

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

每个Sentinel订阅了服务器的_sentinel_:hello频道,因此会收到其他sentinel发送的信息,进行对监视服务器信息的更新

这里更新的信息就是对sentinels字典的更新,简单来说就是有的实例则更新实例结构,若没有则添加

同时通过频道信息发现一个新的sentinel时,会为sentinel在sentinels字典中创建对应的实例结构,同时创建一个命令连接(用于主观下线检测和客观下线检测)

我们知道sentinel之间可以通过频道通信,为什么还需要建立互相之间的命令连接呢?私认为,是使用场景不同,命令连接用于一对一,频道连接一对多

下线检测

在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例发送PING命令,并通过实例返回的PONG命令判断实例是否存活。如果超时未收到实例的响应信息,则认为主观下线

当一个sentinel认为检测的主服务器主观下线时,会发送

1
sentinel is-master-down-by-addr xxx xxx 

给其他服务器,其他服务器会返回同意/不同意下线,当收到总票数达到阈值qourum时则会认为该节点客观下线

当发生客观下线时,会进行sentinel选举,这里的选举算法就是raft算法

从raft的角度理解选举过程:

  1. 每个sentinel都是follower,有机会成为leader
  2. 当发生sentinel选举的时候,会把sentinelInstance中的epoch + 1(类似termID)
  3. candidate sentinel会向其他sentinel发送sentinel命令
  4. follower sentinel投票的原则是先到先得,先接收到的会将sentinel设置为自己的局部leader
  5. 如果有半数以上的sentinel设置为局部领头sentinel,那么这个sentinel就会成为领头sentinel
  6. 同样的如果超时并未选出leader,则重新新选举

故障转移

故障转移包括三个步骤:

  1. 在已下线的主服务器属下的所有从服务器里面,挑选初一个从服务器,并将其转化为主服务器
  2. 让已下线的主服务器属下的所有从服务器复制新的主服务器
  3. 将已下线的主服务器设置为新的主服务器的从服务器

这里如何选择从服务器呢?

  1. 保留所有数据比较新的从服务器
  2. 选择优先级高的服务器
  3. 选择偏移量大的服务器
  4. 选择runID小的服务器

这里可以看出来如果发生主服务器故障,会导致数据不一致,redis集群本身就是AP

对选中的从服务器发送slave of no one(你自由啦!!!)
将其他从服务器发送slave of xxx(变为刚才服务器的从服务器)
当宕机的主服务器重新上线时发送slave of(也变为从服务器)