【Redis】为什么Redis单线程还这么快?通过什么机制进行优化的?

【Redis】为什么Redis单线程还这么快?通过什么机制进行优化的?_第1张图片

文章目录

    • 纯内存操作
    • 高效的数据结构
    • 非阻塞 I/O 与多路复用技术(网络I/O优化)
    • 非CPU密集型任务
    • 单线程的优势
    • 单线程的劣势
    • redis 6.0 引入多线程
    • 后台线程的优化
    • 总结

更多相关内容可查看

Redis的瓶颈在于内存和网络带宽

纯内存操作

Redis是一个内存数据库,它的数据都存储在内存中,这意味着我们读写数据都是在内存中完成,这个速度是非常快的

可能有很多人只知道存内存更快,但是不知道原因

内存:中每个存储单元都有唯一的地址,计算机可以通过这个地址,快速的找到存储单元,实现随机访问,数据传输快,可以更快速的将数据扔给cpu去执行,CPU会将频繁访问的数据和指令从内存中加载到高速缓存(Cache)中,以便更快地访问

磁盘:机械硬盘由盘片、磁头、电机等部件组成。当需要读取数据时,磁头需要移动到相应的磁道和扇区位置,这个过程称为寻道,会比较浪费时间,磁盘缓存主要用于减少对磁盘的实际读写操作,提高数据的读取命中率,但当缓存未命中时,仍然需要从磁盘的物理介质上读取数据

高效的数据结构

Redis是一个KV内存数据库,它内部构建了一个哈希表,根据指定的KEY访问时,只需要O(1)的时间复杂度就可以找到对应的数据。同时,Redis提供了丰富的数据类型,并使用高效的操作方式进行操作,这些操作都在内存中进行,并不会大量消耗CPU资源,所以速度极快

还提供了很多不同的数据结构都有对应的一些优化处理:String、List、Hash、Set、SortedSet

非阻塞 I/O 与多路复用技术(网络I/O优化)

问题:当服务器要处理客户端发来的请求时,需要通过系统调用从网络缓冲区读取数据到程序能够访问的用户空间。在这个过程中,传统的 I/O 操作是阻塞的。这意味着在数据完全接收完成之前,执行读取操作的线程会被挂起,无法执行其他任务。例如,一个服务器同时处理多个客户端连接,如果采用传统的阻塞 I/O 方式,当一个连接在等待数据接收时,整个线程就被占用,无法去处理其他连接的请求,这极大地浪费了服务器资源和降低了效率

意思就是:一个线程去客户端读东西,指令发送给客户端需要等待客户端返回,这样这个线程就一直在等待了,就会产生资源浪费

解决:这是为了解决阻塞 I/O 的问题。I/O 多路复用技术就是为了实现只有在套接字中的数据准备好读取时才进行系统调用。I/O 多路复用模块能够同时监控多个套接字的状态,它会不断检查这些套接字,只有当某个或某些套接字有数据可读时,才会将这些可读的套接字返回给程序。这样,程序就可以只对那些有数据的套接字进行读取操作,避免了在无数据的套接字上进行无效的等待,从而提高了 I/O 操作的效率

多路复用就是去监听多个套接字(也可以理解为多个客户端链接),是否返回数据了(linux的epoll 会检测这个事件),返回到数据就用线程去操作这个数据,而不是让这个线程在等待

非CPU密集型任务

非 CPU 密集型的特性使得 Redis 在运行时不会过度占用 CPU 资源。这使得系统可以将更多的 CPU 资源分配给其他需要的任务,同时 Redis 自身也能利用剩余的系统资源(如内存带宽)来更好地服务请求。例如,在一个服务器上同时运行 Redis 和其他应用程序,由于 Redis 是非 CPU 密集型,不会大量消耗 CPU 资源,其他应用程序可以正常运行,而 Redis 也能高效地利用内存进行数据处理,实现整体系统资源的合理利用,进而保证 Redis 的高性能。

相比之下,像科学计算、图形渲染等任务,需要 CPU 进行大量的算术运算和逻辑处理,属于 CPU 密集型任务。Redis 的这些操作属于非 CPU 密集型,使得 CPU 可以在短时间内完成大量的请求处理。如果 Redis 是 CPU 密集型的,单线程模型下,CPU 长时间忙于复杂计算,会导致其他请求长时间等待,无法充分发挥 Redis 的高性能优势线程可以快速地处理内存操作,并且在处理完一个请求后能迅速切换到下一个请求,不会因为某个请求的复杂计算而阻塞其他请求的处理。例如,在单线程中依次处理多个简单的键值对读取请求,每个请求都能在极短时间内完成,从而实现高并发处理。

