普通主从架构(读写分离)
问题
持久化解决了单机redis的数据保存问题,但是redis还是存在以下两个问题:
- 假如某天这台redis服务器挂了,redis服务将彻底丧失
- redis的读和写都集中到一台机上,如果请求量比较大时,将可能被击溃
解决
为了解决上述两个问题,redis提供了主从架构,在主从架构中,主服务器负责写服务,多台从服务器负责读服务,缓解了单个redis服务器的压力;主服务器将所有数据源源不断的同步到从服务器上,一旦主服务器挂了,还有从服务器可以提供服务,redis服务将不会间断。
特点
- 一台主服务器可以连接多台从服务器
- 从服务器也可以连接其他redis服务器,作为其他redis服务器的主服务器,从而形成一条链
- 主从同步是异步的,从服务器不会阻塞,但是在数据写到从服务器内存的这段期间,从服务器对外提供的还是旧的数据
类型
-
全量同步
指主服务器每次与从服务器同步都是同步全部数据。主服务器持久化数据为一个rdb文件,在此期间用缓存区把所有对主服务器的写操作命令存储起来了,然后再rdb传给从服务器,再把储存起来的命令也传过去;从服务器从接收到的rdb文件加载数据,然后再加载传过来的命令。 -
部分同步
指主服务器每次与从服务器同步都是只同步增量数据。
原理
- 从服务器收到客户端的saveof命令,检查是否存储了主服务器的运行id和复制偏移量。
- 如果没绑存储,从服务器发送 psync 1命令和主服务器进行一次全量复制,并且保存主服务器发过来的运行id 和 复制偏移量
- 如果有存储了,从服务器发送运行id和复制偏移量,主服务器比较运行id是否一致,复制偏移量是否正常,如果不一致或者不正常,进行全量同步
- 如果运行id和复制偏移量正常,那么二者进行增量同步,同步根据传过来复制偏移量,到复制缓存区找到对应的字节,并且把该字节对应的之后的命令都同步过去
操作
-
slaveof master-ip master-port
在从服务器的redis.conf中配置主服务器的host,port -
slave-read-only yes
从服务器默认只读,这里可改成no为可写(可选) -
auth
从服务器配置主服务器的密码(可选) -
requirepass
主服务器配置密码(可选)
从服务器配置好slaveof master-ip master-port
后重启,就可以与主服务器进行同步了。
# 主服务器信息
172.17.0.3:6379> info replication
role:master
connected_slaves:2
# 两个从服务器复制偏移量为4908
slave0:ip=172.17.0.4,port=6379,state=online,offset=4908,lag=1
slave1:ip=172.17.0.5,port=6379,state=online,offset=4908,lag=1
# 主服务器的复制偏移量也为4908
master_repl_offset:4908
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:4907
哨兵模式
作用
- 不断的检查主从架构中的redis服务中正常运行。
- 如果出现问题会发信息提示你。
- 如果主服务器挂了的话,会从从服务器中重新选举一台作为主服务器。
原理
哨兵系统的分类
哨兵系统可以分为单哨兵和多哨兵;单哨兵是指只有一个哨兵进程,多哨兵是指有多个哨兵进程;单哨兵挂了的话,哨兵系统也就挂了,多哨兵只有一个哨兵挂了不影响哨兵系统继续提供服务。大概原理
哨兵也是一个Linux的进程;各个哨兵分布在不同的Linux服务器或者同一个Linux服务器上(一个风险比较大),它们不停的监控redis的主服务器和从服务器与其它的哨兵进程,一旦察觉redis主服务挂了,就会从从服务器中选出一个作为新的主服务器提供服务。哨兵怎么知道监控其它哨兵和redis服务?
哨兵每秒钟会向其它哨兵或者redis服务器发送ping命令,根据是否有返回来判断服务是否已经下线。我们只是在哨兵的配置文件里配置了主服务器信息,但是它怎么知道从服务器信息?
哨兵每十秒钟会向redis主服务器或者从服务器执行info replication的命令,来确认它们的主从关系。我们没有配置其他哨兵的地址,哨兵怎么知道其他哨兵地址?
哨兵每隔两秒就会向redis主节点的sentinel:hello频道发布哨兵对于主节点的判断以及当前哨兵的信息,其它哨兵也会也会如此,并且从中获取所有的哨兵信息。确认一台redis服务器下线经历了什么流程?
哨兵不断的PING redis服务器,当发现服务器超过配置的down-after-milliseconds
的时间都没有响应,就会认为这台主观下线;这时候哨兵会向其他哨兵发送is-master-down-by-addr
命令询问是否可以标记为客观下线,当认为这台redis服务器主观下线的哨兵超过我们配置的quorum
(一般设为哨兵数量的一半加1)的值的时候,我们就可以认为这台redis服务器客观下线。为什么还要去询问其他哨兵呢?这是因为哨兵和redis服务器之间没有ping成功也可以能网络之间的问题。为什么要对哨兵进行领导者选举?
当确定redis服务器确实挂了以后,哨兵要进行故障转移,并且只能有一个哨兵去完成该操作,所以这时候就要选举出一名哨兵来当此重任。那怎么选举呢?
- 哨兵向其它哨兵发送
is-master-down-by-addr
除了确认是否机器是否可以下线以外,会有发起选举的作用 - 其它哨兵收到命令以后,如果如果没有答应其它哨兵的选举请求就会答应该哨兵的请求
- 当同意(包括自己)的哨兵个数达到
quorum
,该哨兵就会成为领导者
-
怎么完成故障转移?
当确定原来的redis主服务器已经客观下线以后,就会从从服务器中选出一台作为新的主服务器,选择顺序如下:
- 看配置的
slave-priority
,如果从服务器不相等,返回最高的那台,如果相同看下一步 - 看offset,即复制偏移量,如果复制偏移量不同,返回最高那台,如果相同看下一步
- 看runid,程序id,runid越低可以看做是越早开启,返回越低那台
确定完是哪台从服务器作为新的主服务器以后,会修改新的从服务器的slaveof与各个哨兵的监控的主服务器的地址和ip。
举个栗子
开启三个redis服务,一主两从;开启三个哨兵;把主服务器关闭掉,查看从服务器是否会产生新的主服务器。
redis配置
# 主服务器 redis-6379.conf
port 6379
daemonize yes
protected-mode no
logfile "6379.log"
dbfilename "dump-6379.rdb"
# 从服务器 redis-6380.conf
port 6380
daemonize yes
protected-mode no
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 139.199.168.61 6379
# 从服务器 redis-6381.conf
port 6381
daemonize yes
protected-mode no
logfile "6381.log"
dbfilename "dump-6381.rdb"
slaveof 139.199.168.61 6379
redis-server redis-6379.conf
启动各个redis服务。
哨兵配置
#sentinel-26379.conf
port 26379
daemonize yes
protected-mode no
logfile "26379.log"
#监控的redis主服务器,最后面的2就是上面提到的quorum,当哨兵响应的个数超过这个数,redis服务器才会被认为是客观下线
sentinel monitor mymaster 139.199.168.61 6379 2
#当超过这个值redis服务器对哨兵的ping不做出响应会被哨兵认为是主观下线
sentinel down-after-milliseconds mymaster 10000
#sentinel-26380.conf
port 26380
daemonize yes
protected-mode no
logfile "26380.log"
sentinel monitor mymaster 139.199.168.61 6379 2
sentinel down-after-milliseconds mymaster 10000
#sentinel-26381.conf
port 26381
daemonize yes
protected-mode no
logfile "26381.log"
sentinel monitor mymaster 139.199.168.61 6379 2
sentinel down-after-milliseconds mymaster 10000
redis-sentinel sentinel-26379.conf
启动各个哨兵服务。
哨兵启动后我们可以查看sentinel-26379.conf
:
port 26379
daemonize yes
protected-mode no
logfile "26379.log"
sentinel myid f65e6f01127c838e023f48b73c0f9642548a176d
sentinel monitor mymaster 139.199.168.61 6379 2
# Generated by CONFIG REWRITE
dir "/usr/local/redis-3.2.6"
sentinel down-after-milliseconds mymaster 10000
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel known-slave mymaster 139.199.168.61 6381
sentinel known-slave mymaster 139.199.168.61 6380
sentinel known-sentinel mymaster 10.104.90.159 26381 a8ca7a98c5b462c5c341650386be589cf31a5761
sentinel known-sentinel mymaster 10.104.90.159 26380 eb637934a6e00a26c36fef681cb1e194127b7d2c
sentinel current-epoch 0
会发现哨兵已经把redis的从服务器和其他哨兵加进来了,我们关闭主服务器kill redis主服务器的进程
,再查看sentinel-26379.conf
:
port 26379
daemonize yes
protected-mode no
logfile "26379.log"
sentinel myid f65e6f01127c838e023f48b73c0f9642548a176d
sentinel monitor mymaster 139.199.168.61 6381 2
# Generated by CONFIG REWRITE
dir "/usr/local/redis-3.2.6"
sentinel down-after-milliseconds mymaster 10000
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel known-slave mymaster 139.199.168.61 6379
sentinel known-slave mymaster 139.199.168.61 6380
sentinel known-sentinel mymaster 10.104.90.159 26381 a8ca7a98c5b462c5c341650386be589cf31a5761
sentinel known-sentinel mymaster 10.104.90.159 26380 eb637934a6e00a26c36fef681cb1e194127b7d2c
sentinel current-epoch 1
我们可以看到监控的主服务器已经切换成 6381而不是之前的6379了。
Java使用
- Java使用redis的一般做法:
Jedis jedis = new Jedis("139.199.168.61", 6379);
System.out.println(jedis.get("hello"));
这种做法最大的弊端在于万一139.199.168.61:6379
这个redis挂了,这个Java应用就废了。
- 如果我们现在知道了哨兵模式,我们可以改写成以下的做法:
//声明一个set 存放哨兵集群的地址和端口
Set<String> sentinels = new HashSet<String>();
sentinels.add("139.199.168.61:26379");
sentinels.add("139.199.168.61:26380");
sentinels.add("139.199.168.61:26381");
JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", sentinels);
// 使用sentinelPool获取jedis对象
Jedis master = sentinelPool.getResource();
System.out.println(master.get("hello"));
master.close();
sentinelPool.destroy();
这样子,当主服务器挂掉以后,哨兵集群会返回给程序新的主服务器地址,保证服务不会挂掉。