一、Redis线程模型演化
1.Redis4.0之前
Redis在处理客户端请求时,通常使用单线程来进行读取、解析、执行和响应返回,因此被称为单线程应用。在4.0版本之前,这一描述是准确的。
- 单线程: 读取、解析、执行和响应返回。
2.Redis4.0之后
从4.0版本开始,Redis开始使用后台线程来处理一些耗时的操作,如清理脏数据、释放超时连接、删除大key等,但网络读写和执行命令仍然是单线程处理的。
- 单线程: 网络读写和执行命令
- 多线程: 清理脏数据、释放超时连接、删除大key等耗时操作。
单线程、多线程对比
Redis 之所以使用单线程是因为 Redis 执行的是纯内存的操作,Redis 服务的瓶颈不在 CPU,而是在网络和内存。单线程避免了不必要的上下文切换和竞争条件,也无需考虑锁的问题,整个线程模型比较简单。
多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。Redis 通过事件模型以及 IO 多路复用等技术,处理性能非常高,因此没有必要使用多线程来执行命令。
3.redis 6.0之后
Redis 6.0使用多条IO线程从IO队列中读取请求并进行解析,然后将解析后的命令交给主线程执行。虽然Redis 6.0支持多线程,但执行命令的线程仍然只有主线程一个,因此仍然被认为是单线程执行的。但Redis 6.0通过使用多线程IO突破了单线程IO的瓶颈,发挥了多核的优势。
- 单线程: 执行命令。
- 多线程: 网络IO读写。
- 多线程: 清理脏数据、释放超时连接、删除大key等耗时操作。
当 Redis Server 收到一个请求的时候,先会进入 IO 队列,多条 IO 线程会从 IO 队列里面读取请求并进行解析,解析之后的命令就会交给 Redis 主线程进行执行;执行后的结果会重新进入 IO 队列,等待 IO 线程将响应返回给客户端。
二、Redis的网络IO模型
上面已经说了,Redis是基于单个主线程 + IO 多线程的线程模型,
1.基于事件驱动的Reactor模型
关于Reactor模型的基本介绍之前写过了:博客
Reactor模型有三种——单Reactor单线程、单Reactor多线程、多Reactor多线程,Redis是基于第二种单Reactor多线程,是基于事件驱动的、多路复用的、异步非阻塞I/O的线程模型。
2.什么是事件驱动,事件驱动的Reactor模型和Java中的AIO有什么区别
- 事件驱动:
事件驱动的I/O模型(Event-driven I/O Model)是一种高效的I/O处理模型,主要用于处理多个I/O事件。在事件驱动的I/O模型中,程序会监听多个I/O事件,并在事件发生时触发相应的处理程序进行处理,而不是等待I/O操作完成。 这种模型避免了阻塞式I/O操作可能带来的性能问题,同时也提高了系统的并发性能。
- AIO:
而AIO虽然也是异步非阻塞的(基于发布订阅模式),但和事件驱动还是不一样的:博客
总的来说,AIO Server需要有一个线程来处理读写事件,在等待OS内核处理读写事件并异步通知此线程的时候可以继续做其他的事情。和Reactor相比其实现的机制还是很不一样的。
3.异步非阻塞底层实现原理
select、poll、epoll操作系统底层原理:博客
三、Redis过期策略
1.常见的过期策略
在缓存系统中,常见的过期策略有以下几种:
-
定时过期策略(TTL):为每个缓存条目设置一个过期时间(Time To Live),当缓存超过这个时间时,缓存条目就会被自动删除。这种策略适用于缓存对象的过期时间比较固定的场景。
-
惰性过期策略(LRU):当缓存空间不足时,删除最近最少使用(Least Recently Used)的缓存条目。这种策略适用于缓存对象的访问模式是热点访问的场景。
-
定期过期策略:周期性地清理缓存中的过期条目,以保证缓存空间的有效利用。这种策略适用于缓存对象的过期时间比较长的场景。
-
基于容量的过期策略(LFU):根据缓存条目的使用频率(Least Frequently Used)来删除缓存条目,以保证缓存空间的有效利用。这种策略适用于缓存空间有限的场景。
-
基于热度的过期策略(Hot Key):根据缓存条目的热度(Hot Key)来删除缓存条目,以保证缓存空间的有效利用。这种策略适用于缓存对象的访问模式是热点访问的场景。
在 Redis 中并没有采用定时过期策略,而是选择了惰性过期策略(第二种)和定期过期策略(第三种)的组合。
2.Redis的惰性删除
Redis不会主动删除过期的key,而是在客户端访问这个key时,Redis会检查这个key是否过期,如果过期了就会删除这个key。
惰性删除的实现原理是基于Redis的请求响应模型。当客户端请求一个key时,Redis会首先检查这个key是否过期,如果过期了就将其删除,并返回一个不存在的响应。如果这个key没有过期,就返回对应的value。
需要注意的是,惰性删除不是实时的,过期key可能会长时间存在于内存中,直到有客户端请求时才被删除。这可能会导致内存浪费,需要根据实际情况选择合适的过期策略。
3.Redis的定期过期删除
Redis的过期字典是一种特殊的字典结构,它用于存储带有过期时间的key和对应的value。过期字典的实现基于哈希表和跳跃表,可以以时间复杂度O(1)的速度进行新增、删除和查找操作。
具体来说,当我们向Redis存储一个带有过期时间的key时,Redis会将其添加到过期字典中。过期字典的键是带有过期时间的key,值是一个指向存储这个key的数据结构的指针。过期字典中的每个键都对应一个过期时间,用来记录这个key何时过期。
Redis在每个 事件循环 中都会检查 一部分 过期字典中的key,并将过期的key删除。为了避免一次检查过多的key,Redis会将所有的过期key分散到不同的时间点上,并使用定时器来监控这些时间点。当Redis在某个时间点上检查到一个过期key时,就会将其删除。
过期字典的实现基于哈希表和跳跃表。哈希表用于快速查找key,而跳跃表则用于按照过期时间排序key。当Redis需要检查过期key时,它会遍历跳跃表,找到已经过期的key,并将其从哈希表和跳跃表中删除。
(1)每次检查一部分,这个一部分是多少
Redis的过期字典每次检查的过期key数量是由服务器配置的一个参数决定的,这个参数叫做hz。hz是Redis服务器每秒运行的事件循环数量,它决定了每秒钟服务器会检查多少个过期key。默认的hz值是10,也就是每秒钟会检查10个过期key。
(2)事件循环是指什么
Redis的事件循环是一个基于事件驱动的异步非阻塞的网络通信模型,它通过监听和处理事件来实现网络通信和其他任务的处理。在Redis中,事件是指网络通信中发生的各种事件,例如客户端连接、读写事件、定时器事件等。
为了处理这些事件,Redis使用了一个事件处理器,它会监听事件,并根据事件的类型和状态来执行相应的操作。事件处理器包含多个事件状态,每个状态都对应一类事件。例如,REDIS_READ_EVENT状态表示读事件,REDIS_WRITE_EVENT状态表示写事件,REDIS_TIME_EVENT状态表示定时器事件等。
当一个事件到达时,Redis会将其添加到事件队列中,并在下一个事件循环中处理它。在事件循环中,Redis会遍历事件队列,并根据事件的类型和状态来执行相应的操作。例如,对于一个客户端连接事件,Redis会建立一个新的客户端连接,并将其添加到客户端列表中;对于一个读事件,Redis会从客户端连接中读取数据并进行处理;对于一个定时器事件,Redis会执行相应的定时任务等等。
需要注意的是,Redis的事件循环是单线程的,因此在任何时候只会有一个事件循环正在执行。这意味着在事件循环中处理事件时,应尽量避免执行耗时较长的操作,以免影响其他事件的处理效率。
- 事件队列
Redis的一个事件循环会处理多个事件,具体处理的事件数量取决于事件队列中的事件数量以及服务器配置的参数。在事件循环中,Redis会遍历事件队列,并根据事件的类型和状态来执行相应的操作。
事件队列是Redis存储事件的数据结构,它是一个先进先出(FIFO)的队列。当一个事件到达时,Redis会将其添加到事件队列中,并在下一个事件循环中处理它。如果事件队列中有多个事件,Redis会按照它们的顺序依次处理。
- 划分依据
Redis的事件循环是以时间为依据划分的。具体来说,Redis会定时执行事件循环,每个事件循环的时间长度由服务器配置的参数决定。在每个事件循环中,Redis会处理所有已经到达的网络请求和定时任务,并等待下一个事件循环的到来。
Redis的事件循环的时间长度由服务器配置的参数hz决定。hz表示Redis服务器每秒运行的周期性任务数量,它决定了每秒钟服务器会执行多少个事件循环。默认的hz值是10,也就是每秒钟执行10个事件循环。
(3)惰性删除
采用懒惰删除机制可以简化Redis的实现。如果Redis需要立即删除过期key,那么它需要在每个事件循环中执行删除操作,这会增加代码的复杂度和维护成本。采用懒惰删除机制可以将删除操作集中在一起,简化代码实现和维护。
采用懒惰删除机制可以简化Redis的实现。如果Redis需要立即删除过期key,那么它需要在每个事件循环中执行删除操作,这会增加代码的复杂度和维护成本。采用懒惰删除机制可以将删除操作集中在一起,简化代码实现和维护。
4.淘汰机制(缓存)
Redis的缓存内存淘汰机制是指在内存不足时,Redis会根据一定的规则和算法自动删除一些缓存数据,以释放一部分内存空间,以保证Redis服务的正常运行。Redis的缓存内存淘汰机制主要有以下几种(用户可选择):
-
LRU算法:LRU全称是Least Recently Used,即最近最少使用。LRU算法的原理是根据数据的访问时间来淘汰缓存数据,即淘汰最近最少被访问的数据。当内存不足时,Redis会自动删除最近最少使用的缓存数据,以释放一部分内存空间。
-
LFU算法:LFU全称是Least Frequently Used,即最不经常使用。LFU算法的原理是根据数据的访问频率来淘汰缓存数据,即淘汰访问频率最低的数据。当内存不足时,Redis会自动删除访问频率最低的缓存数据,以释放一部分内存空间。
-
TTL算法:TTL全称是Time To Live,即生存时间。TTL算法的原理是根据缓存数据的过期时间来淘汰数据。当缓存数据的过期时间到期时,Redis会自动删除这些数据,以释放一部分内存空间。
-
Random算法:Random算法的原理是随机选择一些缓存数据进行删除,以释放一部分内存空间。当内存不足时,Redis会随机选择一些缓存数据进行删除,以释放一部分内存空间。
需要注意的是,Redis的缓存内存淘汰机制并不是完全准确的,它只是在一定程度上保证了Redis服务的可用性和稳定性。在实际使用Redis时,应根据实际情况选择合适的内存淘汰机制,以保证缓存数据的有效性和完整性。
四、持久化
Redis 分别提供了 RDB 和 AOF 两种持久化方式。
1.RDB
RDB(Redis Database)是Redis的一种持久化方式,用于将内存中的数据保存到硬盘上,以便在Redis服务器重启后可以快速恢复数据。RDB持久化通过生成快照(snapshot)来保存数据,将Redis服务器的当前状态以二进制格式写入到磁盘上。RDB是Redis的默认持久化方式,也是较早引入的持久化方法之一。
- RDB的特点:
快速:RDB持久化是一种快速的持久化方式,因为它是通过生成快照将内存数据保存到磁盘上,而不是记录所有写入操作。
紧凑:RDB文件是二进制格式,非常紧凑,占用较小的磁盘空间,适用于数据备份和迁移。
容易恢复:RDB文件保存了Redis服务器在某个时间点的状态,因此在服务器重启时可以使用该文件来快速恢复数据。
配置灵活:可以通过Redis的配置文件来配置RDB的触发条件,如SAVE和BGSAVE命令的频率和阈值。
- RDB的优点:
性能好:RDB持久化是一种快速的持久化方式,因为它是生成快照而不是记录所有写入操作,所以对于大型数据库而言,其性能表现较好。
数据安全:RDB文件是一个紧凑的二进制文件,可以方便地进行备份和迁移,保障数据的安全性。
适用于大数据集:对于大数据集,BGSAVE命令可以在后台进行持久化操作,不会阻塞主进程,确保Redis服务器的响应性。
- RDB的缺点:
定期保存:RDB是定期生成快照,如果Redis服务器在快照生成之后发生故障,可能会丢失最后一次快照后的数据。
内存占用:在生成快照期间,Redis需要将整个数据集写入临时文件,可能会占用大量的内存。
不适用于实时备份:由于RDB是定期保存的,如果需要实时备份数据,RDB并不是最合适的选择。
(1)手动RDB
可以通过向Redis服务器发送SAVE
或BGSAVE
命令来触发手动RDB持久化。
SAVE
命令:SAVE命令将阻塞Redis服务器,直到RDB持久化完成。这意味着在持久化期间,Redis的主线程会被阻塞 ,不能处理其他请求。
BGSAVE
命令:BGSAVE命令会在后台异步执行RDB持久化,Redis可以继续处理其他请求。BGSAVE命令会派生一个子进程 来执行持久化操作,这样不会阻塞主进程。
(2)自动RDB
自动RDB是指根据预定义的触发条件,Redis自动执行RDB持久化。可以通过在配置文件中设置以下参数来配置自动RDB:
save <seconds> <changes>
:表示在指定的时间间隔(seconds秒)内,如果至少有指定数量的键发生了变化(changes次),则Redis将自动执行RDB持久化。
stop-writes-on-bgsave-error yes/no
:表示当BGSAVE命令失败时,是否停止接受写入操作。如果设置为yes,那么当BGSAVE命令失败时,Redis将拒绝接受写入操作,以避免数据丢失。
rdbcompression yes/no
:表示是否对RDB文件进行压缩。如果设置为yes,则RDB文件会被压缩,占用更少的磁盘空间。
(3)后台RDB执行过程
子进程执行RDB写入操作: 在后台RDB持久化过程中,Redis会创建一个子进程来执行实际的RDB写入操作。子进程是通过复制主进程的内存空间来创建的,即在一开始,子进程和主进程共享相同的内存空间。子进程会遍历主进程的数据结构,将内存中的数据进行序列化,生成一个RDB快照。
生成RDB文件: 子进程执行完RDB写入操作后,会将生成的RDB快照以RDB文件的形式写入磁盘。RDB文件是一种二进制文件,其中包含了Redis服务器当前时刻的数据快照,包括所有的键值对、过期时间等信息。
写时复制机制: 写时复制是Copy-on-Write技术的核心机制。当子进程需要修改数据时,不会直接在原始数据上进行修改,而是创建一个数据页的副本(Copy)。这个副本在修改前是只读的,即与原始数据共享相同的内存页。
只有被修改的数据页会被写入磁盘: 在执行RDB写入操作期间,子进程可能对内存中的数据进行了修改。然而,由于写时复制机制的存在,只有被修改的数据页会被复制并写入磁盘,而未被修改的数据页仍然是共享的,不需要重新写入磁盘。这样可以大大减少写入磁盘的数据量,提高持久化的效率。
2.AOF
-
写入操作记录: 当客户端发送写入操作(如SET、DEL等)到Redis服务器时,Redis会将这些写入操作以日志的形式记录在AOF文件中,而不是直接修改内存中的数据。
-
文件追加: 新的写入操作会被追加到AOF文件的末尾,而不是在原有的内容上进行修改。这样可以保证AOF文件的内容是一个完整的操作日志,记录了所有写入操作的顺序和内容。
-
数据同步: 根据配置,Redis可以选择不同的数据同步策略:
always:每个写入操作都会立即同步到磁盘,保证了数据的实时性和完整性,但对性能有一定的影响。
everysec:每秒将写入操作同步到磁盘,保证了较高的性能和数据的部分完整性。
no:不主动进行同步,由操作系统决定何时将数据写入磁盘,性能最高但数据可能有一定程度的丢失风险。 -
AOF重写:
AOF重写:为了避免AOF文件过大,影响恢复速度,Redis支持AOF重写操作。AOF重写是通过扫描内存中的数据结构,生成一系列写入操作的新日志,并将其写入一个新的AOF文件。这个过程不会影响客户端的正常读写操作。
AOF重写触发:AOF重写可以由两种方式触发:
自动触发:通过设置auto-aof-rewrite-percentage和auto-aof-rewrite-min-size配置,当AOF文件大小超过设定阈值时,自动触发重写操作。
手动触发:通过向Redis发送BGREWRITEAOF命令,手动触发AOF重写操作。
AOF重写过程:AOF重写过程中,Redis服务器会使用一个子进程来执行重写操作。子进程会遍历内存中的数据结构,生成新的AOF日志文件,并将其写入磁盘。在生成新AOF文件期间,Redis仍然会将新的写入操作追加到原有的AOF文件中。
AOF重写完成:当AOF重写完成后,Redis会将新的AOF文件更名为原有的AOF文件,并继续使用它记录写入操作。旧的AOF文件可以保留为备份,也可以根据需求删除。
AOF重写周期:AOF重写并不会周期性地触发,只有当满足触发条件时才会执行。因此,可能出现AOF文件大小逐渐增大的情况。如果需要定期压缩AOF文件,可以设置自动触发条件。文章来源:https://www.toymoban.com/news/detail-605405.html
通过以上工作流程,AOF持久化能够将写入操作以日志的形式记录下来,并通过AOF重写操作避免AOF文件过大。当Redis服务器重新启动时,可以通过重新执行AOF文件中的写入操作来恢复数据,保证数据的持久性。同时,AOF持久化也允许用户根据需求选择不同的同步策略,以权衡数据的持久性和性能。文章来源地址https://www.toymoban.com/news/detail-605405.html
到了这里,关于Redis追本溯源(三)内核:线程模型、网络IO模型、过期策略与淘汰机制、持久化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!