单线程的优势

无上下文切换开销:在多线程环境中,CPU 需要在不同线程之间频繁切换上下文,每次上下文切换都需要保存当前线程的状态,并恢复下一个线程的状态,这会消耗一定的 CPU 时间和资源。而 Redis 的单线程模型不存在线程上下文切换的开销,CPU 可以全力运行 Redis 的业务逻辑,提高了运行效率。

小知识:多线程的cpu调度以及上下文切换过程?

  1. 在多线程环境中,计算机系统通常只有一个或有限个 CPU 核心,但可能同时存在多个线程需要执行。由于 CPU在某一时刻只能执行一个线程的指令,操作系统需要通过调度算法来决定哪个线程在何时使用 CPU。当一个线程正在执行时,可能会因为多种原因(如时间片用完、等待 I/O操作完成、被更高优先级的线程抢占等)需要暂停执行,让其他线程有机会使用 CPU。这就导致了 CPU 需要在不同线程之间频繁切换。

  2. 保存当前线程的上下文:CPU将当前正在执行线程的寄存器值、程序计数器、堆栈指针等上下文信息保存到内存中的特定位置,这些信息会被存储到该线程对应的线程控制块(TCB,ThreadControl Block)中。这样,当该线程下次有机会再次执行时,能够从保存的位置恢复这些信息,继续从上次暂停的地方继续执行。加载新线程的上下文:CPU 从内存中读取即将执行的新线程的上下文信息,将其加载到 CPU寄存器中。这些信息包括新线程的程序计数器、堆栈指针以及通用寄存器的值等,使得 CPU 能够按照新线程的状态和执行路径开始执行指令。

无锁开销:多线程编程中,为了保证数据的一致性和安全性,需要使用锁机制来同步对共享资源的访问。加锁和解锁操作不仅会增加代码的复杂性,还会带来一定的性能开销。Redis 单线程模型中,所有操作都是顺序执行的,不存在多个线程同时访问共享资源的情况,因此无需使用锁,避免了锁带来的性能损耗

开发和调试比较友好,可维护性高

单线程的劣势

单线程处理最大的缺点就是,如果前一个请求发生耗时比较久的操作,那么整个Redis就会阻塞住,其他请求也无法进来,直到这个耗时久的操作处理完成并返回,其他请求才能被处理到。

我们平时遇到Redis变慢或长时间阻塞的问题,90%也都是因为Redis处理请求是单线程这个原因导致的。

所以,我们在使用Redis时,一定要避免非常耗时的操作,例如使用时间复杂度过高的方式获取数据、一次性获取过多的数据、大量key集中过期导致Redis淘汰key压力变大等等,这些场景都会阻塞住整个处理线程,直到它们处理完成,势必会影响业务的访问。

redis 6.0 引入多线程

启用多线程的命令

io-threads-do-reads yes 
io-threads [number of threads]

官方建议:4核机器设置2-3个线程,8核机器设置6个线程。线程数应小于机器核心数,尽量不超过8个线程。

Redis 6.0引入多线程是因为网络I/O模块成为CPU时间的瓶颈。多线程用于处理网络I/O,充分利用CPU资源,减少网络I/O阻塞导致的性能损失。多线程模式下,接收、发送和解析命令可以配置为多线程执行,但命令执行(涉及内存操作)仍为单线程。因此,Redis的多线程部分仅用于处理网络数据读写和协议解析,命令执行仍按顺序执行,无并发安全问题。

可见,Redis并不是保守地认为单线程有多好,也不是为了使用多线程而引入多线程。Redis作者很清楚单线程和多线程的使用场景,针对性地优化,这是非常值得我们学习的。

后台线程的优化

Redis内部还有其他工作线程在后台执行,它负责异步执行某些比较耗时的任务,例如AOF每秒刷盘、AOF文件重写都是在另一个线程中完成的。

而在Redis 4.0之后,Redis引入了lazyfree的机制,提供了unlink、flushall aysc、flushdb async等命令和lazyfree-lazy-eviction、lazyfree-lazy-expire等机制来异步释放内存,它主要是为了解决在释放大内存数据导致整个redis阻塞的性能问题。

在删除大key时,释放内存往往都比较耗时,所以Redis提供异步释放内存的方式,让这些耗时的操作放到另一个线程中异步去处理,从而不影响主线程的执行,提高性能。

总结

总共有这几个大的方向,保证了单线程的环境下的处理性能:

内存、数据结构、非阻塞I/O、多路复用,非CPU密集型、单线程的优势、多线程以及后台线程的处理优化

你可能感兴趣的:(数据库,#,Redis,redis,数据库,缓存,多路复用,内存优化,redis快)