1.《redis架构组成》
1.redis学习维度

2.一个基本的键值型数据库包括什么?

1.访问框架

redis通过网络框架进行访问,使得 Redis 可以作为一个基础性的网络服务进行访问,扩大了redis应用范围;

过程:如果客户端发送“put hello world ”这条指令会被包装成一个网络包,服务端收到后进行解析,执行指令;

问题:请求连接,解析,数据存取这些操作使用单线程还是多线程呢?

如果单线程,其中一个环节出错或阻塞就会导致整个流程阻塞,降低了系统响应速度;

如果是多线程,那么当有共享资源的时候,会产生线程竞争,也会影响系统效率,这又该怎么办呢?

注意:Redis 又是如何做到“单线程,高性能”的呢?

2.索引模块

redis采用哈希表作为索引,因为键值数据基本都是保存在内存中的,而内存的高性能随机访问特性可以很好地与哈希表 O(1) 的操作复杂度相匹配。

3.存储模块

redis提供AOF和RDB持久化数据;

4.高可用集群模块

redis提供主从架构,哨兵模式

5.高扩展模块

redis支持分片机制

2.《redis IO多路复用》
redis之所以那么快,除了使用hash表这样的高效的数据结构,还因为他的IO多路复用模型;

1.redis真的是的单线程吗?
redis的键值操作以及IO处理是单线程的

其他操作时多线程的,例如:持久化,异步删除,集群数据同步等;

2.redis为什么使用单线程,多线程不行吗?
多线程情况下,势必会有共享资源的争夺,这就需要 引入 同步语句或者锁来协调多线程之间的运作,一旦引入锁,同步语句,就会增加redis的复杂性,发生阻塞,影响性能;

3.redis 基于多路复用的IO模型
1.我们先来看看客户端,服务端IO通信需要哪些操作?
首先服务端监听客户端的请求(bind/listen),和客户端建立连接(accept),接收客户

端的数据(recv),解析(parse),键值操作,然后返回(send)

潜在阻塞点有两个:

当redis监听到一个客户端连接请求(accept),但是一直未能连接成功时,阻塞

当redis接收客户端发送的请求数据包时,一直未收到数据,阻塞

这就导致 Redis 整个线程阻塞,无法处理其他客户端请求,效率很低。不过,幸运的是,socket 网络模型本身支持非阻塞模式。

2.socket 非阻塞模式
socket 网络模型的非阻塞模式主要体现在三个函数上;

调用socket()函数返回 主动套接字,调用listen() 将主动套接字转换为 监听套接字,此时,可以监听来自客户端的连接请求。调用accept() 返回已连接套接字。

针对监听套接字,我们可以设置非阻塞模式:当 Redis 调用 accept() 但一直未有连接请求到达时,Redis 线程可以返回处理其他操作,而不用一直等待。但是,你要注意的是,调用 accept() 时,已经存在监听套接字了。

虽然 Redis 线程可以不用继续等待,但是总得有机制继续在监听套接字上等待后续连接请求,并在有请求时通知 Redis。

我们也可以针对已连接套接字设置非阻塞模式:Redis 调用 recv() 后,如果已连接套接字上一直没有数据到达,Redis 线程同样可以返回处理其他操作。我们也需要有机制继续监听该已连接套接字,并在有数据达到时通知 Redis。

到此,Linux 中的 IO 多路复用机制就要登场了。

3.redis 基于多路复用的IO 模型
redis 的多路复用IO模型 基于 select/epoll机制实现的;

select/epoll支持以下两点:

red单线程运行下,支持linux内核中同时存在多个 “监听套接字”和“连接套接字”,内核会一直监听这些套接字,每当有请求到达,就会通知redis,处理
select/epoll机制提供 事件回调函数,不同的事件对应不同的回调函数,时间发生调用对应的回调函数

图中的多个 FD 就是刚才所说的多个套接字。Redis 网络框架调用 epoll 机制,让内核监听这些套接字。此时,Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。

为了在请求到达时能通知到 Redis 线程,select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。

select/epoll 一旦监测到 FD 上有请求到达时,就会触发相应的事件。这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。这样一来,Redis 无需一直轮询是否有请求实际发生,这就可以避免造成 CPU 资源浪费。同时,Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。因为 Redis 一直在对事件队列进行处理,所以能及时响应客户端请求,提升 Redis 的响应性能。

例子:

假如两个请求分别对应 Accept 事件和 Read 事件,Redis 分别对这两个事件注册 accept 和 get 回调函数。当 Linux 内核监听到有连接请求或读数据请求时,就会触发 Accept 事件和 Read 事件,此时,内核就会回调 Redis 相应的 accept 和 get 函数进行处理。

4.redis 多路复用IO 模型有没有什么性能瓶颈?
1.任何一个请求在server中耗时,就会影响整个server的性能,后面所有请求都会阻塞等待

