-
一、数据结构与特征
-
1、数据类型与使用场景
-
讲讲Redis的特点??
-
基于内存:读写访问速度快
-
持久性:可以通过将数据持久化到磁盘上的快照和日志文件来保证数据的持久性,防止数据丢失
-
多数据类型:提供丰富的数据结构,灵活适应不同场景
-
原子性操作:redis保证一个操作具有原子性,利于并发环境下的数据一致性
-
分布式:Redis提供分布式特性,可以将数据分布在多个节点上,提高可扩展性
-
-
Redis提供了哪些数据类型??分别适用什么场景
-
常⻅的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
-
String——SDS——计数、分布式锁、共享session信息
-
-
List——双向链表或压缩列表ziplist👉quicklist👉listpack——消息队列
lpush和brpop
或按照点赞顺序显示好友信息 -
Set——哈希表或整数集合——抽奖(去重)、共同关注(交集运算)、点赞(key是文章id,value是用户id)
-
Hash——ziplist或hash表👉listpack——缓存对象(⼀般对象⽤ String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来⽤ Hash 类型存储)
-
Zset——ziplist或跳表——排行榜(权重排序)
-
----------------------------------------------------------------------------------------------------
-
BitMap——签到统计、判断用户登录态
-
HyperLoglog——基数统计,如百万级网页UV计数等
-
Stream——消息队列,全局唯一消息id
-
-
String类型的底层数据结构是什么??效率如何??
-
String类型的底层结构是简单动态字符串(SDS)
-
优势1:快速确定长度——SDS在头部保存了字符串的长度信息,O(1)时间内便可获取字符串的长度 ,int len字段记录字符长度
-
优势2:动态扩容——SDS可以根据实际存储的数据动态扩容。当字符串⻓度变⻓时,SDS会⾃动进⾏内存的扩展,int free字段记录扩容后剩余空间
-
优势3:二进制安全——通过len字段确定长度后,在字符中间会跳过干扰的\0结束标识符
-
-
List类型的底层数据结构在redis中的发展历程??
-
双向链表或压缩列表ziplist👉quicklist👉listpack
-
压缩列表ziplist不记录前后节点指针,记录前一个节点的长度、encoding节点数据的数据类型和长度、content记录节点数据
-
-
ziplist特性:压缩列表可以看作一种连续内存空间的双向链表;记录上一节点和本节点长度来寻址,内存占用低(比链表记录指针占用低);记录列表和哈希表数据👉📢List类型或Hash类型底层或Zset
-
ziplist存在连锁更新的问题:增或删除较大数据时由于产生连续多次空间扩展操作发生“连锁更新”问题
-
-
-
-
Redis3.2之后List类型的底层数据结构由什么支持??
-
quicklist:双端链表,每个节点是一个ziplist ;2.节点采用ZipList,解决了传统链表的内存占用问题;3. 控制了ZipList大小,解决连续内存空间申请效率问题,中间节点可以压缩,进一步节省了内存
-
-
-
Redis7.0后List类型的底层数据结构由什么支持了??
https://juejin.cn/post/7220950867339247653
-
ziplist的内存布局
-
-
listpack内存布局
-
-
区别:ziplist条目保存了上一个条目的长度信息,而listpack则保存了自己的长度信息。当我们向 listpack 加⼊⼀个新元素的时候,不会影响其他节点的⻓度字段的变化,从⽽避免了压缩列表的连锁更新问题。
-
-
redis中跳表这种数据结构支撑哪种数据类型??ZSET的数据结构是怎么样的??
-
zset。因为zset要支持随机的插入和删除,所以不适合用数组来实现。
-
-
同时zset支持排序,我们容易想到
红黑树/平衡树
这样的树形结构来支持,但是redis考虑....所以选择跳表来实现排序的效果。【为什么Zset不用树型结构?】 -
内存占用——跳表比平衡树更灵活
-
性能——高并发情况下,树形结构需要执行rebalance这种涉及整棵树的操作,而跳跃表的变化只涉及局部
-
实现简单——在复杂度和红黑树相同的情况下,跳表的实现更简单和直观。
-
-
了解跳表这种数据结构吗??讲讲它结构设计??
-
跳表是为了解决链表查找元素效率低,在链表的基础上改进而实现的一种“多层”的有序链表,能够快读查找。所谓多层,是指,链表中每个节点维持了多个以层级的关系指向其他节点的指针。
-
-
跳表zskiplist由头尾节点、跳表长度、跳表最大层数组成
-
跳表的节点zskiplistNode:
-
-
1、层:节点的level数组包含多个元素,每个元素包含一个指向其他节点的指针,层数越多访问其他节点的速度越快
-
2、前进指针:指向下一节点
-
3、跨度:记录两个节点间的距离
-
4、字符串和分值:SDS类型元素(通过score找key)和double类型元素
-
-
跳表节点是怎么实现查询的??
-
从头节点的最高层开始逐一遍历每一层
-
同层查找:权重小于目标权重,权重等但SDS小于目标SDS
-
下层查找:上述均不符合的时候,跳表会使用level数组的下一层指针
-
-
跳表节点的层数设计有什么原则吗??
-
需要维持相邻层节点数之间的关系,跳表的相邻两层的节点数量最理想的比例是 2:1,查找复杂度可以降低到 O(logN)。
-
合理控制层数和元素分布,可以在性能和内存之间取得良好平衡
-
怎么样维持这样的比例??
-
跳表在创建节点的时候,随机生成每个节点的层数。随机函数基于幂次定律,即越大的数出现的概率越小。
-
-
-
Redis为什么使用跳表??
-
跳表是什么?实现原理是什么?
-
1.性能优势:
-
快速查找:跳表的多层结构允许其在查找元素时能够快速定位,时间复杂度为 O(log n),与平衡树相当,但实现更简单。
-
高效插入和删除:跳表在插入和删除元素时也具有 O(log n) 的时间复杂度,因为其结构使得这些操作无需像某些树结构那样进行复杂的平衡调整。
-
-
2.实现简单:相比平衡树等复杂的数据结构,跳表的实现相对简单,尤其是在处理大量并发操作时,其锁机制更容易实现和维护。
-
3. 并发性能:跳表的结构使其在高并发环境下表现良好。Redis 作为一个高性能的内存数据库,需要在多客户端并发访问时保持高效的读写性能,跳表能够很好地满足这一需求。
-
4. 使用场景:Redis 中的有序集合(ZSET)需要支持按分数排序、范围查询等功能,跳表的有序性和快速范围查询能力使其成为理想的选择。
-
-
了解redis中哈希表的数据结构吗??rehash过程是什么样的??
-
hashtable是个数组结构,定义了两个哈希表第一个默认修改,第二个用于rehash拷贝,两个哈希表的作用视情况更换
-
哈希表中解决哈希冲突是用链式冲突
-
-
-
渐进式rehash是当涉及扩容(哈希冲突导致链表长度过长时)的数据迁移方案。
rehash触发条件——负载因子过大。=已保存节点/哈希表大小
-
给哈希表2分配空间
-
在rehash进行期间,每次哈希表元素进行在增删改查时,Redis除了会执行相应操作之外,还会顺序将哈希表1中的索引位置上所有key-value迁移到哈希表2上 查找key值,表1找不到去表2找 新增kv,直接到表2——表1的kv数量只会减少,最终为空
-
随着处理请求累积,最终能够完成全部数据的迁移,完成rehash操作
-
-
-
你能说说redis中不同数据结构分别支持什么样的数据类型吗??
-
-
讲讲3种常用的缓存读写策略
-
Cache Aside Pattern
-
read/Write Through Pattern
-
Write Behind Pattern
-
-
String还是Hash存储对象更好??
-
购物车信息用String还是Hash存储更合适??
-
为什么用跳表而不是平衡树、红黑树或B+树??
-
-
2、Redis单线程
-
Redis是单线程的吗??
-
Redis单线程是指网络请求模块使用单线程处理,其他模块仍用多个线程。
-
Redis程序并不是单线程的,有处理关闭文件、AOF刷盘、lazyfree等后台线程,这些线程由于耗时长,如果放在主线程来处理,则易导致阻塞
-
-
讲讲Redis单线程为什么快??
-
1、基于内存操作:避免传统数据库的磁盘I/O瓶颈。内存的读写速度远高于磁盘,这使得Redis能够实现超高的响应速度。
-
2、非阻塞单线程:对于客户端的请求,Redis单线程处理,因此避免多线程环境存在的上下文切换和锁竞争问题
-
原子性操作,无需考虑并发问题
-
高效性,无上下文切换和锁竞争,CPU利用率更高
-
-
3、IO多路复用技术:采⽤I/O多路复⽤机制同时监听多个Socket,内核会⼀直监听这些 Socket 上的连接请求或数据请求。⼀旦有请求到达,就会根据Socket上的事件来选择对应的事件处理器进⾏处理,这就实现了⼀个 Redis线程处理多个 IO 流的效果。
-
4、高效的数据结构:Redis专⻔设计了STRING、LIST、HASH等⾼效的数据结构,依赖各种数据结构提升了读写的效率。
-
-
你知道IO多路复用的底层实现是什么样的吗??
-
IO多路复用模型:客户端的请求socket传入
-
-
以 Redis 为例,它使用了名为 epoll 的 IO 多路复用机制(在 Linux 系统中)。其工作原理如下:
-
Redis 服务器创建一个 epoll 实例,用于监听多个 Socket 连接。
-
当有客户端连接到 Redis 服务器时,该连接会被注册到 epoll 实例中。
-
epoll 会不断监听这些 Socket 连接上的事件(如客户端发送数据、客户端断开连接等)。放入一个队列中
-
当有事件发生时,epoll 会通知 Redis 服务器,服务器再根据事件类型进行相应的处理(如读取数据、发送响应等)——文件事件分派器,分发给命令请求/回复/应答处理器
-
-
Redis真的只有单线程吗??
-
Redis 6.x开始引入了多线程处理网络IO,但核心命令执行依然是单线程,确保性能和一致性。
-
-
-
3、过期策略与内存淘汰机制
-
过期删除有哪些策略??
-
定时删除:设置 key 的过期时间时,同时创建⼀个定时事件,当时间到达时,由事件处理器⾃动执⾏ key 的删除操作。这样可以保证过期的 key 会被尽快删除,但删除过期 key 会占⽤⼀部分 CPU 时间,对CPU不友好。
-
惰性删除: 不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。这样会使⽤很少的系统资源,对CPU友好,但是可能会导致过期key⻓期不被删除,浪费内存空间。
-
定期删除:每隔⼀段时间「随机」从数据库中取出⼀定数量的 key 进⾏检查,并删除其中的过期key。这种⽅法减少了删除操作对cpu的影响,也减少了内存空间的浪费,但是效果也没有两者好,且难以确定删除操作执⾏的时⻓和频率。
-
-
Redis的过期删除策略是什么??
-
Redis 选择「惰性删除+定期删除」这两种策略配和使⽤,以求在合理使⽤ CPU 时间和避免内存浪费之间取得平衡。
-
惰性删除:每次返回或修改key之前,检查key的TTL是否过期,过期则删除
-
定期删除:每个一段时间随机从数据库中取出一定数量(通常20)进行检查,如果一轮中过期数量超过25%,再抽20,,,否则,结束。
-
-
Redis如果报内存错误,可以由哪些反向解决??
-
1、配置参数maxmemory,增加Redis可用内存
-
2、调整内存淘汰策略,及时释放内存空间
-
3、Redis集群模式,横向扩容
-
-
Redis的内存淘汰策略有哪些??
-
一共6种内存溢出策略
-
1、noeviction:默认策略,不删数据,拒绝写入操作并返回客户端错误信息,只相应读操作
-
2、进行数据淘汰:
-
VolatileTTL:优先淘汰更早过期的键值。
-
VolatileLRU: 只对带有过期时间的键使⽤LRU策略,其他键使⽤NoEviction策略。
LRU least Recently Used:最近最少使用
-
VolatileRandom: 只对带有过期时间的键使⽤随机淘汰,其他键使⽤NoEviction策略。
-
VolatitleLFU: 只对带有过期时间的键使⽤LFU策略,其他键使⽤NoEviction策略。
LFU least Frequently Used:使用频率最低
-
AllKeysLRU: 根据最近最少使⽤的原则淘汰最久未使⽤的键。
-
-
Redis中LRU和LFU算法有什么区别??
-
LRU:优先淘汰最近最少被使用的对象(最近-时间层面-最近被用过的更可能再次备用)
-
Redis 实现的是⼀种近似 LRU 算法,在 Redis 的对象结构体中添加⼀个额外的字段,⽤于记录此数据的最后⼀次访问时间。当 Redis 进⾏内存淘汰时,会使⽤随机采样的⽅式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使⽤的那个。
-
LFU:优先淘汰使用频率最低的对象(调用层面-使用少的未来也用得少)
-
LFU维护⼀个使⽤计数,每当⼀个对象被访问时,其使⽤计数增加。在需要淘汰对象时,选择使⽤计数最低的对象淘汰。
-
-
-
4、网络模型与通信协议
-
讲讲阻塞IO模型和非阻塞IO模型二者的概念和优缺点
-
阻塞IO模型在用户态发出读取数据申请和等待过程中阻塞、内核缓冲返回数据的过程也阻塞——两阶段均阻塞,会使CPU阻塞,性能差
-
非阻塞IO模型在发出读数申请阶段是循环往复,直到数据就绪,非阻塞,内核返回数据过程是阻塞的——第一阶段非阻塞,但CPU可能空转,二阶段阻塞
-
-
IO多路复用模型的原理是什么??
-
不依次处理IO事件,而是哪些内核数据就绪了,就去处理对应的IO事件。IO复用模式可以确保读数据的时候,数据一定是存在的,避免了阻塞。
-
判断内核数据是否就绪,取决于监听FD(File Descriptor)是否可读可写
-
IO多路复用是利用单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。不过监听FD的方式、通知的方式又有多种实现,常见的有:select、poll、epoll
-
-
IO多路复用模型应用不同函数的实现是什么样的?几种函数的区别是什么呢??
-
1、select模式:
-
能监听的FD(File Descriptor)最大不超过1024,有上限
-
每次select都需要把所有要监听的FD都拷贝到内核空间
-
用户态每次都要遍历所有FD来判断就绪状态
-
2、poll模式的问题:
-
poll利用链表解决了select中监听FD上限的问题,但依然要遍历所有FD,如果监听较多,性能会下降
-
3、epoll模式解决上述两种模式的缺陷
-
基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查效率都非常高
-
每个FD只需要执行一次epoll_ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝FD到内核空间
调用epoll_wait可以得到通知
-
利用ep_poll_callback(回调函数)机制来监听FD状态,无需遍历所有FD,因此性能不会随监听的FD数量增多而下降
-
-
五种IO模型的对比
-
-
-
-
二、持久化与高可用
-
1、RDB与AOF
-
了解Redis的持久化机制吗??
-
Redis有两种持久化技术将
数据持久化到磁盘上
,分别是AOF日志和RDB快照。默认下会开启RDB,不开启AOF。 -
AOF-Append Only File,指每执⾏⼀条写操作命令,就把该命令以追加的⽅式写⼊到⼀个⽂件⾥;
必须写完再追加写操作命令,一能避免额外的检查开销,二不会阻塞当前写操作命令的执行
-
RDB-是在指定的时间间隔内将内存中的数据通过异步生成数据快照并且以二进制的方式保存到磁盘中
-
-
AOF有哪些写回策略,这些策略如何选取??执行原理你了解吗??
-
Redis写入AOF日志的过程如下图
-
-
Always是每次写操作命令执⾏完后,同步将 AOF ⽇志数据写回硬盘;每次写入 AOF 文件数据后,就执行 fsync() 函数;
-
Everysec每次写操作命令执⾏完后,先将命令写⼊到 AOF ⽂件的内核缓冲区,然后每隔⼀秒将缓冲区⾥的内容写回到硬盘;创建一个异步任务来执行 fsync() 函数;
-
No就是不控制写回硬盘的时机。每次写操作命令执⾏完后,先将命令写⼊到 AOF ⽂件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。永不执行 fsync() 函数
-
-
高性能选NO;高可靠选Always;允许丢失一点数据+高性能选Everysec
-
执行原理:三种策略只是在控制 fsync() 函数的调用时机。
-
-
AOF持久化机制的重写机制是什么?你是否了解??
-
AOF日志文件过大会带来性能问题,如重启redis后读取AOF恢复数据速度变慢
-
为了避免日志文件过大,Redis提供了AOF重写机制,当文件大小超过一定阈值,则启用,以压缩AOF文件
-
重写机制:直接扫描数据中所有的键值对,根据每一个键值对当前最新的状态生成一条写操作命令,接着将该命令写入新的AOF文件中,重写完成后,用新的AOF文件替换原AOF文件。
-
重写的过程是由后台子进程完成的,不阻碍主进程正常处理命令
-
为什么不复用原AOF,而要写新AOF文件?
-
因为如果复用,但重写失败的话,会污染原来的AOF;而新AOF重写失败则弃之,对原AOF文件无影响
-
-
-
AOF持久化机制和AOF重写机制的实现区别是什么??
-
AOF写入日志是在主进程完成的
-
AOF重写机制则是在后台bgrewriteaof子进程中完成的
-
为什么不用线程而用子进程?
-
如果使用线程,多线程之间会共享内存 ,那么修改共享内存时,需要加锁来保证数据的安全,导致性能降低 ;
-
而使用子进程,父子进程通过“只读”方式共享内存数据,任一进程修改了共享内存,就会发生
写时复制
,于是父子进程就有了独立的数据副本,不用加锁,就不会降低性能 -
同时也能避免阻塞主线程
-
-
-
讲讲AOF是如何在后台子进程中重写的??
-
fork创建bgrewriteaof子进程
-
-
step1复制页表:创建子进程时,操作系统会把主进程的“页表”复制一份给子进程,“页表”记录虚拟地址与物理内存地址的映射关系。父子进程的虚拟内存地址不同,但指向同一处物理内存地址。即实现父子进程以“只读”方式共享物理内存数据
-
-
step2写时复制Copy-On-WriteCOW:任一进程向物理内存执行写操作时,CPU触发写保护中断,复制“被修改”的物理内存,重新设置映射关系,修改父子进程的内存权限为“可读写”。之后才进行内存的“写操作”,这个过程就叫“写时复制”
写时复制顾名思义:在发生写操作时,操作系统才会复制物理内存。可以避免由于物理内存过大复制时间过长导致父进程长时间的阻塞
-
-
Step3重写缓冲:重写AOF过程中主进程修改了已存在的key-value,为了解决数据不一致问题,在创建bgrewriteaof子进程后同时启用“AOF重写缓冲区”。即AOF重写期间,主进程需要执行客户端的命令、写命令追加到AOF缓冲区与AOF重写缓冲区(3个工作过)
-
Step4信号处理:当子进程完成AOF重写,向主进程发生信号,收到信号则调用“信号处理函数”,将AOF重写缓冲区的命令追加到AOF文件中;将新的AOF改名并覆盖原来的AOF文件
-
-
AOF重写会在哪些时机被触发??
-
时机⼀:bgrewriteaof 命令被执⾏。
-
时机⼆:主从复制完成 RDB ⽂件解析和加载(⽆论是否成功)。
-
时机三:AOF 重写被设置为待调度执⾏。
-
时机四:AOF 被启⽤,同时 AOF ⽂件的⼤⼩⽐例超出阈值,以及 AOF ⽂件的⼤⼩绝对值超出阈值。
-
另外,这⾥还需要注意,在这四个时机下,其实都不能有正在执⾏的 RDB ⼦进程和 AOF 重写⼦进程,否则的话,AOF 重写就⽆法执⾏了。
-
-
AOF重写工作在什么情况下会对主线程造成阻塞??
-
创建子进程期间:页表复制时,页表过大可能会造成阻塞
fork()函数是主线程调用的,该函数阻塞,则Redis主线程阻塞
-
写时复制期间:如果修改的数据为大key,需要拷贝的物理内存越大,阻塞时间越长
-
信号处理期间:AOF重写缓冲区的追加可能会导致阻塞
-
-
RDB是什么?为什么会有RDB?解决了什么问题??
-
RDB-Redis Database Backup file, RDB全量快照记录了某一瞬间的「所有内存数据」,记录的是实际数据。二进制数据
AOF记录的是命令操作的日志,不是实际的数据
-
命令
save
直接在主线程中生成RDB文件;命令bgsave
创建子进程来生成RDB文件 -
每次执⾏快照,都是把内存中的「所有数据」都记录到磁盘中。执行快照是个比较繁重的操作,如果频繁执行,对redis性能产生影响;如果频率太低,遇到服务故障时,丢失的数据库会更多。
-
-
指定RDB快照时,主进程能够修改key value吗??
-
执行bgsave过程中,redis依然可以继续处理操作命令,数据可以被修改
-
写时复制技术Copy-On-Write:针对主进程要修改的数据,会复制一份物理内存,主进程对这块数据副本进行修改操作。那么RDB快照的数据和修改后的数据不一致的,需要下一次RDB快照才能同步更新
-
-
写时复制技术是如何应用到AOF和RDB持久化机制中的??
-
均在父子进程需要发生写操作时,针对“待修改”的物理内存进行复制,并修改映射页表的映射关系,之后再对物理内存做相应的修改,“读写”共享。这样父子进程有自己的数据副本,互不影响。
-
其他物理内存区域仍然是“只读共享”
-
-
Redis4.0提出的混合持久化机制是如何工作的??
-
AOF重写子进程会先将主线程共享的内存数据以RDB快照的方式写入AOF文件,
-
而记录在AOF重写缓冲区的操作命令会写入AOF文件。
-
此时AOF文件由“是 RDB 格式的全量数据”和“AOF 格式的增量数据”
-
优点:重启Redis加载数据加载速度变快,丢失数据变少
-
缺点:AOF文件可读性变差,且兼容性较差
-
-
Redis大Key会对持久化产生什么影响??
-
AOF 写回策略采用Always时,主线程在执行 fsync() 函数的时候,阻塞的时间会比较久
-
AOF重写和RDB快照过程,都分别会
fork()
函数创建一个子进程来处理任务,以下可能导致父进程阻塞: -
1、创建子进程时,大Key会影响页表大小,越大则阻塞时间越长
-
2、父进程修改了共享内存中的大Key,触发“ 写时复制”,大Key占用的物理内存越大,复制过程时间越长,可能阻塞父进程
-
-
Redis大Key除了会影响持久化,还有可能影响哪些方面??
-
客户端超时阻塞:由于Redis是单线程处理请求命令,处理大Key可能比较耗时,阻塞Redis主进程
-
引发网络阻塞:每次获取大Key产生的网络流量较大
-
阻塞工作线程:使用del删除大Key会阻塞工作线程
-
内存分布不均:主线程在执行 fsync() 函数的时候,阻塞的时间会比较久
-
-
如何避免大KEy??
-
设计阶段:将大key拆分,尽量避免存入大key
-
定期检查:定期检查Redis是否存在大Key,如果大Key可删除,不使用del命令,而使用unlink命令异步删除大key
-
-
-
2、主从复制原理
-
什么是主从复制??
-
Redis的主从复制机制,保证单个Redis服务宕机后,有备份节点继续提供服务。该模式能够保证多台服务器的数据一致性,采用“读写分离”的方式
-
-
-
主从复制的第一次同步阶段的时序操作是怎么样的??
-
分为三个阶段
-
-
第一阶段建立连接,协商同步:从节点执行
replicaof
命令(Redis5.0之前用slaveof)形成主从关系,随机发送psync?-1
命令,表示要进行数据同步;随后主服务器fullresync
作为相应命令返回给对方,全量复制 -
第二阶段主服务器同步全部数据给从服务器:主服务器会执行 bgsave 命令来生成 RDB 文件,然后把RDB文件传输给从服务器。将主服生成RDB期间/发送RDB期间/从服加载RDB期间的主服新写操作命令记录到replication buffer缓冲区。
-
第三阶段:主服务器发送新写操作命令给从服务器:发送缓冲区中记录的写操作命令给从服务器,从服务器再次执行,此时主从服务器的数据一致。
-
-
主从服务器的数据一致性是如何保证的??
-
第一次同步的过程中,在异步传输RDB文件的过程中将主服的新写操作记录到缓冲区,随后从服务再次加载,以保证数据一致性。
-
完成第一次同步后,通过维护长连接命令传播维护 数据一致性:
-
完成第一次同步后,主从服务器之间会维护一个TCP连接,后续主服会通用该连接将写操作命令传播给从服,从服执行命令。而且这个连接是长连接的,目的是避免频繁的 TCP 连接和断开带来的性能开销。(基于长连接进行传播)
-
-
主从服务器建立连接后是如何同步写操作命令的??
-
基于长连接的命令传播。
-
主服在发送写命令给从服的同时,还会将写命令写入repl_backlog_buffer 缓冲区里,因此 这个缓冲区里会保存着最近传播的写命令。
-
-
主从服务器的第一次全量复制可能带来什么问题??如何缓解这个问题??
-
第一次全量复制的过程中,主进程生成RDB文件和传输RDB文件是耗时的操作
-
阻塞主进程:当有大量从服务器时,主服务器需要忙于使用fork()来创建子进程,如果主服中内存数据大,则fork()执行消耗的时间长,导致阻塞主进程,影响Redis正常处理请求。
-
占用网络带宽:传输RDB文件会占用主服务器的网络带宽,对主服的相应命令请求产生影响。
-
解决方式:从服务器可以扮演“主服务器”经理角色,将数据同步给从服务器。主服务器将压力分摊给经理角色的从服务器
-
-
-
主从服务之间的TCP长连接如果断开了,恢复后如何保证主从的数据一致性??
-
增量复制再次同步网络断开期间的写操作命令。
-
-
主服如何确定要将哪些增量数据同步?参考以下两个参数:
-
repl_backlog_buffer是一个环形缓冲区,长连接过程中会将写操作命令同步存入该缓冲区
-
replication_offset 复制偏移量,该参数标记缓冲区的同步进度,主服用master_repl_offset记录自己“写”到的位置,从服用slave_repl_offset记录自己“读”到的位置
-
网络断开重连之后,从服务器会通过psync命令将自己的slave_repl_offset发送给主服务器,主服根据自己的offset计算偏差再决定选择增量同步还是全量同步。随后将增量数据写入repliaction buffer缓冲区
-
-
-
如何避免网络断开恢复后频繁使用全量同步,而多采用增量同步?
-
调整repl_backlog_buffer缓存区大小尽可能大一些,减少出现从服务器要读取的数据被覆盖的概率,从而使得主服务器采用“增量同步”的方式。
-
调整的参考是以下公式,second为断线后重连花费的平均时间,per为断线期间主服平均产生的写命令数据量大小
比如,主平均产生1MB的写命令,断线恢复时间平均为5s,则buffer内存大小不能低于5MB,为了应对突发情况,设置为2倍以上10MB更保险
-
-
-
如何判断Redis某个节点是否正常工作??
-
主从节点之间维持ping-pong心跳检测机制来检测对方的存活状态。如果一个节点被很多节点ping都没有pong回应,就认为这个节点挂掉了,就会断开与这个节点的连接。
-
-
主从节点发送心跳间隔是一样的吗??
-
Redis主节点默认每隔10s对从节点发送ping命令,判断从节点的存活性和连接状态。
-
Redis从节点每隔1s就会发送replconf ack{offset},给主节点上报自身当前的复制偏移量
-
能够实时监控主从节点的网络状态
-
上报offset检查复制数据是否丢失,若丢失,可以及时从主节点的repl buffer区拉取数据,保证数据一致性
-
-
主从复制架构中,过期的key如何处理??
-
主节点处理了一个key或者通过淘汰算法淘汰了一个key,这个时间主节点模拟一条del命令发送给从节点,从节点接受该命令后就进行删除key的操作
-
-
主从复制中两个Buffer(repl buffer、repl backlog buffer)缓存区有什么不同??
-
1、出现的阶段不同:
-
repl backlog buffer只在增量复制阶段出现,一个主节点分配一个对应的buffer
-
repl buffer在全量复制和增量复制阶段都会出现,主节点会给每个新连接的从节点分配一个buffer
-
-
2、缓冲区内存满了有不同区别
-
repl backlog buffer满了,由于是环形缓冲区,会直接覆盖起始位置的数据
-
repl buffer满了,会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制
-
-
-
如何避免/应对主从复制机制可能出现主从节点的数据不一致问题?
-
主从数据不一致,指客服端从从节点读取到的值与主节点中的最新值不一致
-
出现原因:主从节点间的命令复制是异步进行的,无法达到强一致性。
-
1、保证网络稳定性:尽量保证主从节点间的网络连接状况良好,避免主从的节点在不同的机房
-
2、监控和报警:可以开发外部监控系统来实时监测主从节点的状态,包括复制延迟(offset差值)、连接状态等等。设置报警机制:当某个从节点的进度差值大于我们预设的与之,可以让客户端不在和这个从节点连接读取数据
-
-
主从切换什么情况会导致数据丢失??
-
丢失原因:异步复制同步丢失、集群产生脑裂数据丢失
-
-
异步复制如何减少数据的丢失??
-
主节点处理完写操作给客户端返回ok,随后异步同步给从节点,此时若发生断电,从节点数据丢失。
-
解决方案:合理配置Redis的参数min-salves-max-lag,表示一旦所有的从节点数据复制和同步的延迟超过了该参数值,则主节点会拒绝接受客户端发送的请求。
假设设为10s,如果数据同步所需要的时间超过10s,master拒绝写入新请求。这样将主从节点的数据差控制在10s内,即使 master 宕机也只是这未复制的 10s 数据。
-
当客户端发现master不可写了,则采取缓存降级措施,将数据暂时写入本地缓存和磁盘中;或者将数据写入Kafka消息队列。等master恢复正常,重新写入。
-
-
-
3、Redis哨兵机制
-
什么是哨兵机制??为了解决什么问题??
-
哨兵机制是一种监控和管理Redis实例实现自动故障转移和高可用性的机制,作用是实现主从节点故障转移。
-
哨兵系统由由⼀组独⽴运⾏的进程组成,这些进程定期检查Redis主节点和从节点的状态,并在主节点宕机时选择⼀个从节点升级为新的主节点,并将新master相关信息通知给slaves和客户端。
-
哨兵其实也是一个节点,相当于”观察者节点“,观察对象是主从节点
-
-
哨兵机制是如何工作的?工作原理是什么??
-
主要负责三件事:监控、选主、通知
-
1. 监控
-
哨兵定期检查与其关联的所有Redis实例的健康状况,包括主节点和从节点。
-
2. 故障检测
-
当哨兵检测到主节点不可⽤(宕机)时,会尝试选举⼀个新的主节点。
-
3. 故障转移
-
选举新主节点后,哨兵会通知所有相关的从节点更新其配置,使其将选出的新主节点作为其新的主节点。
-
4. ⾃动恢复
-
⼀旦主节点重新可⽤,哨兵会将其重新添加到系统中,并根据需要将其配置为从节点,以提供故障恢复和负载分担。
-
哨兵机制使得Redis在主节点发⽣故障时能够快速进⾏⾃动故障转移,确保系统的⾼可⽤性。
-
-
哨兵如何判断主节点真的故障了??
-
哨兵节点每隔1s给所有主从节点发送ping命令,所有节点会发送响应命令给哨兵。
-
如果有节点没有在规定的时间内没有响应哨兵节点的PING命令,那么哨兵会将它们标记为”主观下线“
-
当一个哨兵判断主节点为「主观下线」后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。
-
当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,主节点被标记为”客观下线“——真的故障了
quorum 的值一般设置为哨兵个数的二分之一加 1,例如 3 个哨兵就设置 2。
-
-
为了避免哨兵误判节点的状态,Redis是如何解决的??
-
哨兵集群:多个节点部署成哨兵集群(最少需要三台机器来部署哨兵集群),通过多个哨兵节点一起判断,就可以就可以避免单个哨兵因为自身网络状况不好,而误判主节点下线的情况。
-
哨兵对主观下线的主节点进行投票判定,如果赞同票超过一定值,才认定主节点发生了故障
-
-
故障转移过程如何选出新主节点??
-
优先级→复制进度→ID号
-
-
过滤掉已经离线的从节点;过滤掉历史网络连接状态不好的从节点;将剩下的从节点,进行三轮考察:优先级、复制进度、ID 号。
-
选出新master后,向其发送SLAVEOF no one 命令,哨兵 leader 会以每秒一次的频率向被升级的从节点发送 INFO 命令,当被升级节点的角色信息从原来的 slave 变为 master ,说明升级成功
-
-
故障转移过程具体有哪几个步骤??
-
step1:选出新主节点——过滤掉已经离线的从节点;过滤掉历史网络连接状态不好的从节点;将剩下的从节点,进行三轮考察:优先级、复制进度、ID 号。
-
step2:让已下线主节点属下的所有「从节点」修改复制目标,修改为复制「新主节点」——通过向「从节点」发送 SLAVEOF 命令来实现
-
step3:通知客户的主节点已更换——通过Redis的发布订阅机制实现。
-
客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息。客户端收到该信息后则用新master的IP地址和端口进行通信
-
step4:将旧主节点变为从节点——当旧主节点重新上线时,哨兵集群就会向它发送SLAVEOF 命令
-
-
哨兵集群是如何组成的??又是如何监控从节点动态的??
-
哨兵节点之间是通过 Redis 的发布者/订阅者机制来相互发现的。所有的哨兵节点都订阅master节点上的__sentinel__:hello的频道,哨兵之间可以相互感知连接,然后组成集群。
-
哨兵会每 10 秒一次的频率向主节点发送 INFO 命令来获取所有「从节点」的信息,根据信息即可与slave建立连接进行监控
-
-
-
4、Redis集群
-
什么是集群脑裂?为什么会导致数据丢失??
-
主节点由于网络问题与所有的从节点都失联了,但此时的主节点和客户端的⽹络是正常的,持续将写操作更新到缓冲区,哨兵误判master失联重新选举master,导致出现了两个主节点。
-
等网络恢复,旧主节点会降级为从节点,再与新主节点进行同步复制的时候,由于会从节点会清空自己的缓冲区(第一次同步为全量同步),所以导致之前客户端写入的数据丢失了。
-
-
如何解决集群脑裂带来的数据丢失??
-
当主节点发现从节点下线或者通信超时的总数量⼩于阈值时,那么禁⽌主节点进⾏写数据,直接把错误返回给客户端。
-
设置主节点连接的从节点中⾄少有 N 个从节点,并且主节点进⾏数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主节点就不会再接收客户端的写请求了。
-
等到新主节点上线时,就只有新主节点能接收和处理客户端请求,
-
此时,新写的数据会被直接写到新主节点中。⽽原主节点会被哨兵降为从节点,即使它的数据被清空了,也不会有新数据丢失。
-
-
Redis切片集群模式是如何存储数据的??
-
Redis Cluster 方案采用哈希槽(Hash Slot),来处理数据和节点之间的映射关系。Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,这些哈希槽类似于数据分区,每个键值对数据都会根据它的 key,被映射到一个哈希槽中。
数据key不是与节点绑定,而是与slot绑定; 每个master节点可以映射多个插槽,如7001节点可以映射到0~3000的插槽,并可以通过命令实现插槽的转移
-
根据键值对的 key,按照 CRC16 算法 (opens new window)计算一个 16 bit 的hash值。
-
然后对16384取余,得到的结果就是slot值。——对应master节点
-
-
Redis的分布式集群是怎么设计的?
-
Redis Cluster采用HashSlot来实现Key值的均匀分布和实例的增删管理。
-
首先默认分配了16384个Slot(这个大小正好可以使用2kb的空间保存),每个Slot相当于一致性Hash环上的一个节点。接入集群的所有实例将均匀地占有这些Slot,而最终当我们Set一个Key时,使用
CRC16(Key) % 16384
来计算出这个Key属于哪个Slot,并最终映射到对应的实例上去。 -
去中心化访问:(无论访问集群中的哪个节点,你都能够拿到想要的数据)
-
每个节点都保存有完整的HashSlot - 节点映射表,也就是说,每个节点都知道自己拥有哪些Slot,以及某个确定的Slot究竟对应着哪个节点。
-
无论向哪个节点发出寻找Key的请求,该节点都会通过CRC(Key) % 16384计算该Key究竟存在于哪个Slot,并将请求转发至该Slot所在的节点。
-
总结一下就是两个要点:映射表和内部转发,这是通过著名的Gossip协议来实现的。
-
-
Redis集群相对于主从模式有什么优势??集群解决什么问题??
-
主从模式是Redis的基础架构,它的优点是简单易用,适合读多写少的场景。同时缺点也明显,比如可能出现单点故障、扩展性有限等。Redis集群就是为了解决主从机制的缺陷而设计的,通过分片&&多主架构提供了更好的扩展性和高可用性。
-
高可用性:主节点故障,集群可自动实现故障转移。且有其他主节点作保障
-
扩展性:集群模式支持水平扩展,可以通过增加新的主节点来分担读写负载,提高系统的整体性能和吞吐量。
-
性能:集群中数据分布在多个主节点上,读写操作可以在多个节点上并行执行,提高了系统的并发处理能力。
-
数据持久化:数据在多个主节点和从节点之间复制,提高了数据的冗余度和持久性。主从模式数据持久性依赖于主节点和从节点的数据同步。如果主节点发生故障,可能会导致数据丢失。
-
-
Redis集群怎么实现数据一致性??
-
数据一致性的两个角度:持久化文件与内存数据一致性、不同节点之间的数据一致性
-
前者需要通过基于AOF&RDB的全量同步与增量同步持久化机制来保障
-
后者从主从复制哨兵机制、故障转移、分布式事务的角度来实现数据 一致性。
-
主从复制:
-
Redis 集群中的每个主节点都有一个或多个从节点。主节点负责处理写操作,从节点负责处理读操作。
-
主节点的数据更新会通过异步复制的方式同步到从节点,确保数据的一致性。
-
-
故障转移:
-
当主节点发生故障时,Redis 集群会自动进行故障转移。从节点中的一个会被提升为主节点,继续处理写操作,确保服务的连续性和数据的一致性。
-
-
分布式事务:
-
Redis 集群支持多键事务,通过 MULTI 和 EXEC 命令确保一组命令的原子性执行。在事务执行期间,其他客户端的请求会被阻塞,直到事务完成。
-
-
-
-