深圳创意网站建设,现在1做啥网站流量大,网站制作用什么编程,网站怎么做外部优化1.《redis架构组成》 1.redis学习维度 2.一个基本的键值型数据库包括什么#xff1f; 1.访问框架
redis通过网络框架进行访问#xff0c;使得 Redis 可以作为一个基础性的网络服务进行访问#xff0c;扩大了redis应用范围#xff1b;
过程#xff1a;如果客户端发送“pu…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重写是异步的吗
重写时会有由主线程forkfork会阻塞主线程出来一个后台子线程 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指令当前实例是哪个主库的从库
现在有实例 1ip172.16.19.3和实例 2ip172.16.19.5
在实例2上执行 replicaof 172.16.19.3,实例2就变成了实例1的从库并开始从实例 1 上复制数据
主从第一次同步时
第一阶段从库会发送 给主库 fsync -1 命令给主库告诉它我这是第一次同步我要求全量同步fsync 命令 有两个参数
主库runID唯一随机ID由于第一次同步所以从库不知道主库runID传“”
从库偏移量offset由于第一次同步传 -1
第二阶段 主库返回 “FULLRESYNC 主库runID master_repl_offsetrepl_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 bufferRedis和客户端通信也好和从库通信也好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/21” 个认为哨兵判定为“主观下线”那么主库就被认定为 “客观下线”这时候就要进行选主机制
通知选主成功后需要把新主库信息通知给客户端和从库并让从库执行 replicaof 命令与新主库进行数据同步
2.选主机制 1.选主之前我们来先看一下什么情况下会被判定为“客观下线”
哨兵集群内当有 N/21 个哨兵节点 认为 “主观下线”就认为主库 “客观下线”
对于从库有一个哨兵判定为“主观下线”那么就下线了因为从库挂了对整个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_offsetslave_repl_offset一致表明不是主从同步导致数据丢失执行第二步
第二步查看redis客户端在主从切换后的一段时间内有一个客户端仍然在和原主库通信并没有和升级的新主库进行交互。这就相当于主从集群中同时有了两个主库。
得出结论出现脑裂现象
2.脑裂发生的原因及脑裂为什么会导致数据丢失呢 a.脑裂发生的原因 主库阻塞或 cpu资源被其他进程占用获取不到cpu资源
主库执行耗资源易阻塞操作或发生内存 swap无法及时响应客户端响应哨兵心跳例如bigkeykeys等操作 主库所在机器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/21如果 K 等于 1就设为 1将 min-slaves-max-lag 设置为十几秒例如 1020s在这个配置下如果有一半以上的从库和主库进行的 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我要去访问你的数据你给我放行哦
但是 ASKASKING命令客户端不会更新本地 实例槽 的映射关系表
也就是说当10001槽还未完成数据搬移 客户端又去 访问10001槽还是会去实例A而不是 实例B