操作bigkey,分配内存空间比较耗时,同样删除bigkey时释放内存空间也比较耗时
使用复杂度过高的命令:例如SORT/SUNION/ZUNIONSTORE,或者时间复杂度O(N),但是N特别大
大量key集中过期:Redis的过期机制也是在主线程中执行的,大量key集中过期会导致处理一个请求时,耗时都在删除过期key,耗时变长;
**淘汰策略:**淘汰策略也是在主线程执行的,当内存超过Redis内存上限后,每次写入都需要淘汰一些key,也会造成耗时变长;
**AOF刷盘开启always机制:**每次写入都需要把这个操作刷到磁盘,写磁盘的速度远比写内存慢,会拖慢Redis的性能;
主从全量同步生成RDB:虽然采用fork子进程生成数据快照,但fork这一瞬间也是会阻塞整个线程的,实例越大,阻塞时间越久;
解决:一方面需要开发人员手动处理,另一方面 Redis在4.0推出了lazy-free机制,把bigkey释放内存的耗时操作放在了异步线程中执行,降低对主线程的影响。

2.并发量大时,虽然使用 多路复用IO,单个线程,多个IO,当时redis主线程处理仍然是单线程,顺序执行,无法利用计算机多个CPU(内核)

解决:Redis在6.0推出了多线程,可以在高并发场景下利用CPU多核多线程读写客户端数据,进一步提升server性能,当然,只是针对客户端的读写是并行的,每个命令的真正操作依旧是单线程的。

3.《持久化:AOF和RDB》
1.AOF
1.AOF写日志
AOF 写日志使用的是 写后记录日志

好处:

避免命令错误,AOF日志恢复时出现错误。redis不会检查 命令是否正确,所以如果先记日志,后执行命令,会导致记日志成功,执行命令失败
不会阻塞主线程。因为写日志是磁盘操作,影响性能,最终导致后面请求耗时
AOF的缺点:

写日志和命令执行都是在主线程执行,所以记录日志会阻塞下一个命令操作

写后 记录日志 ,宕机会导致数据丢失

*案例:set testkey testvalue,“3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例如,“$3 set”表示这部分有 3 个字节,也就是“set”命令。

2. 三种写回策略
AOF 配置项 appendfsync 的三个可选值。

Always:同步写回,同步刷盘,写一次,记录一次(丢失数据少,影响redis性能)
Everysec:每秒写回,每秒执行一次刷盘(最多丢失一秒数据)
No:操作系统控制的写回, 每个命令执行完把日志写回 AOF文件的内存缓冲区,由操作系统决定什么时候刷盘(速度快,但是会出现数据丢失严重的情况)

3.AOF日志文件过大问题
.文件过大带来的问题

文件系统不支持太大的文件

文件过大导致 命令追加的耗时,影响性能

文件过大, AOF日志回写的时,由于是逐条命令执行,会导致系统启动过慢

4.AOF重写
1.什么是AOF重写?

AOF重写时,redis根据当前数据库的现状,创建一个新的AOF日志文件,他会获取redis当前状态所有数据,原来的AOF文件每个键值对可能有多个命令,而重写机制把多个命令合并成一个命令,记录该键值对的当前状态;

当我们对一个列表先后做了 6 次修改操作后,列表的最后状态是[“D”, “C”, “N”],此时,只用 LPUSH u:list “N”, “C”, "D"这一条命令就能实现该数据的恢复,这就节省了五条命令的空间。

2.AOF重写是异步的吗?

重写时,会有由主线程fork(fork会阻塞主线程)出来一个后台子线程 bgrewriteaof,会把主线程当状态所有内存复制给后台子线程bgrewriteaof,子线程去执行AOF重写。

fork原理

