当前位置 : 主页 > 编程语言 > java >

redis为什么这么快

来源:互联网 收集:自由互联 发布时间:2023-03-22
一直以来Redis以“快“在缓存中间件中大杀四方。Redis是复杂、大数据量列表查询耗时、高并发访问缓慢的优化手段——上缓存的必备“武器”,同时“Redis为什么这么快?”已经成为了

一直以来Redis以“快“在缓存中间件中大杀四方。Redis是复杂、大数据量列表查询耗时、高并发访问缓慢的优化手段——上缓存的必备“武器”,同时“Redis为什么这么快?”已经成为了当下程序员面试的必考题之一。

常见的答案有“Redis是基于内存操作的,没有磁盘IO当然猛”,“Redis专门设计和优化的数据结构为快速查询提供了保障”,“Redis是单线程工作,省去了上下文切换和CPU消耗,不存在锁竞争更没有死锁”,以及“Redis使用基于IO多路复用机制的线程模型所以快”。

当然,这些答案都没问题。但是为什么基于IO多路复用机制的线程模型就会快呢?在Redis工作的过程中,这种模型究竟发挥了什么作用?值得我们进一步探索。

什么是I/O多路复用机制的线程模型?

理论角度讲,I/O多路复用是一种同步 IO 模型,实现一个线程可以监视多个文件句柄。一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出 cpu。IO 是指网络 IO,多路指多个TCP连接(即 socket 或者 channel),复用指复用一个或几个线程。

实现角度讲,I/O多路复用模型是利用 select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,然后程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个客户端的网络IO连接请求(尽量减少网络 IO 的时间消耗)。

redis为什么这么快_多路复用

Redis中如何使用I/O多路复用模型?

Redis基于Reactor模式开发了自己的网络事件处理器,称之为文件事件处理器(File Event Hanlder)。文件事件处理器由Socket、IO多路复用程序、文件事件分派器(dispather),事件处理器(handler)四部分组成。

文件事件处理器的模型如下所示:

redis为什么这么快_Redis_02

I/O 多路复用模型封装了底层内核提供的select、poll、epoll三种函数,Redis之所以快就是因为优先选择了时间复杂度为O(1)的epoll函数作为底层实现。三种函数逐级进化,都用来处理fd句柄(文件描述符标识) 由慢到快再到起飞,让我们看看这三大函数的演变:

Select函数执行流程:Select是一个阻塞型函数,当没有数据时会阻塞在select代码行,有数据时会改变标识位,返回数据停止阻塞,进行数据处理。其缺点在于利用bitmap(1024位)装载客户端,最多能处理1024个客户端,高并发请求下处理缓慢;标识位的改变不可重用,有数据标识位就会被置位;select不能通知用户态哪个socket有数据,仍然需要O(n)遍历,同时处理的流越多,无差别轮询时间就越长。

Poll函数执行流程:Poll函数从功能上与select函数相差不大,但结构上通过链表进行实现,优化了bitmap处理客户端数量限制的问题且将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,不存在数据标识位占位的问题,但是无差别轮询的问题依然存在。

Epoll函数执行流程:Epoll函数通过新增三个方法的实现和数据结构的变更,把从轮询的本质变成了事件驱动通知。

redis为什么这么快_多路复用_03

epoll_create就是用来创建一个epoll池,epoll_ctl用来将fd句柄加载到epoll池中。

第一个优化点。fd句柄加入epoll池后,需要新增、修改、删除的操作时,如果是map数据结构管理fd句柄元素有容量上的限制,如果是list来管理,则需要遍历整个链表才能进行删改操作。而Linux内核对于 epoll 池的内部实现利用了红黑树的结构体来管理这些进程来的句柄fd,时间复杂度从O(n)降为O(log n),在大量的元素中仍能保持稳定的性能查询。

第二个优化点。数据在准备好之后,如何迅速感知呢?也就是我们说的事件驱动。那就是在epoll_ctl方法中除了要将句柄放入epoll池中,还有一个重要的工作就是设置poll的回调,这也是epoll池高效的核心原理。在内核中实现的结构体中存在file_operations结构,其中的poll方法就提供了定制监听事件的机制实现。通过poll机制让上层能直接告诉底层,数据就绪后,自动将fd相关的结构体放到就绪队列(也就是epoll_wait)中,在网络硬件等基础硬件进行回调时,在就绪队列中将准备好的数据唤醒进行处理。

这样linux内核在处理socket请求的过程就从无差别轮询升级为事件驱动,在高并发的情况下,通过poll这个“跑腿工”来通知“各单位”来活了,实现了处理请求的高效性。

这下你明白Redis为什么这么快了吗?其实nginx、Go语言中的协程等等基于linux内核实现的高性能都是epoll池发挥的关键作用哦。

上一篇:Spring MVC 拦截器详解
下一篇:没有了
网友评论