Redis技能熟练掌握之十年内功
- 1.redis是什么?为什么要使用redis?
- 2.redis一般应用于什么场景(四个场景)?
- 3. Redis持久化机制是什么?各自的优缺点?一般咋么用?
- 4. redis五个基础类型支持的操作?
- 5. Redis 的对象机制(redisObject)?
- 6. Redis数据类型有哪些底层数据结构?
- 7. Redis RDB触发方式有什么
- 8. Redis边读边写如何保存内存数据一致性的?
- 9.RDB快照操作的时间内,如果发生服务崩溃咋么办?
- 10. AOF是写前日志还是写后日志?
- 11.如何实现AOF的?
- 12.什么是AOF重写?
- 13.补充:aof重写日志目的?
- 14.主线程fork出子线程是如何复制内存数据的?
- 15.重写日志整个过程时,主线程会被阻塞?
- 16. 为什么aof重写不复用aof日志?
- 17. Redis过期键的删除策略有?
- 18.Redis内存淘汰算法有哪些?
- 19. redis的管道有什么用?
- 20.Redis如何做内存优化?
- 21. 什么是Redis事务?
- 22. 说说redis事务相关命令?
- 23. 谈谈redis不支持回滚?
- 24.Redis 对 ACID的支持性理解?
- 25. Redis事务其他实现?
- 26. Redis集群的主从复制模型是咋样的?
- 27. Redis事务中watch是如何监视实现的呢?
- 28.Redis全量复制的三个阶段?
- 29. Redis增量复制的流程?
- 30.增量复制如果在断网期间,repl_backlog_size环形缓存区写满之后,从库是会丢失掉那部分覆盖掉的数据还是直接进行全量复制呢?
- 31.Redis为什么不持久化的主服务器自动重启非常危险呢?
- 32. Redis为什么主从全量复制使用RDB而不使用AOF?
- 33. Redis为什么还有无磁盘复制模式?
1.redis是什么?为什么要使用redis?
redis是一种支持key-value等多种数据结构的存储系统,可用于缓存,事件发布与订阅,高速队列等场景,支持网络,提供字符串,列表,哈希,集合结构,直接存取,基于内存,可持久化。
- 读写性能优异
- Redis能读的速度是11w次/s,写的速度是8.1w次/s
- 数据类型丰富
- Redis支持二进制案例的Strings,lists,hashes,Sets及OrderedSets数据类型操作。
- 原子性
- Redis的所有操作都是原子性的,同时redis还支持对几个操作合并后的原子性操作。
- 丰富的特性
- redis支持publish/subscribe,通知,key过期等特性。
- 持久性
- redis支持AOF,RDB等持久化方式。
- 发布订阅
- 分布式
- Redis cluster。
2.redis一般应用于什么场景(四个场景)?
- 热点数据的缓存
1.1 缓存是Redis最常见的应用场景,之所有这么使用,主要是因为Redis读写性能优异。而且逐渐有取代memcached,成为首选服务端缓存的组件。而且,Redis内部是支持事务的,在使用时候能有效保证数据的一致性。
- 限时业务的应用
2.1 redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。
- 计数器的相关问题
redis由于incrby命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成、具体业务还体现在比如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。
- 分布式锁
这个主要利用redis的setnx命令进行,setnx:"set if not exists"就是如果不存在则成功设置缓存同时返回1,否则返回0 ,这个特性在后台中有所运用,因为我们服务器是集群的,定时任务可能在两台机器上都会运行,所以在定时任务中首先 通过setnx设置一个lock,如果成功设置则执行,如果没有成功设置,则表明该定时任务已执行。 当然结合具体业务,我们可以给这个lock加一个过期时间,比如说30分钟执行一次的定时任务,那么这个过期时间设置为小于30分钟的一个时间就可以,这个与定时任务的周期以及定时任务执行消耗时间相关。
3. Redis持久化机制是什么?各自的优缺点?一般咋么用?
- Redis持久化是吧当前进程数据生成快照保存到磁盘上的过程,针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
- AOF 是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的Redis收到的每一条命令,这些命令以文本的形式保存。
- Redis4.0提出了混合使用AOF日志和内存快照的方法,简单来说,内存快照以一定的频率执行,在两次快照之间,使用AOF日志记录这期间的所有命令操作。
这样一来,快照就不用频繁地执行,这就避免了fork对主线程的影响,而且,AOF日志也只用于记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现日志文件过大的情况了,也可以避免重写开销。
如:T1和T2时刻的修改,用AOF日志记录,等到第二次坐全量快照时,就可以清空AOF日志,因为此时的修改都已经记录到快照里了,恢复就不再用日志了。
好处
:这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势, 实际环境中用的很多。
4. redis五个基础类型支持的操作?
- 字符串
1.1 字符串可以是整型、浮点型,数字类型可以自增或自减;
1.2 字符串类型的话,可以对子付出进行操作- 列表list
2.1 一个链表,链表每个节点都是一个字符串
2.2 对链表的两端进行push和pop操作,读取单个或多个元素,根据值查找或删除元素;- 哈希Hash
3.1 包含键值对的无序散列表
3.2 包含方法有添加、删除、获取单个元素- Set集合
4.1 包含字符串的无需集合
4.2 方法:添加、删除、获取、还包含计算交集、并集、差集等- Zset集合
5.1 和散列一样,用于存储键值对
5.2 字符串成员与浮点数之间有序映射;元素的排列顺序由分数大小决定,方法包含了添加、获取、删除、以及根据分数范围或成员获取元素。
5. Redis 的对象机制(redisObject)?
- 提供了统一访问不同数据结构的接口
- 提供了类型泛型,通过不同的类型泛型推断,优化存储结构
/** Redis 对象*/
typedef struct redisObject {// 类型unsigned type:4;// 编码方式unsigned encoding:4;// LRU - 24位, 记录最末一次访问时间(相对于lru_clock); // 或者 LFU(最少使用的数据:8位频率,16位访问时间)unsigned lru:LRU_BITS; // LRU_BITS: 24// 引用计数int refcount;// 指向底层数据结构实例void *ptr;} robj;
6. Redis数据类型有哪些底层数据结构?
Redis 设计的底层数据结构分为:如下
- 简单动态字符串 - sds
- 用于存储字符串值的数据结构
- 特点包含:
-
- 动态扩展,避免字符串缓冲区溢出
-
- 常数级获取字符串长度
-
- 可修改性: 可以对字符串进行修改操作,如追加、删除、替换等
-
- 二进制安全:可以存储任意二进制数据,不仅可以鵆文本字符串,还可以存储图片、视频等二进制数据。
- 空间预分配和惰性空间释放
- Redis字符串类型最多可以容纳512MB数据。
- 压缩列表 - ZipList
- 快表 - QuickList
- 字典/哈希表 - Dict
- 整数集 - IntSet
- 跳表 - ZSkipList
7. Redis RDB触发方式有什么
- 手动触发
1.1 save命令:阻塞当前redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上不建议使用。
1.2 bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短,bgsave流程图如下所示:
- 自动触发
2.1 redis.conf中配置save m n ,即在m秒内有n次修改时,自动触发bgsave生成rdb文件;
2.2 主从复制,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点
2.3 执行debug reload命令重载redis时也会触发bgsave操作;
2.4 默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作;
8. Redis边读边写如何保存内存数据一致性的?
思考
:rdb由于生产环境下我们为redis开辟的内存空间区域都比较大,比如6gb,那么将内存中的数据同步到硬盘的过程可能就会持续比较长的时间,而实际情况是这段时间redis服务一般都会接收到新的写操作请求
。那么如何保证数据一致性
呢?
从两方面说:
- RDB的核心思路是copy-on-write,来保证在进行快照操作的这段时间,需要压缩写入磁盘上的数据在内存不会发生变化。一方面,redis主进程会fork一个新的快照进程专门来做这个事情,这样保证了redis服务不会停止接收客户端包括写请求在内的任何请求;
- 另一方面,这段时间发生变化的数据会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。
9.RDB快照操作的时间内,如果发生服务崩溃咋么办?
在没有将数据完全写入到temp rdb file(或磁盘)前,这次快照操作都不算成功。如果出现了服务崩溃的情况,将上一次完整的rdb快照文件作为恢复内存数据的参考。也就是说,在快照过程中不能影响上一次的备份数据。redis服务会在磁盘上创建一个临时文件进行数据操作,待操作成功后才会用这个临时文件替换掉上一次的历史备份。
10. AOF是写前日志还是写后日志?
AOF日志采用的写后日志,即先写内存,后写日志。
Q:为什么采用写后日志(针对AOF)?
A:
- 避免额外的检查开销,Redis AOF写前日志,肯定需要对命令正确性校验,否则redis使用日志恢复数据时,就可能出错;但写后日志就不用再次检查了。
- 不会阻塞当前的操作
但这种也存在潜在风险:
- 如果命令执行完成了,写日志前宕机了,会丢失数据。
- 主线程写磁盘压力大,导致写盘慢,阻塞后续操作。
11.如何实现AOF的?
AOF日志记录了redis的每个命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)。
- 命令追加:当aof持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的aof_buf缓冲区。
- 文件写入和同步:关于何时将aof_buf缓冲区的内容写入aof文件中,redis提供了三种写会策略。
- always:同步写回,每个命令执行完,立马同步地将日志写回磁盘
- EverySec: 每秒写回,每个命令执行完,只是先把日志写到 AOF文件内存缓存区,每隔一秒把缓存区中的内容写入磁盘。
- No,操作系统控制的写回,每个命令执行完,只是先把日志写到AOF文件的内存缓存区,由操作系统决定何时将缓存区写回磁盘。
三种写回策略的优缺点
:
上面三种写回策略体现了一个重要原则:trade-off
,取舍,指在性能和可靠性保证之间做取舍。
关于AOF的同步策略是涉及到操作系统的write函数和fsync函数的,在《redis设计与实现》中这样说明的:
- 为了提高文件写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入文件时,操作系统通常会将数据暂存到一个内存缓存区里,当缓存区的空间被填满或者到了指定时限后,才真正将缓存区中的数据写到磁盘里。
- 这样的操作虽然提高了效率,但也为数据写入带来了安全问题,如果计算机停机,内存缓存区的数据会丢失。为此,系统提供了fsync,fdatasync同步函数,可以强制操作系统立刻将缓存区中的数据写会到硬盘里,从而确保写入数据的安全性。
12.什么是AOF重写?
Redis通过创建一个
新的AOF文件
替代现有的AOF
,新旧两个AOF文件保存的数据相同
,但新AOF文件没有了冗余命令
。
- aof重写会阻塞吗?
- aof重写过程是后台进程bgrewriteaof来完成的。主线程fork出后台bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据,然后bgrewriteaof子进程就可以不影响主线程的情况下,逐一把内存的数据写成操作,记入重写日志。
- 所以aof在重写时,在fork进程时是会阻塞主线程的。
- aof日志重写时机决定因素?
- auto-aof-rewrite-min-size:aof重写文件的最小大小,默认是64MB。
- auto-aof-rewrite-percentage:(当前aof文件大小 - 上个aof文件大小)/ 上次aof文件大小, 当前aof文件增量比;
- 这两个配置项可以控制何时重写日志
13.补充:aof重写日志目的?
- AOF(Append Only File)重写是 Redis 中一种优化 AOF 持久化机制的操作,主要是为了解决 AOF 文件过大的问题。AOF 是一种持久化方式,它会将每个写操作追加到文件的末尾,记录所有写操作的命令,以便在 Redis 重启时重新执行这些命令来恢复数据。
- 当 AOF 文件变得非常大时,可能会影响 Redis 的性能,因为每次重启 Redis 时都需要重新加载整个 AOF 文件,这可能会导致启动时间较长。为了解决这个问题,Redis 提供了 AOF 重写机制。
- AOF 重写是通过读取当前数据库中的数据来生成一个新的 AOF 文件,这个新文件包含了对数据库当前状态的记录,而不是所有历史操作的记录。通过 AOF 重写,可以有效地减小 AOF 文件的大小,提高 Redis 的性能,同时保持数据的持久性。
总结来说,AOF 重写的目的是为了优化 AOF 持久化机制,减小 AOF 文件的大小,提高 Redis 的性能,并保证数据的持久性。
14.主线程fork出子线程是如何复制内存数据的?
前置了解
:写时复制
- 写时复制(copy-on-write,简称COW)是计算机程序设计中的技术,用于在需要复制数据时延迟实际复制操作。写时复制的基本思想是,当多个进程或线程需要访问同一块内存时,系统会首先共享这块内存,只有在其中一个进程或线程视图修改数据时,才会将数据复制到新的内存块中,使得该进程或线程独享修改后的数据,而其他进程或线程继续共享原始数据。
- 在操作系统中,写时复制通常用于处理进程间共享内存的情况。当一个进程fork(复制)时,操作系统并不立即复制父进程的内存空间,而是让子进程或父进程共享相同的内存页。只有当父进程或子进程中的一个尝试修改共享内存页时,操作系统才会执行实际的复制操作,将数据复制到新的内存页中,以确保父进程和子进程之间的内存不会互相干扰。
- 总结: 写时复制技术可以有效地节省内存空间和提高系统性能,因为它延迟了复制操作的执行,仅在需要修改数据时才进行复制。这样可以避免不必要的数据复制,减少内存占用和提高程序的运行效率。写时复制在操作系统中的应用有助于实现进程间内存共享和提高系统的并发性能。
接着,redis采用了操作系统写时复制(cow)机制,就是为了避免一次性拷贝大量内存数据给子进程造成阻塞;fork子进程时,子进程会拷贝父进程的页表,即虚实映射关系(虚拟内存和物理内存的映射索引表),而不会拷贝物理内存,这个拷贝会消耗大量cpu资源,并且拷贝完成前会阻塞主线程,阻塞时间取决于内存中的数据量,数据量越大,则内存页表越大。拷贝完成后,父子进程使用相同的内存地址空间。
但如果主进程可以有数据写入,这时候就会拷贝物理内存的数据。如下图(进程1看做是主进程,进程2看作是自进程)
在主进程有数据写入时,而这个数据刚好在页c中,操作系统会创建这个页面的副本(页c的副本),即拷贝当前页的物理数据,将其映射到主进程中,而子进程还是使用原来的的页c。
15.重写日志整个过程时,主线程会被阻塞?
- fork子进程时,需要拷贝虚拟页表,会对主线程阻塞;
- 主进程有bigkey写入时,操作系统会创建页面的副本,并拷贝原有的数据,会对主线程阻塞;
- 子进程重写日志完成后,主进程追加aof重写日志缓存区可能会对主线程阻塞;
16. 为什么aof重写不复用aof日志?
两方面原因:
- 父子进程写同一个文件会产生竞争问题,影响父进程的性能;
- 如果AOF重写过程中失败了,相当于污染了原来的aof文件,无法做恢复数据使用。
17. Redis过期键的删除策略有?
单机版redis,存在两种删除策略:
- 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除;
- 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和cpu的折中(删除会释放内存,但是频繁删除操作对 cpu不好),该删除的频率和执行时间都受到了限制。
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除,由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过redis从节点读取数据时,很容易读取到已经过期的数据。
Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断;如果该数据已过期,则不返回给客户端;将redis升级到3.2 可以解决数据过期问题。
18.Redis内存淘汰算法有哪些?
Redis共支持八种淘汰算法策略,分别是:noeviction、volatile-random、volatile-ttl、volatile-lru、volatile-lfu、allkeys-random和allkeys-lfu策略。
主要分三类:
- 不淘汰
- noeviction
- 对设置过期时间的数据进行淘汰
- 随机:volatile-random
- ttl:volatile-ttl
- lru:volatile-lru
- lfu:volatile-lfu
- 全部数据进行淘汰:
- 随机:allkeys-random
- lru:allkeys-lru
- lfu:allkeys-lfu
补充
:LRU和LFU算法
LRU是least recently used,按照最近最少使用的原则来筛选数据。这种模式下会使用LRU算法来筛选设置了过期时间的键值对。
Redis优化的算法实现:
redis会记录每个数据的最近一次被访问的时间戳。在redis决定淘汰数据时,第一次会随机选出N个数据,把它们作为一个候选集合。接下来,Redis会比较N个数据的lru字段,把lru字段值最小的数据从缓存中淘汰出去。通过随机读取待删除集合,可以让Redis不用维护一个巨大的链表,也不用操作链表,进而提升性能。
LFU算法:LFU缓存策略是在lRU策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用LFU策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
19. redis的管道有什么用?
- Redis的管道是通过在客户端和服务器之间建立一个TCP连接的方式来实现的。客户端在发送多个命令时,这些命令会被打包成一个请求,并通过TCP连接发送给Redis服务器。服务器接收到这个请求后,会依次执行每个命令,并将每个命令的响应返回给客户端。客户端在接收到所有命令的响应后,可以一次性获取所有结果。
- 在实现上,Redis会在客户端发送的请求中添加特殊的标记,以便服务器能够识别这是一个管道请求。服务器会在执行每个命令后将结果保存在一个缓冲区中,最后一次性将所有结果发送给客户端。这样就实现了客户端可以在一次请求中发送多个命令,并一次性获取所有结果的功能。Redis的管道机制可以有效减少网络延迟,提高通信效率。
20.Redis如何做内存优化?
- 缩减键值对象:缩减键(key)和值(value)的长度,
key长度:如在设计键时,在完整描述业务的情况下,键值越短越好;
value长度:值对象缩减是比较复杂的,常见需求是将业务对象序列化成二进制数组放入redis。首先应该精简在业务对象,去掉不必要的属性,其次在序列化工具选择上,应该选择更加高效的序列化工具降低字节数组大小。以java为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更加高效的序列化工具,如protostuff,kryo等;
- 共享对象池
对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内存结构至少占16字节,甚至超过了整个自身空间消耗,所以Redis内存维护一个【0-9999】的整数对象池,用于节约内存。除了整型对象,其他类型list,hash,set,zset内部元素也可以使用整型对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节约内存。- 字符串优化
- 编码优化
- 控制key的数量
21. 什么是Redis事务?
redis 事务的本质是一组命令的集合。事务支持一次执行多条命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性地执行一个队列中的一系列命令。
22. 说说redis事务相关命令?
multi、exec、discard和watch是redis事务相关的命令
multi:开启事务,redis会将后续的命令逐个放入队列,然后使用exec命令来原子化执行这个命令系列。
exec:执行事务中的所有操作命令。
discard:取消事务
watch:监视一个或多个key,如果事务在执行前,这个key被其他命令修改,则事务被中断,不会执行事务中的任何命令。
unwatch:取消watch对所有key的监视。
23. 谈谈redis不支持回滚?
以下是这种做法的优点:
- redis命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这就是说,从实际角度看,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
24.Redis 对 ACID的支持性理解?
- 原子性atomicity
首先通过上文知道运行期的错误是不会回滚的,很多文章由此说Redis事务违背原子性的;而官方文档认为是遵从原子性的。
Redis官方文档给的解释是,Redis的事务是原子性的:所有的命令,要么全部执行,要么全部不执行。而不是全部成功。
- 一致性consistency
redis事务可以保证命令失败的情况下得以回滚,数据恢复到没有执行之前的样子,是保证一致性的,除非redis进程意外终结。
- 隔离性Isolation
redis事务是严格遵守隔离性的,原因是redis是单进程单线程模式(v6.0之前),可以保证命令执行过程中不会被其他客户端命令打断。但是,Redis不像其他结构化数据库有隔离级别这种设计。
- 持久性Durability
redis事务是不保证持久性的,这是因为redis持久性策略中不管是rdb还是AOF,都是异步执行的,不保证持久性是出于对性能的考虑。
25. Redis事务其他实现?
- 基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序执行,其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完。
- 基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐。
- 虽然Lua脚本本身并不能提供像关系型数据库中事务那样的ACID特性,但通过将多个命令打包在一个Lua脚本中执行,可以保证这些命令的原子性。这意味着,Lua脚本中的所有命令要么全部执行成功,要么全部执行失败,避免了中间状态的出现。
26. Redis集群的主从复制模型是咋样的?
- 主从复制,是将一台redis服务器的数据,复制到其他redis服务器。前者称为主节点,后者称为从节点,数据的复制是单向的,只能由主节点到从节点。
主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
主从库之间采用的是读写分离的模式
读操作:主库、从库都可以接收;
写操作:首先到主库执行,然后,主库将写操作同步给从库。
注意:在2.8 版本之前只有全量复制,在2.8版本之后有全量和增量复制:
- 全量(同步)复制:比如第一次同步时
- 增量(同步)复制:只会把主从库网络断连期间主库收到的命令,同步给从库
27. Redis事务中watch是如何监视实现的呢?
Redis使用WATCH命令来决定事务是继续执行还是回滚,那就需要在multi之前使用WATCH来监视某些键值对,然后使用multi命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。
当时用EXEC执行事务时,首先会比对WATCH所监控的键值对,如果没有发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,Redis都会取消执行事务前的WATCH命令。
28.Redis全量复制的三个阶段?
第一阶段是主从间建立连接、协商同步的过程:
主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步啦。第二阶段是主库将所有数据同步给从库:
从库收到数据后,在本地完成数据加载,这个过程依赖于内存快照生成的RDB文件。第三阶段主库会把第二阶段执行过程中新收到的写命令,再发送给从库:
具体操作就是,当主库 完成RDB文件发送后,就会把此时replication buffer中的修改操作发送给从库,从库再重新执行这些操作。这样一来,主从库就实现同步啦。
- 彩蛋:为什么redis官方设计增量复制?
- 如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大的。从redis2.8开始,网络断了之后,主从库会采用增量复制的方式继续同步。
29. Redis增量复制的流程?
先看两个概念:replication_buffer
和repl_backlog_buffer
repl_backlog_buffer
:它是为了从库断开之后,如何找到主从差异数据而设计的环形缓存区,从而避免全量复制带来的性能开销。如果从库断开时间太久,repl_backlog_buffer环形缓存区被主库的写命令覆盖了,那么从库连接上主库后只能乖乖地进行一次全量复制,所以repl_backlog_buffer配置尽量大一些,可以降低主从断开后全量复制的概率
。而在repl_backlog_buffer中找主从差异的数据后,如何发送给从库呢?这就用到了replication buffer。
replication buffer
:Redis和客户端通信也好,和从库通信也好,Redis都需要给分配一个内存buffer进行数据交互,客户端是一个client,从库也是一个client,我们每个client连上Redis后,Redis都会分配一个client buffer,所有数据交互都是通过这个buffer进行的:Redis先把数据写到这个buffer中,然后再把buffer中的数据发到client socket中再通过网络发送出去,这就完成了数据交互。所以主从在增量同步时,从库作为一个client,也会分配buffer,只不过这个buffer专门用来传播用户的写命令到从库,保证数据一致性,我们通常把它叫做replication buffer。
30.增量复制如果在断网期间,repl_backlog_size环形缓存区写满之后,从库是会丢失掉那部分覆盖掉的数据还是直接进行全量复制呢?
对于这个问题来说,有两个关键点:
- 一个从库如果和主库断连时间过长,造成它在主库repl_backlog_buffer的slave_repl_offset位置上的数据已经覆盖掉了,此时从库和主库将进行全量复制。
- 每个从库会记录自己的slave_repl_offset,每个从库的复制进度也不一定相同。在和主库重连进行恢复时,从库会通过psync命令把自己记录的slave_repl_offset发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。
31.Redis为什么不持久化的主服务器自动重启非常危险呢?
- 我们设置节点A为主服务器,关闭持久化,节点B和C从节点A复制数据。
- 这是出现一个崩溃,但Redis具有自动重启系统,重启了进程,因为关闭了持久化,节点重启后只有一个空的数据集。
- 节点B和C 从节点A进行复制,现在节点A是空的,所以节点B和C上的复制数据会被删除。
- 当在高可用系统中使用Redis Sentiel,关闭了主服务器的持久化,并且允许自动重启,这种情况是很危险的。比如服务器可能在很短时间内就完成了重启,以至于sentinel都无法检测到这次失败,那么上面的这种失败情况就发生了。
如果数据比较重要,并且在使用主从复制时关闭了主服务器持久化功能的场景中,都应该禁止实例自动重启。
32. Redis为什么主从全量复制使用RDB而不使用AOF?
- RDB文件内容是经过压缩的二进制数据(不同数据类型数据做了针对性优化),文件很小。而AOF文件记录的是每一次写操作的命令,写操作越多文件就会变得很大,其中还包括很多对同一个key的多次冗余操作。在主从全量数据同步时,传输RDB文件可以尽量降低对主库机器网络带宽的消耗,从库在加载RDB文件时,一是文件小,读取整个文件的速度很快,二是因为RDB文件存储的都是二进制数据,从库直接按照RDB协议解析还原数据即可,速度会非常快,而AOF需要依次重放每个命令,这个过程会经历冗长的处理逻辑,恢复速度相比RDB会慢很多,所以使用RDB进行主从复制的成本低。
- 假设要使用AOF做全量复制,意味着必须打开AOF功能,打开AOF就要选择文件刷盘的策略,选择不当会严重影响Redis性能。而RDB只有在需要定时备份和主从全量复制数据时才会触发一次快照。而在很多丢失数据不敏感的业务场景,其实是不需要开启AOF的。
33. Redis为什么还有无磁盘复制模式?
Redis模式是磁盘复制,但是如果使用比较低速的磁盘,这种操作会给主服务器带来较大的压力。Redis从2.8.18版本开始支持无磁盘复制。使用这种设置时,子进程直接将RDB通过网络发送给从服务器,不使用磁盘作为中间存储。
无从盘复制模式:master创建一个新进程直接dump RDB到slave的socket,不经过主进程,不经过硬盘。适用于disk较慢,并且网络比较快的时候。
使用
repl_diskless_sync
配置参数来启动无磁盘复制。
使用repl_diskless_sync_delay
参数来配置传输开始的延迟时间;master等待一个repl_diskless_sync_delay
的秒数,如果没slave来的话,就直接传,后来的得排队了;否则就可以一起传;