a、fork子进程,fork这个瞬间一定是会阻塞主线程的(注意,fork时并不会一次性拷贝所有内存数据给子进程,老师文章写的是拷贝所有内存数据给子进程,我个人认为是有歧义的),fork采用操作系统提供的写实复制(Copy On Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题,但**fork子进程需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。拷贝内存页表完成后,子进程与父进程指向相同的内存地址空间,也就是说此时虽然产生了子进程,但是并没有申请与父进程相同的内存大小。**那什么时候父子进程才会真正内存分离呢?“写实复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据,这个过程中,父进程也可能会产生阻塞的风险,就是下面介绍的场景。

b、fork出的子进程指向与父进程相同的内存地址空间,此时子进程就可以执行AOF重写,把内存中的所有数据写入到AOF文件中。但是此时父进程依旧是会有流量写入的,如果父进程操作的是一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应的内存数据,申请新的内存空间,这样逐渐地,父子进程内存数据开始分离,父子进程逐渐拥有各自独立的内存空间。因为内存分配是以页为单位进行分配的,默认4k,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产阻塞风险。另外,如果操作系统开启了内存大页机制(Huge Page,页面大小2M),那么父进程申请内存时阻塞的概率将会大大提高,所以在Redis机器上需要关闭Huge Page机制。Redis每次fork生成RDB或AOF重写完成后,都可以在Redis log中看到父进程重新申请了多大的内存空间。

重写的过程总结为**“一个拷贝,两处日志”**。

一个拷贝:主线程的内存数据拷贝给子线程bgrewriteaof,子线程对数据进行分析,重写日志

两处日志:

主线程操作的AOF日志,如果重写期间有新的写入操作,写入操作放入 AOF日志缓冲区
AOF重写日志 ,重写期间新的写入操作,也会放入AOF重写日志缓冲区(等bgrewriteaof重写日志完成,就把AOF重写日志缓冲区的数据重写 形成新的AOF日志文件,这时候就可以代替旧的文件了)

每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用额外的线程进行数据重写,所以,这个过程并不会阻塞主线程。

5.问题
1.AOF 日志重写的时候,是由 bgrewriteaof 子进程来完成的,不用主线程参与,我们今天说的非阻塞也是指子进程的执行不阻塞主线程。但是,你觉得,这个重写过程有没有其他潜在的阻塞风险呢?如果有的话,会在哪里阻塞?

fork进程会阻塞

copy-onwrite机制,当父进程有数据写入已存在的key,且为bigkey,申请新的内存空间时父进程会阻塞

2.AOF 重写也有一个重写日志,为什么它不共享使用 AOF 本身的日志呢?

父子进程共享一个日志文件,出现资源争夺的情况,阻塞父进程

如果重写失败呢?那么AOF文件会被污染,无法进行宕机后,数据回写;新建一个重写日志文件,重写失败,直接删除就好了;

2.RDB(既可以保证可靠性,还能在宕机时实现快速恢复)
1.redis生成RDB快照的两个命令
save:阻塞主线程

bgsave: 创建一个子线程,专门生成RDB文件,避免主线程阻塞,RDB默认使用方式

2.redis处理RDB快照,主线程怎么进行写操作
进行 全量快照 时,主进程会fork出来一个子进程,会copy内存数据的内存页表地址,copy完直到父子进程就有共享同一个内存数据的内存地址;

在子进程写RDB的时候,主进程正常读,写的时候是使用操作系统 Copy-On-Write(写时复制)机制,会申请新的内存空间存放访问的key键值对数据,bgsave 子进程会把这个副本数据写入 RDB 文件,主线程仍然写原来的数据;

3.增量快照
我们可以频繁的执行全量快照吗?

注意:如果频繁地执行全量快照,也会带来两方面的开销。

**频繁将全量数据写入磁盘,会给磁盘带来很大压力,**多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,**fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。**如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了
当我们第一次做完全量快照之后,后面T1,T2时刻做快照时只需要把修改的数据写入快照文件就行了。

所以,我们需要申请额外的内存空间来存储 元数据信息;

虽然跟 AOF 相比,快照的恢复速度快,但是,快照的频率不好把握,如果频率太低,两次快照间一旦宕机,就可能有比较多的数据丢失。如果频率太高,又会产生额外开销

4.混合使用 AOF 日志和内存快照
redis4.0中提出混合使用 AOF 日志和内存快照的方案。

内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

好处:

避免频繁fork对主进程的影响
主进程写入时不会有额外的内存空间开销
AOF日志文件也不会过大
如下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。

3.持久化方案总结
数据不能丢失时,内存快照RDB和 AOF 的混合使用是一个很好的选择;
如果允许分钟级别的数据丢失,可以只使用 RDB;
如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡;
4.《主从数据同步》
1.读写分离
主库写数据,并同步给从库

主从库均可读数据

为什么读写分离,写数据可以写在从库上吗?

不可以,写在所有库上,如果某一个写操作失败了呢?是不是意味着主从库数据不一致了呢?

这时候我们为了数据一致,就要协调主从库写操作要么都完成,有一个不完成就回滚,这势必会用到锁,以及实例之间协商是否均完成写操作,这样会影响redis性能;

主从库模式,主库有数据,会同步给从库,这样组从库数据就是一致的。

2.主从同步
1.第一次同步
redis实例启动时可以使用 replacof ip (Redis 5.0 之前使用 slaveof)指令当前实例是哪个主库的从库

现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5)

在实例2上执行 replicaof 172.16.19.3,实例2就变成了实例1的从库,并开始从实例 1 上复制数据

主从第一次同步时

第一阶段:从库会发送 给主库 fsync ? -1 命令给主库告诉它,我这是第一次同步,我要求全量同步,fsync 命令 有两个参数

主库runID(唯一随机ID),由于第一次同步,所以从库不知道主库runID,传“?”,

从库偏移量offset,由于第一次同步传 -1

第二阶段: 主库返回 “FULLRESYNC 主库runID master_repl_offset(repl_backlog_buffer中主库偏移量)” ,由于是全量同步,从库会记录master_repl_offset,作为自己下一次增量同步的slave_repl_offset ;

而且主库会生成 RDB快照(执行bgsave命令), 通过网络发送给 从库,从库拿到RDB 快照删除从库本地数据,读快照,同步数据

第三阶段: 在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求,这些写请求会在replication buffer,和repl_backlog_buffer写一份,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作

2.主从级联模式分担全量复制时的主库压力
对于主库来说,全量同步还有两个地方压力:

生成RDB文件 和 传输RDB文件

当从库数量比较多,进行全量复制,就会多次fork子进程,生成RDB文件,会阻塞主进程

所以,我们可以选举处理出来一个从库A (一般选内存大,网络好的从库),让其他家从库同步从库A的数据,

就可以给主库减轻压力

一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。

基于长连接的命令传播 直接走 replication_buffer

3.主从网络连接断了怎么办
断了会使用增量复制

repl_backlog_buffer相当于主线程写操作的一个备份,就是为了防止从库断连接,在恢复时,无法恢复连接断开到恢复的数据

