目录
Redis单线程是指接收客户端---解析请求---进行数据读写请求操作---发送数据到客户顿这个过程是由一个线程(主线程)来完成的。
但Redis程序并不是单线程的,Redis在启动的时候,是会启动后台程序(BIO)的:
1.Redis2.6版本,会启动2个线程,分别处理文件关闭、aof刷盘这两个任务
2.Redis4.0版本后,新增了一个新的后台程序,实现数据的异步惰性删除,解决删除数据效率比较低的问题
3.Redis6版本线程模型,网络 I/O 和命令处理都是单线程
4.Redis7,采用多个线程来处理网络请求,提高网络请求处理的并行度,对于读写操作命令依然使用单线程处理。
1.Redis大部分操作都在内存中完成,并且采用了高效的数据结构,因此Redis瓶颈并非CPU,而是机器内存或网络单宽。
2.Redis采用单线程避免多线程间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。
2.Redis采用了I/O多路复用处理大量客户端socket请求,I/O多路复用是指一个或一组线程处理多个tcp连接,使用单线程就能实现同时处理多个客户端的连接,无需创建或维护过多线程/进程。
将用户socket对应的文件描述符(FileDescriptor)注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor反应模式。
如上图对Redis的I/O多路复用模型进行一下描述说明:
(1)一个socket客户端与服务端连接时,会生成对应一个套接字描述符(套接字描述符是文件描述符的一种),每一个socket网络连接其实都对应一个文件描述符。
(2)多个客户端与服务端连接时,Redis使用 I/O多路复用程序 将客户端socket对应的FD注册到监听列表(一个队列)中,并同时监控多个文件描述符(fd)的读写情况。当客服端执行accept、read、write、close等操作命令时,I/O多路复用程序会将命令封装成一个事件,并绑定到对应的FD上。
(3)当socket有文件事件产生时,I/O 多路复用模块就会将那些产生了事件的套接字fd传送给文件事件分派器。
(4)文件事件分派器接收到I/O多路复用程序传来的套接字fd后,并根据套接字产生的事件类型,将套接字派发给相应的事件处理器来进行处理相关命令操作。
Redis的读写操作都是在内存中的,所以Redis性能才会高,但是当Redis重启后,内存中的数据就会丢失,为了保证内存中的数据不会丢失,Redis实现了数据持久化机制,这个机制会把数据存储到磁盘中,这样Redis重启就能够从磁盘中恢复原有的数据。
1.AOF日志:每执行一条操作命令,就把该命令以追加写的方式写入到一个文件里。
2.RDB快照:将某一时刻的内存数据以二进制的方式写入到磁盘。
3.混合持久方式:Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点。
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
默认情况下,redis是没有开启AOF(append only file)的。开启AOF功能需要设置配置:appendonly yes。
Redis 是先执行写操作命令后,才将该命令记录到 AOF 日志里的。这么做其实有两个好处。
当然,这样做也会带来风险:
Redis 写入 AOF 日志的过程:
Redis 提供了 3 种写回硬盘的策略,控制的就是上面说的第三步的过程。 在 Redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:
由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的峰值时,Redis就会自动启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集或者可以手动使用命令 bgrewriteaof 来重写。
AOF 重写机制是在重写时,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。
1:在重写开始前,redis会创建一个“重写子进程”,这个子进程会读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
2:与此同时,主进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
3:当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中
4:当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中
5:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
实现类似照片记录效果的方式,就是把某一时刻的数据和状态以文件的形式写到磁盘上,也就是
快照。这样一来即使故障宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。
这个快照文件就称为RDB文件(dump.rdb)
Redis 提供了两个命令来生成 RDB 文件,分别是 save
和 bgsave
,他们的区别就在于是否在「主线程」里执行:
RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
Redis 还可以通过配置文件的选项来实现每隔一段时间自动执行一次 bgsave 命令,默认会提供以下配置:
- save 900 1
- save 300 10
- save 60 10000
别看选项名叫 save,实际上执行的是 bgsave 命令,也就是会创建子进程来生成 RDB 快照文件。
只要满足上面条件的任意一个,就会执行 bgsave,它们的意思分别是:
这里提一点,Redis 的快照是全量快照,也就是说每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。
可以的,执行 bgsave 过程中,Redis 依然可以继续处理操作命令的,也就是数据是能被修改的,关键的技术就在于写时复制技术(Copy-On-Write, COW)
RDB 优点是数据恢复速度快,但是快照的频率不好把握。频率太低,丢失的数据就会比较多,频率太高,就会影响性能。
AOF 优点是丢失数据少,但是数据恢复不快。
为了集成了两者的优点, Redis 4.0 提出了混合使用 AOF 日志和内存快照,也叫混合持久化,既保证了 Redis 重启速度,又降低数据丢失风险。
先使用RDB进行快照存储,然后使用AOF持久化记录所有的写操作,当重写策略满足或手动触发重写的时候,将最新的数据存储为新的RDB记录。这样的话,重启服务的时候会从RDB和AOF两部分恢复数据,既保证了数据完整性,又提高了恢复数据的性能。简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式。----》AOF包括了RDB头部+AOF混写
在使用 Always 策略的时候,主线程在执行完命令后,会把数据写入到 AOF 日志文件,然后会调用 fsync() 函数,将内核缓冲区的数据直接写入到硬盘,等到硬盘写操作完成后,该函数才会返回。
当使用 Always 策略的时候,如果写入是一个大 Key,主线程在执行 fsync() 函数的时候,阻塞的时间会比较久,因为当写入的数据量很大的时候,数据同步到硬盘这个过程是很耗时的。
当使用 Everysec 策略的时候,由于是异步执行 fsync() 函数,所以大 Key 持久化的过程(数据同步磁盘)不会影响主线程。
当使用 No 策略的时候,由于永不执行 fsync() 函数,所以大 Key 持久化的过程不会影响主线程。
AOF 重写机制和 RDB 快照(bgsave 命令)的过程,都会分别通过 fork()
函数创建一个子进程来处理任务。会有两个阶段会导致阻塞父进程(主线程):
Redis事务允许依次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序的串行化执行而不会被其他命令插入,不允许加塞。
事务执行过程是这样的:
MULTI
);EXEC
)。当事务中有任何一个事务语法出现错误,Redis会直接返回错误,所有命令都不会执行
当事务前期语法正确,编译通过,但执行exec后报错:事务中对的命令执行,错的命令停止执行
Redis 是可以对 key 设置过期时间的,因此需要有相应的机制将已过期的键值对删除,而做这个工作的就是过期键值删除策略。
每当我们对一个 key 设置了过期时间时,Redis 会把该 key 带上过期时间存储到一个过期字典(expires dict)中,也就是说「过期字典」保存了数据库中所有 key 的过期时间。
当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:
定时删除策略的做法是,在设置 key 的过期时间时,同时创建一个定时事件,当时间到达时,由事件处理器自动执行 key 的删除操作。这样对内存友好,但在过期key较多的情况下回占用相当一部分CPU,对CPU不友好。
定期删除策略的做法是,每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
惰性删除策略的做法是,不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
Redis选择【惰性删除+定期删除】两种策略配合使用,以求在合理使用CPU时间和避免内存浪费之间取得平衡。
AOF 文件分为两个阶段,AOF 文件写入阶段和 AOF 重写阶段。
1.写入阶段:当Redis以AOF模式持久化是,如果数据库中某个过期键还没被删除,那么AOF文件会保留此过期键,当此过期键被删除后,Redis会像AOF中AOF文件追加一条del命令来显式的删除该键值。
2.重写阶段:执行 AOF 重写时,会对 Redis 中的键值对进行检查,已过期的键不会被保存到重写后的 AOF 文件中,因此不会对 AOF 重写造成任何影响。
RDB 文件分为两个阶段,RDB 文件生成阶段和加载阶段。
1.生成阶段:从内存中持久化生成RDB文件时,会对key进行过期检查,过期的key不会保存到新的RDB文件中。因此Redis中的过期键不会对新的RDB文件产生影响。
2.加载阶段:RDB 加载阶段时,要看服务器是主服务器还是从服务器,分别对应以下两种情况:
在 Redis 的运行内存达到了某个阀值,就会触发内存淘汰机制,这个阀值就是我们设置的最大运行内存,此值在 Redis 的配置文件中可以找到,配置项为 maxmemory。
Redis 内存淘汰策略共有八种,这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。
针对「进行数据淘汰」这一类策略,又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。
server.db[i].expires
)中任意选择数据淘汰。server.db[i].expires
)中挑选将要过期的数据淘汰LRU 全称是 Least Recently Used 翻译为最近最少使用,会选择淘汰最近最少使用的数据。
LFU 全称是 Least Frequently Used 翻译为最近最不常用,LFU 算法是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
所以, LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时,就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题,相比于 LRU 算法也更合理一些。
文章参考:
1.Redis之I/O多路复用模型实现原理_redis io多路复用_有盐先生的博客-CSDN博客
2.小林coding:图解Redis介绍 | 小林coding (xiaolincoding.com)
3. 尚硅谷Redis7实战:尚硅谷Redis零基础到进阶,最强redis7教程,阳哥亲自带练(附redis面试题)_哔哩哔哩_bilibili
4.javaguide:Redis常见面试题总结(下) | JavaGuide(Java面试 + 学习指南)