今天我们讨论一下 redis 面试高频题,为什么 Redis 那么快?
首先,你可以先想一下答案,我先说下大家普遍的答案:
- 单线程
- 基于内存操作,速度快
- I/O 多路复用
相信很多人第一时间回答出来上面这些,那么面试官一般会接着问,所有的操作都是单线程吗?单线程为什么快呢,什么是 I/O 多路复用?很多人这个时候就会 G 了。
今天我们好好聊聊,首先我们要知道,我们常说的 Redis 是单线程,主要是指 Redis 在网络 I/O 和 键值的读写操作是有一个线程来完成的,但是其他的一些功能是由其他的线程来执行的,如:
- 持久化数据落盘
- 集群主从同步
- 异步数据删除
所以严格来说,Redis 并不是单线程的。但是在面试中,我们可以先铺垫一下,如果面试官刨根问底,显然是不够的。
1. Redis 为什么用单线程
日常开发中,我们接触都更多是多线程,为了能够提升系统的请求数或者是吞吐率,我们会采用多线程来实现。看下图:
为啥会出现预期和实际结果不一样的情况?其实是当出现多线程同时在操作一个共享的资源时,我们为了保证结果的正确性,我们需要有额外的开销来保证,如锁。当有锁出现了,我们就需要在考虑在什么时候需要获取锁,释放锁,其中就需要记录锁的状态,可想而知,性能就会下降。这就是多线程模式在面临高并发场景下共享资源的访问问题。
所以大家应该知道为啥 Redis 要用单线程了吧,我们接着来说说为什么单线程的 Redis 为啥能获得高性能的原因。
2. 单线程 Redis 为什么那么快
我们正常理解下,多线程在处理能力上是要高于单线程的,但是为啥 Redis 的单线程模型却能达到惊人的每秒 10 万级别的处理能力呢?(采用百度百科)
这就是 Redis 在设计上的优秀之处。
实现高性能的一个方面是 Redis 是基于内存操作,它内部高效的数据结构,如跳表(可以看看之前的文章,# 今天终于知道 Redis 为什么要用跳跃表了)哈希表等,还有就是 Redis 采用了 I/O多路复用机制,从而保障在网络 I/O 中能够高效的处理并发请求,实现高吞吐率。这二者是实现高性能的重要原因。
接下来就说说本文最重要的一个 I/O 多路复用机制。
3. I/O 多路复用模型
在 Linux 中,我们都知道 Linux 对文件的操作实际上就是通过文件描述符(fd),通过一个进程监听多个文件描述符,一旦某个文件描述符准备就绪,就会通知对应的程序响应并处理,这种通知的方式优势在于在单个时间内能够处理更多的链接。
Linux 中的 I/O 多路复用机制是指一个线程处理多个 IO 流,也就是我们通常说的 select/epoll。
在 Redis 单线中,允许在内核中同时存在多个监听文件描述符,内核会去监听在这上面的链接请求,一但有请求就会交给 Redis 线程处理,从而实现一个Redis 线程可以处理多个 IO 流。那么什么是 select、epoll?
3.1 select
select 是一个函数,它支持最大的连接数是 1024 或者 2048,因为在 select 函数需要传入fd_set 参数,这个 fd_set 的长度取决于操作系统的位数 1024 或者 2048。
其中 fd_set 是一个 bitmap,当数据没有到缓冲区那么就是 0, 反之到了缓冲区就是 1。select 函数的功能是将 fd_set 遍历,判断标识位是否存在变化,若发生变化就发起中断处理。
3.2 epoll
epoll 的首次提出是在 Linux 2.6 内核中,他是为了解决 select 的缺点。
它定义了 epoll_event 结构体来处理,解决 select 存在最大链接数的限制。epoll 不会遍历所有的文件描述符(fd),epoll 会将准备就绪的文件描述符维护在一块指定的空间内,每次从其中取出已经准备就绪的文件描述符进行处理,大大提高了性能。
这就是 select 和 epoll 的区别,想看具体的源码可以自行了解,这里只是简单的描述一下。
3.3 Redis I/O 多路复用模型
在 Redis 中,其网络框架调用采用的是复用机制中的 epoll 机制,让内核监听文件描述符,此时 Redis 线程不会阻塞在某一个特定的监听或已连接的文件描述符,从而可以达到同时处理多个链接请求,提高并发性能。如下图,Redis I/O 多路复用模型:
为了当请求到达时会通知 Redis 线程, select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。
说说回调机制时如何高效的工作的。当 select/epoll 监测到有文件描述符请求到达时,会发出对应的事件处理,这些事件会被放到一个事件处理队列中,然后 Redis 会对事件进行处理。通过对队列进行轮询,可以提高 CPU 利用率。同时,Redis 在处理事件时,会调用其相应的事件处理函数,实现基于事件的回调。最终使得请求能够第一时间及时响应,再一步提升 Redis 的相应性能。
举个发起读数据的例子,更好的理解上面 Redis I/O 多路复用模型。
当程序发起 Accept 和 Read 事件时, Redis 线程会注册这 Accept 和 Get 事件 对应的回调函数。当 Linux 内核监听到有链接请求或者读数据的请求时,会触发 Accept 和 Read 事件,与此同时调用 Redis 的 Accept 和 Get 函数进行数据处理。
4. 总结
现在,我们知道啦,Redis 为什么快了吧。 Redis 单线程是指在网络 I/O 和 键值的读写操作是有一个线程来完成的,采用单线程的好处是避免了多线程并发需要竞争获取锁。单线程之所以性能高效是因为其选择了 I/O 多路复用模型。搞懂了 select/epoll 这这些,慢慢会发现自己会逐渐比身边的伙伴优秀一些。
好了,就到这,我是程序员祝融,欢迎关注,我们下期再见!