replication buffer:主要用来与客户端进行数据传输,它是传输通道,有几个客户端就有几个replication buffer;

无论全量复制、基于长连接的命令传播,以及增量复制(传输偏移量offset数据),数据传输都会用到replication buffer。

repl_backlog_buffer,只要有从库存在,这个repl_backlog_buffer就会存在。主库的所有写命令除了传播给从库之外,都会在这个repl_backlog_buffer中记录一份,缓存起来,只有预先缓存了这些命令,当从库断连后,从库重新发送psync $master_runid o f f s e t , 主 库 才 能 通 过 offset,主库才能通过offset,主库才能通过offset在repl_backlog_buffer中找到从库断开的位置,只发送$offset之后的增量数据给从库即可。

1、repl_backlog_buffer:它是为了从库断开之后,如何找到主从差异数据而设计的环形缓冲区,从而避免全量同步带来的性能开销。如果从库断开时间太久,repl_backlog_buffer环形缓冲区被主库的写命令覆盖了,那么从库连上主库后只能乖乖地进行一次全量同步,所以repl_backlog_buffer配置尽量大一些,可以降低主从断开后全量同步的概率。而在repl_backlog_buffer中找主从差异的数据后,如何发给从库呢?这就用到了replication buffer。

2、replication buffer:Redis和客户端通信也好,和从库通信也好,Redis都需要给分配一个 内存buffer进行数据交互,客户端是一个client,从库也是一个client,我们每个client连上Redis后,Redis都会分配一个client buffer,所有数据交互都是通过这个buffer进行的:Redis先把数据写到这个buffer中,然后再把buffer中的数据发到client socket中再通过网络发送出去,这样就完成了数据交互。所以主从在增量同步时,从库作为一个client,也会分配一个buffer,只不过这个buffer专门用来传播用户的写命令到从库,保证主从数据一致,我们通常把它叫做replication buffer。如果主从连接断开,那么replication_buffer就不存在了。

3、既然有这个内存buffer存在,那么这个buffer有没有限制呢?如果主从在传播命令时,因为某些原因从库处理得非常慢,那么主库上的这个buffer就会持续增长,消耗大量的内存资源,甚至OOM。所以Redis提供了client-output-buffer-limit参数限制这个buffer的大小,如果超过限制,主库会强制断开这个client的连接,也就是说从库处理慢导致主库内存buffer的积压达到限制后,主库会强制断开从库的连接,此时主从复制会中断,中断后如果从库再次发起复制请求,那么此时可能会导致恶性循环,引发复制风暴,这种情况需要格外注意。

repl_backlog_buffer环形缓冲区在主库中只有一个,每个replication_buffer对应一个客户端(redis client或者从库)

4.总结
Redis 的主从库同步的基本原理,总结来说,有三种模式:全量复制、基于长连接的命令传播,以及增量复制。

1. 第一次复制时 全量复制

2. 全量复制完成之后会建立长连接,进行数据同步

3. 如果连接断了,等连接恢复后就进行增量复制

5.《哨兵集群》
1.哨兵机制流程
主从机制下主库挂了存在的问题?

主库是否真的下线了?
选主机制,是怎么进行的?
怎么把主库信息通知给从库和客户端呢
什么是哨兵呢?

哨兵就是一个运行在特殊模式下的redis服务,主从实例在运行的同时,他们也在运行;

哨兵机制主要有三个作用:监视,选主,通知

监视:哨兵集群会每个一段时间ping 主从库, 如果主从库没有响应ping命令,那么就判定为“主观下线”

选主:如果主库被 “N/2+1” 个认为哨兵判定为“主观下线”,那么主库就被认定为 “客观下线”,这时候就要进行选主机制;

通知:选主成功后,需要把新主库信息,通知给客户端和从库,并让从库执行 replicaof 命令,与新主库进行数据同步

2.选主机制
1.选主之前我们来先看一下什么情况下会被判定为“客观下线”?

哨兵集群内当有 N/2+1 个哨兵节点 认为 “主观下线”就认为主库 “客观下线”;

对于从库,有一个哨兵判定为“主观下线”,那么就下线了,因为,从库挂了对整个redis集群来说没有太大影响;

2.怎样进行选主的?

先筛选,后打分

先按照 一定的条件筛选, 后 按一定条件 打分;

a.筛选:过滤掉下线的 从库,然后,过滤网络连接不好的从库?

那么怎么判定网络连接不好呢?

根据 down_after_milliseconds*10这个配置项,down_after_milliseconds为 网络连接最大超时时间,如果,从库连接超时超过这个时间就被认定为 断连,如果断连超过10次,就判定,该从库网络状态不稳定,不参与主库选举;

b.打分,根据 优先级->从库同步进度->从库ID号 进行三轮打分

第一轮:优先级最高的为主库(我们可以设置内存大,网络带宽高的从库 为高优先级),相同进行第二轮

第二轮:数据同步进度最快的为主库(每个从库都有一个slave_repl_offset值,越大代表同步的越快)相同进行第三轮

第三轮:从库ID号小的为主库

3.主从切换的过程中是否能够正常处理请求呢?
读写分离的情况下,读请求不影响个,写操作会出现异常;

写失败的持续时间=主从切换的时间+客户端感知到主库的时间

