文章目录
说明
本篇文档基于redis3.0.2版本,如果涉及到其他版本会单独说明
Redis 简介
由来
2008年,意大利的一家创业公司Merzia推出了一款基于MySQL的网站实时统计系统LLOOGG,然而没过多久该公司的创始人Salvatore Sanfilippo便对MySQL的性能感到失望,于是他决定亲自为LLOOGG量身订做一个数据库,并于2009年开发完成,这个数据库就是Redis。
不过Salvatore Sanfilippo并不满足只将Redis用于LLOOGG这一款产品,而是希望更多的人使用它,于是在同一年Salvatore Sanfilippo将Redis开源发布,并开始和Redis的另一名主要的代码贡献者Pieter Moordhuis一起继续着Redis的开发。
Salvatore Sanfilippo自己也没想到,短短的几年时间,Redis就拥有了庞大的用户群体。Hacker News在2012年发布了一份数据库的使用情况调查,结果显示有近12%的公司在使用Redis。国内如新浪微博,街旁网,知乎网,国外如GitHub,Stack Overflow,Flickr等都是Redis的用户。
VMware公司从2010年开始赞助Redis的开发,Salvatore Sanfilippo和Pieter Noordhuis也分别在3月和5月加入VMware,全职开发Redis。
为什么Redis效率那么高
1.完全基于内存,绝大部分请求是纯粹的内存操作,数据存在内存中。
2.数据结构简单,对数据的操作也简单,Redis中的数据结构是专门设计的,详解Redis内存模型。
3.采用了I/O多路复用模型,非阻塞IO,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争,不用考虑锁的问题,不存在加锁释放锁的操作,也没有存在死锁的情况。
Nosql数据库对比
1.性能
三者的性能都比较高,总的来讲:Memcache和Redis差不多,要高于MongoDB。
2.便利性
memcache数据结构单一。
redis丰富一些,数据操作方面redis更好一些,较少的网络IO次数。
mongodb支持丰富的数据表达,索引,最类似关系型数据库,支持的查询语言非常丰富。
3.存储空间
redis在2.0版本后增加了自己的VM特性,突破物理内存的限制;可以对key value设置过期时间(类似memcache)。
memcache可以修改最大可用内存,采用LRU算法。
mongoDB适合大数据量的存储,依赖操作系统VM做内存管理,吃内存也比较厉害,服务不要和别的服务在一起。
4.可用性
redis支持主从集群,分片集群可用性较高,哨兵集群可用性较高
Memcache本身没有数据冗余机制,也没必要;对于故障预防,采用依赖成熟的hash或者环状的算法,解决单点故障引起的抖动问题。
mongoDB支持master-slave,replicaset(内部采用paxos选举算法,自动故障恢复),auto sharding机制,对客户端屏蔽了故障转移和切分机制。
5.可靠性
redis支持(快照、AOF):依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响。
memcache不支持,通常用在做缓存,提升性能。
MongoDB从1.8版本开始采用binlog方式支持持久化的可靠性。
6.一致性
Memcache 在并发场景下,用cas保证一致性。
redis事务支持比较弱,只能保证事务中的每个操作连续执行。
MongoDB从4.0开始支持 multi-document ACID事务
各个Nosql数据库的优缺点
1.Redis
优点:
1.支持多种数据结构,如 string(字符串)、 list(双向链表)、dict(hash表)、set(集合)、zset(排序set)、hyperloglog(基数估算)
2.支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止数据丢失的手段。
3.支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据的同步复制,支持多级复制和增量复制,master-slave机制是Redis进行HA的重要手段。
4.单线程处理客户端请求,所有命令串行执行,并发情况下不需要考虑数据一致性问题。
5.支持pub/sub消息订阅机制,可以用来进行消息订阅与通知。
6.支持简单的事务需求,但业界使用场景很少,并不成熟。
7.支持数据分片、哨兵监控
缺点:
1.Redis只能使用单线程,性能受限于CPU性能,故单实例CPU最高才可能达到5-6wQPS每秒(取决于数据结构,数据大小以及服务器硬件性能,日常环境中QPS高峰大约在1-2w左右)。
2.支持简单的事务需求,但业界使用场景很少,并不成熟,既是优点也是缺点。
3.Redis在string类型上会消耗较多内存,可以使用dict(hash表)压缩存储以降低内存耗用。
使用场景:
1. 缓存
2. 消息队列,消息的发布和订阅(不推荐)
3. 数据存储(根据持久化可以把redis当做数据库来用)
4. 分布式session
5. 分布式锁
6. redis事务(不推荐)
7. 布隆过滤器
2.Memcache
优点:
1.Memcached可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key、value的字节大小以及服务器硬件性能,日常环境中QPS高峰大约在4-6w左右)。适用于最大程度扛量。
2.支持直接配置为session handle。
3.多线程
缺点:
1只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。
2.无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失。
3.无法进行数据同步,不能将MC中的数据迁移到其他MC实例中。
4.Memcached内存分配采用Slab Allocation机制管理内存,value大小分布差异较大时会造成内存利用率降低,并引发低利用率时依然出现踢出等问题。需要用户注重value设计。
3.MongoDB
优点:
1.更高的写负载,MongoDB拥有更高的插入速度。
2.处理很大的规模的单表,当数据表太大的时候可以很容易的分割表。
3.高可用性,设置M-S不仅方便而且很快,MongoDB还可以快速、安全及自动化的实现节点(数据中心)故障转移。
4.快速的查询,MongoDB支持二维空间索引,比如管道,因此可以快速及精确的从指定位置获取数据。MongoDB在启动后会将数据库中的数据以文件映射的方式加载到内存中。如果内存资源相当丰富的话,这将极大地提高数据库的查询速度。
5.非结构化数据的爆发增长,增加列在有些情况下可能锁定整个数据库,或者增加负载从而导致性能下降,由于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响,整个过程会非常快速。
缺点:
1.MongoDB从4.0开始支持 multi-document ACID事务
2.MongoDB占用空间过大 。
3.MongoDB没有成熟的维护工具
安装Redis 3.0
1.安装所需依赖
yum -y install cpp binutils glibc glibc-kernheaders glibc-common glibc-devel gcc make gcc-c++ libstdc++-devel tcl
2.创建目录,下载安装包
mkdir -p /usr/local/src/redis
cd /usr/local/src/redis
下载安装包
wget http://download.redis.io/releases/redis-3.0.2.tar.gz
3.解压
tar -xvf redis-3.0.2.tar.gz
4.编译安装
1)进入目录
cd redis-3.0.2
2)编译:
make
3)测试(跳过)
make test #这个就不要执行了,需要很长时间
4)安装
make install
5.启动
正确启动方式:不管是前台启动还是后台启动,都要指定配置文件,启动哨兵也要指定配置文件
1)备份配置文件
cp redis.conf redis.conf.bak
2)修改配置,让redis后台启动
vi redis.conf
找到daemonize属性,默认为no,把值改为yes
daemonize yes
3)启动
redis-server redis.conf
此时,看不到任何的界面了,通过进程查看是否启动成功:
ps –ef | grep redis
4)运行客户端测试
默认ip(127.0.0.1),端口(6379)连接命令
redis-cli
指定ip和端口连接命令
redis-cli -h 127.0.0.1 -p 6380
持久化机制(AOF和RDB)
1.什么是持久化
2.AOF
1.概述
与RDB存储某个时刻的快照不同,AOF持久化方式会记录客户端对服务器的每一次写操作命令,并将这些写操作以Redis协议追加保存到以后缀为aof文件末尾,在Redis服务器重启时,会加载并运行aof文件的命令,以达到恢复数据的目的。
2.开启AOF持久化方式
Redis默认不开启AOF持久化方式,我们可以在配置文件中开启并运行更加详细的配置,如下面的redis.conf文件:
# 开启aof机制
appendonly yes
# aof文件名
appendfilename "appendonly.aof"
# 写入策略,always表示每个写操作都保存到aof文件中,也可以是everysec或no
appendfsync always
# 默认不重写aof文件
no-appendfsync-on-rewrite no
# 保存目录
dir ~/redis/
3.三种写入策略
在上面的配置文件中,我们可以通过appendfsync选项指定写入策略,有三个选项
appendfsync always
# appendfsync everysec
# appendfsync no
1.always
客户端的每一个写操作都保存到aof文件中,这种策略很安全,但是每个写请求都有IO操作,所以很慢。
2.everysec
appendfsync的默认写入策略,每秒写入一次aof文件,因此,最多丢失1s的数据。
3. no
交由操作系统来处理什么时候来写入aof文件,更快,但也最不安全
4.AOF重写
AOF将客户端的每一个写操作都追加到aof文件末尾,比如对一个key多次执行incr命令,这时候,aof保存每一次命令到aof文件中,aof文件会变得非常大。
incr num 1
incr num 2
incr num 3
incr num 4
incr num 5
incr num 6
...
incr num 100000
aof文件太大,加载aof文件恢复数据时,就会非常慢,为了解决这个问题,Redis支持aof文件重写,通过重写aof,可以生成一个恢复当前数据的最少命令集,比如上面的例子中那么多条命令,可以重写为:
set num 100000
aof文件是一个二进制文件,并不是像上面的例子一样,直接保存每个命令,而使用Redis自己的格式,上面只是方便理解。
1.两种重写方式
1.【AOF重写命令】
可以使用BGREWRITEAOF命令来重写AOF文件。
2.【AOF重写配置】
重写策略的参数设置:
auto-aof-rewrite-percentage 100
当前的AOF文件大小超过上一次重写时的AOF文件大小的百分之多少时会再次进行重写,如果之前没有重写过,则以启动时的AOF文件大小为依据。
auto-aof-rewrite-min-size 64mb
限制了允许重写的最小AOF文件大小,通常在AOF文件很小的时候即使其中有些冗余的命令也是可以忽略的。
重写操作具体流程:
(1)redis fork一个子进程
(2)子进程基于当前内存中的数据,构建日志,开始往一个新的临时的AOF文件中写入日志
(3)redis主进程,接收到client新的写操作之后,在内存中写入日志,同时新的日志也继续写入旧的AOF文件
(4)子进程写完日志文件之后,redis主进程将内存中的新日志再次追加到新的AOF文件中
(5)用新的日志文件替换掉旧的日志文件
存储结构:内容是redis通讯协议(RESP )格式的命令文本存储。
5.AOF文件损坏
在写入aof日志文件时,如果Redis服务器宕机,则aof日志文件会出格式错误,在重启Redis服务器时,Redis服务器会拒绝载入这个aof文件,可以通过以下步骤修复aof并恢复数据。
1、备份现在aof文件,以防万一。
2、使用redis-check-aof命令修复aof文件,该命令格式如下:
# 修复aof日志文件
$ redis-check-aof -fix file.aof
3、重启Redis服务器,加载已经修复的aof文件,恢复数据。
6.AOF的优点
AOF只是追加日志文件,因此对服务器性能影响较小,速度比RDB要快,消耗的内存较少。
7.AOF的缺点
AOF方式生成的日志文件太大,即使通过AOF重写,文件体积仍然很大。
恢复数据的速度比RDB慢。
8.AOF最佳配置
appendonly yes :开启aof持久化,默认是no。修改成yes之后,aof才能生效
appendfilename "appendonly-${port}.aof" :修改aof文件名
dir /bigdiskpath:修改aof文件存储的目录
no-appendfsync-on-rewrite yes:当写入操作很频繁的时候,如果出现问题,是否不进行写入
3.RDB
1,概述
RDB是redis的默认持久化方式
RDB方式是通过快照完成的,当符合一定条件时Redis会自动将内存中的所有数据进行快照存储到硬盘上,写入二进制文件中,默认的文件名为 dump.rdb。而在Redis服务器启动时,会重新加载dump.rdb文件的数据到内存中恢复数据。进行快照的条件在配置文件中指定,有2个参数构成:时间和改动的键的个数,当在到达指定时间时被更改的键的个数达到指定数值时就会进行快照
注意:由于Redis使用fork来复制一份当前进程,那么子进程就会占有和主进程一样的内存资源,比如说主进程8G内存,那么在备份的时候必须保证有16G的内存,要不然会启用虚拟内存,性能非常差。
2.开启RDB持久化方式
开启RDB持久化方式很简单,客户端可以通过向Redis服务器发送save或bgsave命令让服务器生成rdb文件,或者通过服务器配置文件指定触发RDB条件。
1.save命令
save命令是一个同步操作
当客户端向服务器发送save命令请求进行持久化时,服务器会阻塞save命令之后的其他客户端的请求,直到数据同步完成。
2.bgsave
bgsave是一个异步操作
当客户端向服务器发出bgsave命令时,Redis服务器主进程会forks一个子进程来数据同步问题,在将数据保存到rdb文件之后,子进程会退出。
所以,与save命令相比,Redis服务器在处理bgsave采用子线程进行IO写入,而主进程仍然可以接收其他请求,但forks子进程的操作是同步的,所以forks子进程时,一样不能接收其他请求,这意味着,如果forks一个子进程花费的时间太久(一般是很快的),bgsave命令仍然有阻塞其他客户的请求的情况发生。
save和bgsave的区别
3.服务器配置自动触发
在Redis配置文件中的save指定到达触发RDB持久化的条件,比如【多少秒内至少达到多少写操作】就开启RDB数据同步。
例如我们可以在配置文件redis.conf指定如下的选项:
# 900s内至少达到一条写命令
save 900 1
# 300s内至少达至10条写命令
save 300 10
# 60s内至少达到10000条写命令
save 60 10000
之后在启动服务器时加载配置文件。
一个save表示两个条件, 比如save 900 1 代表 900秒内有一个key被修改则快照保存,这个key被修改后要等到900时才会被快照
# 启动服务器加载配置文件
redis-server redis.conf
这种通过服务器配置文件触发RDB的方式,与bgsave命令类似,达到触发条件时,会forks一个子进程进行数据同步,不过最好不要通过这方式来触发RDB持久化,因为设置触发的时间太短,则容易频繁写入rdb文件,影响服务器性能,时间设置太长则会造成数据丢失。
3.RDB文件的写入过程
RDB保存过程:
-
redis调用fork函数创建子进程
-
父进程继续处理 client 请求,子进程负责将内存内容写入到临时文件。由于 os 的实时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时 os 会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程地址空间内的数据是 fork时刻整个数据库的一个快照。
-
当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。client 也可以使用 save 或者 bgsave 命令通知 redis 做一次快照持久化。 save 操作是在主线程中保存快照的,由于 redis 是用一个主线程来处理所有 client 的请求,这种方式会阻塞所有client 请求。所以不推荐使用。另一点需要注意的是,==每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步变更数据。==如果数据量大的话,而且写操作比较多必然会引起大量的磁盘 io 操作,可能会严重影响性能
RDB文件是经过压缩的,可以通过配置来禁用压缩
压缩后的rdb文件占用磁盘空间小,但是压缩和解压缩会消耗CPU性能
不压缩的rdb文件大,但是不需要CPU进行压缩和解压缩
RDB默认生成的文件名为dump.rdb,当然,我可以通过配置文件进行更加详细配置,比如在单机下启动多个redis服务器进程时,可以通过端口号配置不同的rdb名称,如下所示:
# 是否压缩rdb文件
rdbcompression yes
# rdb文件的名称
dbfilename redis-6379.rdb
# rdb文件保存目录
dir ~/redis/
4.RDB的优点
1.与AOF方式相比,通过rdb文件恢复数据比较快
2.RDB文件可压缩,适合于数据备份。
5.RDB的缺点
1.如果服务器宕机的话,采用RDB的方式会造成某个时段内数据的丢失,比如我们设置10分钟同步一次或5分钟达到1000次写入就同步一次,那么如果还没达到触发条件服务器就死机了,那么这个时间段的数据会丢失。
2.使用save命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
3.使用bgsave命令在forks子进程时,如果数据量太大,forks的过程也会发生阻塞,另外forks子进程会耗费内存。
6.RDB最佳配置
1、不使用自动快照策略,往往根据业务需求自己设置;
2、rdb文件名:默认文件名为dump.rdb,开发过程中如果不是单点的redis,往往需要在中间加上端口号加以区分;
3、rdb文件保存路径:往往不保存在当前目录,需要保存在一个比较大的磁盘空间中;
4、stop-writes-on-bgsave-error yes:后台写入如果发生异常则停止写入
关闭RDB:只需要把save条件全删了就可以了
3.1版本后,redis关闭时会自动RDB
4.选择RDB还是AOF呢?
1、启动优先级:AOF高,如果RDB和AOF同时开启,默认会使用AOF持久化机制;
2、体积:RDB 小;AOF 大
3、恢复速度: RDB快;AOF 慢
4、数据安全性:RDB易丢失数据;AOF根据写入策略不同,数据丢失情况不同
5、数据轻重: RDB文件较重,每次快照都需要保存所有的数据;AOF较轻,采用追加日志的形式记录数据;
Redis的主从复制(读写分离)集群
注意部署集群使用单数个节点
主从复制的好处有2点:
1、 避免redis单点故障
2、 构建读写分离架构,满足读多写少的应用场景
1.主从架构
1.1. 启动实例
1)在当前目录下,创建6379、6380、6381目录,
2)分别将安装目录下的redis.conf拷贝到这三个目录下。
3)分别修改3个目录下的redis.confg配置文件,将端口分别设置为:6379(Master)、6380(Slave)、6381(Slave)。同时要设置pidfile文件为不同的路径。
vi 6381/redis.conf
4)分别启动三个redis实例:
查看进程:
5)通过客户端查看连接状态:
1.2 设置主从
在redis中设置主从有2种方式:
1、 在redis.conf中设置slaveof
a) slaveof
2、 使用redis-cli客户端连接到redis服务,执行slaveof命令
a) slaveof
第二种方式在重启后将失去主从复制关系。
我们采用第二种方式进行演示。生产环境应该采用第一种。
我们以6379为主,其它两个为从。
设置主从
登录6380和6381,使用slaveof 127.0.0.1 6379命令
查看主从信息:
INFO replication
主:
role:角色
connected_slaves:从库数量
slave0:从库信息
从:
1.3 测试
在主库写入数据:
set test 123
在从库读取数据:
get test
2. 主从从架构
2.1 启动实例
重启三个服务,取消刚才的 主从关系
让6379为主,6380以6379为主,6381以6380为主,形成链式主从:6381 ——》 6380 ——》6379
6379(主):
6380(从):
6381(从从):
2.2 测试
在主库设置数据:
在6380获取数据:
在6381获取数据:
3. 从库只读
默认情况下redis数据库充当slave角色时是只读的不能进行写操作。
可以在配置文件中开启非只读:slave-read-only no
4.复制的过程原理
1.当从库和主库建立MS关系后,从库会向主数据库发送PSYNC命令;
2.主库接收到PSYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来;
3.当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis;
4.从Redis接收到后,会载入快照文件并且执行收到的缓存的命令;
5.之后,主Redis每当收到写命令时就会将命令发送给从Redis,从而保证数据的一致;
我们一旦玩集群,主就无需设置RDB或AOF了。哪怕是主库关闭了RDB,也不影响主从复制。
5.无磁盘复制
通过前面的复制过程我们了解到,主库接收到SYNC的命令时会执行RDB过程,即使在配置文件中禁用RDB持久化也会生成,那么如果主库所在的服务器磁盘IO性能较差,那么这个复制过程就会出现瓶颈,庆幸的是,Redis在2.8.18版本开始实现了无磁盘复制功能(不过该功能还是处于试验阶段)。
原理:
Redis在与从数据库进行复制初始化时将不会将快照存储到磁盘,而是直接通过网络发送给从数据库,避免了磁盘IO性能差问题。
开启无磁盘复制:repl-diskless-sync yes
6.复制架构中出现宕机情况,怎么办?
如果在主从复制架构中出现宕机的情况,需要分情况看:
1、 从Redis宕机
a) 从库可以做持久化,可以把持久化的时间间隔调长一些,因为从库丢失一部分数据没关系,重启时可以从主库同步,这样就解决了从库挂掉需要把主库所有的数据都往自己同步占用大量的网络IO的问题
b) 如果从库在断开期间,主库的变化不大,从库再次启动后,主库依然会将所有的数据做RDB操作吗?还是增量更新?(从库有做持久化的前提下)
i. 不会的,因为在Redis2.8版本后就实现了,主从断线后恢复的情况下实现增量复制。
c) 这个相对而言比较简单,在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据;
i. 如果从库宕机期间,主库有新增的数据,从库重新启动后也会和主库的数据保持一致不会缺失那部分新增的数据,因为从库重启时会把offset偏移量也发给主库,主库发现从库的偏移量和自己的偏移量不一致,就会根据从库的偏移量开始把从库缺少的所有数据同步给从库,该功能叫做增量同步
2、 主Redis宕机
a) 这个相对而言就会复杂一些,需要以下2步才能完成
i. 第一步,在从数据库中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务;
ii. 第二步,将主库重新启动后,执行SLAVEOF命令,将其设置为其他库的从库,因为主库和从库是有主从复制的,所以主库宕机其他从库不会丢失数据,把主库设置为其他库的从库,又会重新同步数据,所以不会丢失数据;
b) 这个手动完成恢复的过程其实是比较麻烦的并且容易出错,有没有好办法解决呢?当前有的,Redis提供的哨兵(sentinel)的功能。
哨兵
1.什么是哨兵
Redis Sentinel 是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel 集群和 Redis 数据集群。
其中 Redis Sentinel 集群是由若干 Sentinel 节点组成的分布式集群,可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel 的节点数量要满足 2n+1(n>=1)的奇数个。
哨兵的作用
- 监控所有服务器是否正常运行:通过发送命令返回监控服务器的运行状态,除了监控主服务器、从服务器外,哨兵之间也相互监控。
- 自动故障迁移:当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换master。同时那台有问题的旧主也会变为新主的从,也就是说当旧的主即使恢复时,并不会恢复原来的主身份,而是作为新主的一个从。
2.原理
单个哨兵的架构:
多个哨兵的架构:
哨兵本身也有单点故障的问题,所以在一个一主多从的Redis系统中,可以使用多个哨兵进行监控,哨兵不仅会监控主数据库和从数据库,哨兵之间也会相互监控。每一个哨兵都是一个独立的进程,作为进程,它会独立运行。
3.环境
当前处于一主多从的环境中:
4.配置哨兵
进入redis的安装目录:
启动哨兵进程首先需要创建哨兵配置文件:
vim sentinel.conf
输入内容:
sentinel monitor taotaoMaster 127.0.0.1 6379 1
说明:
taotaoMaster:监控主数据库的名称,自定义即可,可以使用大小写字母和“.-_”符号
127.0.0.1:监控的主数据库的IP
6379:监控的主数据库的端口
1:判断主数据库客观下线的票数
启动哨兵进程:
redis-sentinel ./sentinel.conf
由上图可以看到:
1、 哨兵已经启动,它的id为9059917216012421e8e89a4aa02f15b75346d2b7
2、 为master数据库添加了一个监控
3、 发现了2个slave(由此可以看出,哨兵无需配置slave,只需要指定master,哨兵会自动发现slave)
5.检测主观下线状态
在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel在内)发送PING命令,并通过实例返回的PING命令回复俩判断实例是否在线。
在图16-17展示的例子中,带箭头的连线显示了Sentinel1和Sentinel2是如何向实例发送PING命令的:
- Sentinel1将向Sentinel2、主服务器master、从服务器slave1和slave2发送PING命令。
- Sentinel2将向Sentinel1、主服务器master、从服务器slave1和slave2发送PING命令。
实例对PING命令的回复可以分为以下两种情况:
- 有效回复:实例返回+PONG-LOADING-MASTERDOWN三种回复的其中一种
- 无效回复:实例返回除+PONG、-LOADING、-MASTERDOWN三种回复之外的其他回复,或者在指定时限内没有返回任何回复。
Sentinel配置文件中的 down-after-milliseconds选项指定了Sentinel判断实例进入主观下线所需的事件长度:如果一个实例在down-after-milliseconds毫秒内,连续想Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示这个实例已经进入主观下线状态。
以图16-17展示的情况为例子,如果配置文件指定Sentinel的down-after-milliseconds选项的值为50000毫秒,那么当主服务器master连续50000毫秒都向Sentinel1返回无效回复时,Sentinel1就会将master标记为主观下线,并在master所对应的实例结构的flags属性中打开SRI-S-DOWN标识。
6.检查客观下线状态
当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel就会将主服务器判定为客观下线,并对主服务器执行故障转移操作。
客观下线状态的判断条件
当认为主服务器已经进入下线状态的Sentinel的数量,超过Sentinel配置中设置的quorum参数的值,那么该Sentinel就会认为主服务器已经进入客观下线状态。比如说,如果Sentinel在启动时载入了以下配置:
sentinel monitor master 127.0.0.1 6379 2
那么包括当前Sentinel在内,只要总共有两个Sentinel认为主服务器已经进入下线状态,那么当前Sentinel就将主服务器判断为客观下线。
不同Sentinel判断客观下线的条件可能不同
对于监视同一个主服务器的多个Sentinel来说,它们将主服务器判断为客观下线的条件可能也不同:当一个Sentinel将主服务器判断为客观下线时,其他Sentinel可能并不是那么认为的。比如说,对于监视同一个主服务器的五个Sentinel来说,如果Sentinel1在启动时载入了一下配置:
sentinel monitor master 127.0.0.1 6379 2
那么当五个Sentinel中有两个Sentinel认为主服务器已经下线时,Sentinel1就会将主服务器判断为客观下线。
而对于载入以下配置的Sentinel2来说:
sentinel monitor master 127.0.0.1 6379 5
仅有两个Sentinel认为主服务器已下线,并不会命令Sentinel2将主服务器判断为客观下线。
7.领导者哨兵选举
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线住服务器执行故障转移操作。
以下是Redis选举领头Sentinel的规则和方法:
- 所有在线的Sentinel都有被选为领头Sentinel的资格,换句话说,监视同一个主服务器的多个在线Sentinel中的任意一个都有可能成为领头Sentinel。
- 每次进行领头Sentinel选举之后,不论选举是否成功,所有Sentinel的配置纪元(configuration epoch)的值都会自增一次。配置纪元实际上就是一个计数器,并没有什么特别的。
- 在一个配置纪元里面,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里面就不能再更改。
- 每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel。
- 当一个Sentinel(源Sentinel)向另一个Sentinel(目标Sentinel)发送SENTINEL is-master-down-by-addy命令,并且命令中的runid参数不是*符号而是源Sentinel的运行ID时,这表示源Sentinel要求目标Sentinel将前者设置为后者的局部领头Sentinel。
- Sentinel设置局部领头Sentinel的规则是先到先得:最先向目标Sentinel发送设置要求的源Sentinel将成为目标Sentinel的局部领头Sentinel,而之后接收到的所有设置要求都会被目标Sentinel拒绝。
- 目标Sentinel在接收到SENTINEL is-master-down-by-addr命令之后,将向源Sentinel返回一条命令回复,回复中的leader_runid参数和leader_epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元。
- 源Sentinel在接收到目标Sentinel返回的命令回复之后,会检查回复中leader_epoch参数的值和自己的配置纪元是否相同,如果相同的话,那么源Sentinel继续取出回复中的leader_runid参数,如果leader_runid参数的值和源Sentinel的运行ID一致,那么表示目标Sentinel将源Sentinel设置成了局部领头Sentinel。
- 如果有某个Sentinel被半数以上Sentinel设置成了局部领头Sentinel,那么这个Sentinel成为领头Sentinel。举个例子,在一个由10个Sentinel组成的Sentinel系统里面,只要有大于等于10/2+1=6(num(sentinels)/2+1)个Sentinel将某个Sentinel设置为局部领头Sentinel,那么被设置的那个Sentinel就会成为领头Sentinel。
- 因为领头Sentinel的产生需要半数以上Sentinel的支持,并且每个Sentinel在每个配置纪元里面只能设置一次局部领头