主从切换的时间我们可以通过 down_after_milliseconds(连接最大超时时间)设置,down_after_milliseconds值越小,哨兵集群就越敏感,down_after_milliseconds设置过小,可能会由于主库压力过大,或网络不好导致发生不必要的主从切换,但是当主库真正的故障时,down_after_milliseconds越小,对业务的影响也就越小;

当主从切换有写操作到达时,我们可以把 写操作放入客户端缓存或使用消息中间件缓存,主从切换完成,客户端感知到主库时,再读入缓存中写操作;注意,如果down_after_milliseconds过大,主从切换时间过长,会导致消息对列有大量写请求;

4.哨兵集群
配置哨兵信息 只需要设置主库的 IP 和端口

sentinel monitor

哨兵并没有 将自己的连接信息发送给主库,也就意味 哨兵之间是互相不知道地址的,那么哨兵是如何通信的呢?

问题:

哨兵之间是如何通信的?
哨兵和从库之间是如何通信的?
哨兵是如何把 主从切换后的新主库信息发送给客户端的?哨兵和客户端之间是如何通信的?
5.PUB/SUB模式解决哨兵,主从库,客户端之间的通信

1.哨兵之间是如何通信的?

通过发布/订阅模式,每个哨兵发布自己的连接配置信息给 主库上的 “sentinel:hello”频道,并订阅这个频道,这样就可以获取其他哨兵节点的信息啦;

2.哨兵和从库之间是如何通信的?

哨兵会发送INFO命令给主库,主库就会返回 “从库 信息列表” 给哨兵节点,这样哨兵节点就可以和从库之间建立长连接,保持心跳了

3.哨兵和客户端之间是如何通信的?客户端通过监控了解哨兵进行主从切换的过程呢?

不但主库提供发布订阅机制,哨兵节点也提供PUB/SUB机制,哨兵提供的消息订阅频道有很多,不同频道包含了主从库切换过程中的不同关键事件。

客户端同过 “SUBSCRIBE 频道 ” 监控主从库的装态了

比如:想监听主从切换完成的状态: SUBSCRIBE +switch-master

那么知道了这些频道,客户端就可以根据自己的需要订阅这些频道了,就能实时监控主从切换,甚至主从库的状态了

6.由哪个哨兵节点来执行主从切换呢?
使用了Raft的公式算法

任何一个哨兵节点判断主库“主观下线”后,都会发送 is-matser-down-by-addr命令给其他哨兵节点;

其他哨兵节点会根据自己与主库的连接状态返回对应的结果,连接完好返回不赞成N,断连返回Y

当某个哨兵节点拿到的赞成票>= quorum 的值,就判定主库“客观下线”

当一个哨兵节点判断一个 主库“客观下线”,就选举自己为Leader ,完成主从切换操作;

任何一个想成为 Leader 的哨兵,要满足两个条件:

第一,拿到半数以上的赞成票;

第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。

以 3 个哨兵为例,假设此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以了。

在 T1 时刻,S1 判断主库为“客观下线”,它想成为 Leader,就先给自己投一张赞成票,然后分别向 S2 和 S3 发送命令,表示要成为 Leader。

在 T2 时刻,S3 判断主库为“客观下线”,它也想成为 Leader,所以也先给自己投一张赞成票,再分别向 S1 和 S2 发送命令,表示要成为 Leader。

在 T3 时刻,S1 收到了 S3 的 Leader 投票请求。因为 S1 已经给自己投了一票 Y,所以它不能再给其他哨兵投赞成票了,所以 S1 回复 N 表示不同意。同时,S2 收到了 T2 时 S3 发送的 Leader 投票请求。因为 S2 之前没有投过票,它会给第一个向它发送投票请求的哨兵回复 Y,给后续再发送投票请求的哨兵回复 N,所以,在 T3 时,S2 回复 S3,同意 S3 成为 Leader。

在 T4 时刻,S2 才收到 T1 时 S1 发送的投票命令。因为 S2 已经在 T3 时同意了 S3 的投票请求,此时,S2 给 S1 回复 N,表示不同意 S1 成为 Leader。发生这种情况,是因为 S3 和 S2 之间的网络传输正常,而 S1 和 S2 之间的网络传输可能正好拥塞了,导致投票请求传输慢了。

最后,在 T5 时刻,S1 得到的票数是来自它自己的一票 Y 和来自 S2 的一票 N。而 S3 除了自己的赞成票 Y 以外,还收到了来自 S2 的一票 Y。此时,S3 不仅获得了半数以上的 Leader 赞成票,也达到预设的 quorum 值(quorum 为 2),所以它最终成为了 Leader。

如果这一轮没有产生Leader 就会进行下一轮投票

要保证所有哨兵实例的配置是一致的,尤其是主观下线的判断值 down-after-milliseconds。

1,哨兵投票机制:
a:哨兵实例只有在自己判定主库下线时,才会给自己投票,而其他的哨兵实例会把票投给第一个来要票的请求,其后的都拒绝
b:如果出现多个哨兵同时发现主库下线并给自己投票,导致投票选举失败,就会触发新一轮投票,直至成功

2,哨兵Leader切换主从库的机制:
哨兵成为Leader的必要条件:a:获得半数以上的票数,b:得到的票数要达到配置的quorum阀值

主从切换只能由Leader执行,而成为Leader有两个必要的条件,所以当哨兵集群中实例异常过多时,会导致主从库无法切换

6.《redis主从同步与故障切换的坑》
1.主从数据不一致
redis主库同步时,是异步传输命令,不会等待命令传输成功,有以下两种情况导致redis主从同步数据不一致

redis主库有网络延迟,导致从库没有接收到同步命令,此时刚好访问到改数据,就会返回旧数据
从库及时收到了主库的命令,但是,也可能会因为正在处理其它复杂度高的命令(例如集合操作命令)而阻塞。
解决方案:

针对第一种情况,主从在同一个局域网内,并保证网络通畅
第二种情况,我们还可以开发一个外部程序来监控主从库间的复制进度
Redis 的 INFO replication 命令可以查看主库接收写命令的进度信息(master_repl_offset)和从库复制写命令的进度信息(slave_repl_offset)。

我们就可以开发一个监控程序,先用 INFO replication 命令查到主、从库的进度,然后,我们用 master_repl_offset 减去 slave_repl_offset,这样就能得到从库和主库间的复制进度差值了。

如果某个从库的进度差值大于我们预设的阈值,我们可以让客户端不再和这个从库连接进行数据读取,这样就可以减少读到不一致数据的情况。不过,为了避免出现客户端和所有从库都不能连接的情况,我们需要把复制进度差值的阈值设置得大一些。

2.过期数据
1.reids过期策略
定期淘汰:Redis 每隔一段时间(默认 100ms),就会随机选出一定数量的数据,检查它们是否过期,并把其中过期的数据删除

惰性过期:读到过期数据删除

但是,这两种策略都不能保证不会读到过期数据;

对于惰性过期,Redis 3.2 之前的版本,从库在服务读请求时,并不会判断数据是否过期,直接返回过期数据;

Redis 3.2之后判断是否过期,过期返回null

2.EXPIRE,PEXPIRE,EXPIREAT和PEXPIREAT

当主从库全量同步时,如果主库接收到了一条 EXPIRE 命令,那么,主库会直接执行这条命令。这条命令会在全量同步完成后,发给从库执行。而从库在执行时,就会在当前时间的基础上加上数据的存活时间,这样一来,从库上数据的过期时间就会比主库上延后了。

所以在业务应用中使用 EXPIREAT/PEXPIREAT 命令,把数据的过期时间设置为具体的时间点,避免读到过期数据。

3.不合理配置项导致的服务挂掉(protected-mode 和 cluster-node-timeout。)
1.protected-mode
这个配置项的作用是限定哨兵实例能否被其他服务器访问,yes时,其余哨兵实例部署在其它服务器,那么,这些哨兵实例间就无法通信。当主库故障时,哨兵无法判断主库下线,也无法进行主从切换,最终 Redis 服务不可用。

protected-mode no
bind 192.168.10.3 192.168.10.4 192.168.10.5
1
2
2.cluster-node-timeout
当我们在 Redis Cluster 集群中为每个实例配置了“一主一从”模式时,如果主实例发生故障,从实例会切换为主实例,受网络延迟和切换操作执行的影响,切换时间可能较长,就会导致实例的心跳超时(超出 cluster-node-timeout)。实例超时后,就会被 Redis Cluster 判断为异常。而 Redis Cluster 正常运行的条件就是,有半数以上的实例都能正常运行。

所以,如果执行主从切换的实例超过半数,而主从切换时间又过长的话,就可能有半数以上的实例心跳超时,从而可能导致整个集群挂掉。所以,我建议你将 cluster-node-timeout 调大些(例如 10 到 20 秒)。

4.总结

Redis 中的 **slave-serve-stale-data 配置项设置了从库能否处理数据读写命令,**你可以把它设置为 no,这样一来,从库只能服务 INFO、SLAVEOF 命令,这就可以避免在从库中读到不一致的数据了。

slave-read-only 是设置从库能否处理写命令,slave-read-only 设置为 yes 时,从库只能处理读请求,无法处理写请求

7.《脑裂现象》
1.什么是脑裂?
所谓的脑裂,就是指在主从集群中,同时有两个主节点,它们都能接收写请求,脑裂一般发生在 主从切换的某一个环节。

一次数据丢失的排查?

第一步: 我们根据可以监控(INFO replication 可以获可取master_repl_offset和slave_repl_offset) 主库的写进度 master_repl_offset和 从库复制进度 slave_repl_offset,比较两者差值,如果差值过大,再看从库未复制的数据是否为丢失的数据; 如果master_repl_offset,slave_repl_offset一致,表明不是主从同步导致数据丢失,执行第二步;

第二步:查看redis客户端,在主从切换后的一段时间内,有一个客户端仍然在和原主库通信,并没有和升级的新主库进行交互。这就相当于主从集群中同时有了两个主库。

得出结论:出现脑裂现象;

2.脑裂发生的原因及脑裂为什么会导致数据丢失呢?
a.脑裂发生的原因
主库阻塞或 cpu资源被其他进程占用,获取不到cpu资源

主库执行耗资源易阻塞操作或发生内存 swap,无法及时响应客户端,响应哨兵心跳,例如:bigkey,keys等操作
主库所在机器cpu被其他 资源(比较消耗cpu的程序)占用,导致主库进入 “假死” 状态

b.脑裂为什么会导致数据丢失?

假如说,响应哨兵心跳的最大延时时间down-after-milliseconds 为12s, 主库由于cpu资源受限或 执行bigkey操作阻塞13无法响应请求,主从切换要3s,那么在第12s的时刻主库 就会被判定为 “客观下线”,第13s主库恢复正常,那么客户端就能够对主库进行 2s的写操作,这时 主从切换还没完成;

主从切换完成后,原主库降级为 从库,会清空自己的数据,读取 新主库的全量复制RDB文件,那么 “ 从切换还没完成的2s内的写数据” 就会丢失

3.如何避免脑裂问题?
主要用到两个配置项:min-slaves-to-write 和 min-slaves-max-lag

min-slaves-to-write : 主从同步时,主库同步的最少从库数量

min-slaves-max-lag: 主从同步数据复制时,主库给从库发送ACK最大延时时间(以秒为单位)

例子:

假设我们将 min-slaves-to-write 设置为 1,把 min-slaves-max-lag 设置为 12s,把哨兵的 down-after-milliseconds 设置为 10s,主库因为某些原因卡住了 15s,导致哨兵判断主库客观下线,开始进行主从切换。同时,因为原主库卡住了 15s,没有一个从库能和原主库在 12s 内进行数据复制,原主库也无法接收客户端请求了。这样一来,主从切换完成后,也只有新主库能接收请求,不会发生脑裂,也就不会发生数据丢失的问题了。

建议:

假设从库有 K 个,可以将 min-slaves-to-write 设置为 K/2+1(如果 K 等于 1,就设为 1),将 min-slaves-max-lag 设置为十几秒(例如 10~20s),在这个配置下,如果有一半以上的从库和主库进行的 ACK 消息延迟超过十几秒,我们就禁止主库接收客户端写请求。

8.《切片集群》
数据分片存储,数据如何分布到不同的实例?
客户端如何知道数据在哪个实例?
案例:Redis 保存 5000 万个键值对,每个键值对大约是 512B,大概有25GB数据

横向扩展:横向增加当前 Redis 实例的个数(不受硬件限制,大小几乎无限,不影响性能,但是复杂度增加)

纵向扩展:增大计算机内存和磁盘空间(受磁盘大小限制,性能低,但是配置简单)

1.数据分片存储,数据如何分布到不同的实例?
使用redis cluster 实现分片集群

引入槽(slot)的概念,使用CRC16算法计算key hash值,与16384取模,获得的数在0~16383的范围,数据就分部在

16384个槽中;

哈希槽又是如何被映射到具体的 Redis 实例上的呢?

两种方式为实例分配 槽:

cluster create 命令创建集群,Redis 会自动把这些槽平均分布在集群实例上。例如,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N 个。

如果每台机器内存,性能不一样,可以手动设置每台实例的槽数; cluster addslots 命令手动分配哈希槽。

例如:

redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4

注意: 手动分配槽需要把16384个槽分配完,否则redis集群无法工作

2.客户端如何定位数据?
一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。

那么,客户端为什么可以在访问任何一个实例时,都能获得所有的哈希槽信息呢?

这是因为,Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。

客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。

3.MOVED,ASK,ASKING
实例和槽的映射关系不是一成不变的:

删除,添加实例,redis会从新分配hash槽
为了负载均衡,redis需要把实例上的hash槽重新分配一遍
所以,hash槽和实例映射关系改变了怎么办?怎么定位数据呢?

redis cluster 提供了 重定向机制

1.MOVED命令

假如一个键值对 “hello:redis”,分布在实例A的10001这个槽,但是增加节点,数据重新,10001这个槽被分配给了实例B,

且10001槽的数已完全搬移,但是客户端无法感知,redis槽分配的内部变化,客户端内存里还是原来的 实例-槽 的映射关系;

客户端会先访问实例A(实例之间槽数据共享,实例A知道10001槽被分配给了实例B),找不到10001槽,那实例A就会返回 “MOVED 10001 实例B:port” 告诉 客户端数据在实例B上,那么客户端就会访问实例B,并更新本地 实例&槽 的映射关系,下次再找10001槽就直接去实例B上找。

2.ASK 命令

如果10001槽数据正在搬移到 实例B上呢?

那么实例A就会返回 “ASK 10001 实例B:port” ,告诉 10001槽在实例B上,但是10001槽数据还未搬移完;

3.ASKING

客户端收到 ASK命令,就会 发送ASKING命令去告知实例B我要去访问你的数据,你给我放行哦!

但是 ,ASK,ASKING命令,客户端不会更新本地 实例&槽 的映射关系表;

也就是说当10001槽还未完成数据搬移, 客户端又去 访问10001槽,还是会去实例A,而不是 实例B;

redis核心技术与实战(四)高可用高扩展篇相关推荐

  1. redis核心技术与实战(二)缓存应用篇

    1.<旁路缓存:redis 在缓存中工作原理> 1.缓存的两个特征 1.什么是缓存,有什么特征? 磁盘->内存->cpu 之间读写速度差异巨大,为了平衡他们之间的差异,操作系统 ...

  2. 亿级流量网站架构核心技术 跟开涛学搭建高可用高并发系统

    亿级流量网站架构核心技术 跟开涛学搭建高可用高并发系统 1.高并发原则 1.1 无状态 1.2 拆分 1.3 服务化 1.4 消息队列 1.5 数据异构 1.6 缓存银弹 1.7 并发化 2 高可用原 ...

  3. 读书笔记:《亿级流量网站架构核心技术 -- 跟开涛学搭建高可用高并发系统》

    from <亿级流量网站架构核心技术 – 跟开涛学搭建高可用高并发系统> 概述 一个好的设计要做到,解决现有的需求和问题,把控实现和进度风险,预测和规划未来,不要过度设计,从迭代中演进和完 ...

  4. 微服务Springboot实战大揭秘/高并发/高可用/高负载/互联网技术-任亮-专题视频课程...

    微服务Springboot实战大揭秘/高并发/高可用/高负载/互联网技术-320人已学习 课程介绍         Java架构师系列课程是针对有志向架构师发展的广大学员而设置,不管你是工作一到三年, ...

  5. 《Redis核心技术与实战》学习总结(1)

    [Redis]| 总结/Edison Zhou 0写在开头 作为Key/Value键值数据库,Redis的应用非常广泛.在之前多年的工作生涯中,我也只是关注了零散的技术点,没有对Redis建立起一套整 ...

  6. 分布式系统 概念 高可用 高并发 学习笔记

    分布式系统 概念 高可用 高并发 学习笔记 0. 分布式系统基本概念 0.1 背景 分布式系统是由一组通过网络进行通信.为了完成共同的任务而协调工作的计算机节点组成的系统.分布式系统的出现是为了用廉价 ...

  7. 《Redis核心技术与实战》学习总结(2)

    [Redis]| 总结/Edison Zhou 1上一篇的遗留问题 上一篇总结了一个KV数据库的基本架构 和 Redis的底层数据结构概览,重点总结了Sorted Set的两个数据结构的切换,但没有介 ...

  8. 高可用集群篇(五)-- K8S部署微服务

    高可用集群篇(五)-- K8S部署微服务 一.K8S有状态服务 1.1 什么是有状态服务 1.2 k8s部署MySQL 1.2.1 创建MySQL主从服务 1.2.2 测试主从配置 1.2.3 k8s ...

  9. 谷粒商城电商项目 高可用集群篇

    更多视频,JAVA收徒 QQ:987115885谷粒商城电商项目 高可用集群篇339.k8s-简介.mp4340.k8s-架构原理&核心概念.mp4341.k8s-集群搭建-环境准备.mp43 ...

  10. 分布式高可用高并发物联网(车联网-JT808协议)平台架构方案

    技术支持QQ:78772895 平台基于(<JT/T808-2011道路运输车辆卫星定位系统终端通讯协议及数据格式>以及<JT/T808-2013道路运输车辆卫星定位系统北斗兼容车载 ...

最新文章

  1. 关于IssueVision命令模式中对象生命周期的困惑
  2. 五、线程的概念和特点
  3. bat递归查找指定文件_dos命令find图文教程,查找搜索文件文本字符串,bat批处理脚本...
  4. “巨杉数据库”获1000万美元B轮融资,DCM领投
  5. C中结构体的存储分配
  6. 笨方法学python3怎么样_有个很笨的女朋友,是怎么样的体验?
  7. 末日博士:比特币不应出现在散户或机构投资者的投资组合中
  8. Hack the box靶机 Blunder
  9. Ubuntu20.04安装视频播放器SMPlayer
  10. efs android 分区 img,选择别人efs文件包都是.img格式的,怎么添加你们的.efs文? 爱问知识人...
  11. 求不规则立方体表面积java_立方体的表面积怎么求(测算表面积公式全集)
  12. Windows下运行LSD-SLAM
  13. Android app包下fragment详细使用
  14. 移动应用开发选型:向左还是向右?
  15. 数加生产制造执行系统(MES)——优势
  16. 互联网架构概述 互联网架构演变过程
  17. python练习题15:恺撒密码 I
  18. 电赛A题无线运动传感节点总结(一)
  19. linux下循环读取目录文件,Linux 文件目录树的遍历
  20. Threejs中的Shadow Mapping(阴影贴图)

热门文章

  1. php mysql 查询缓存_mysql 查询缓存使用详解
  2. html5 coverflow,使用FancyCoverFlow实现3D无限循环切换视图
  3. linux 定时器 代码,linux C++ 定时器代码
  4. BugkuCTF-WEB题网站被黑
  5. mac linux win三系统安装教程,【教程】macbook pro上安装三系统详解教程(mac os x+windows+linux ubuntu)...
  6. mysql从服务器配置_mysql主从服务器配置基础教程
  7. php 支付宝小程序授权登陆验签_星巴克“啡快”登陆支付宝小程序,让你“飞快”取到咖啡...
  8. cad设计院常用字体_趣谈 | 那些年我们看过的电气图纸(附CAD/EPLAN区别)
  9. linux wine 性能,Wine 1.9.16 发布,改善 GDI 性能
  10. mysql tree_MySQL树形遍历(二)