本文分为以下几个部分说明介绍redis单线程
1.redis为何使用单线程
2.redis使用单线程为何性能那么高
3.redis哪些功能不是单线程
一.redis为何使用单线程
现在的cpu都是多核多线程的架构,理论上程序上开多个线程运行,就代表能有多个线程同时并发的在运行,N个线程运行的总时间等于运行时间最长的任务;单线程运行的总时间是每个任务运行时间之和。
举个例子:
有三个任务
任务 | 运行时间 |
任务1 | 10s |
任务2 | 20s |
任务3 | 15s |
单线程执行的总时间:45s 多线程执行的总时间:20s
所以随着运行的线程数增多,系统的吞吐量也会上升。但是实际情况往往是这样:随着线程数的增加,吞吐量是会先上涨,后面,即使再增加线程数,吞吐量也是不会再上涨了,趋于平稳状态。
为什么会这样呢?
多线程运行时,通常都会访问一些共享资源,这些资源同时只能一个线程访问与修改,修改完后才能让别的线程访问,这就导致了多线程并发运行,变成了单线程串行运行,多线程都在排队等待,而且还有上下文切换的开销。这就导致了即使增加再多的线程也无法增加吞吐量。
举个例子:
假如redis是多线程处理请求的,一个线程从list集合LPOP获取元素,需要对集合的长度-1;有另一个线程从list集合LPUSH获取元素,需要对集合的长度+1;两个线程需要对共享资源(集合长度)访问,对共享资源的访问同时只能是一个线程,这样多线程就变成串行操作。
并发编程中,对共享资源的访问都是比较难处理的,如果只是简单的加一个粗粒度的锁来保证,这就会导致性能上不去的情况。
所以采用多线程会导致访问共享资源问题很难处理,而且会导致代码比较复杂,难易维护,所以redis采用了单线程模型。
二.redis使用单线程为何性能那么高
官方数据:redis单线程模型可以达到10Wtps/s。
我觉得redis那么快主要有两个原因:
- 数据都存放在内存,高效的数据结构,比如hash列表,通过计算key的hash值就能快速定位到存放位置。
- io多路复用的应用,一个线程就可以高效的管理多个连接。
redis的单线程操作是指:网络 IO和键值对读写是由一个线程来完成的,所以下面总结介绍一下阻塞io和非阻塞io的区别。以及非阻塞io在多路复用的应用。
- 阻塞io
阻塞io模型下,由于调用socket的读写方法都会阻塞,一直等待有对端有数据传输,才会访问,所以一个线程只能处理一条连接,所以需要创建很多个线程来处理连接;这种模式可以由线程池来优化,但是也是治标不治本,因为很多线程其实是空闲不工作的。
- 非阻塞io
非阻塞的模式下,调用socket的读写方法,如果没有数据可读,就会立即返回,线程可以做其他事情;如果有数据可读,就会从内核把数据拷贝到应用程序,完成数据的获取。
- 多路复用
在多路复用的模式下,通常是( select/epoll模式 ),对建立好的连接,可以向操作系统订阅可读可写事件,当连接可读写时,操作系统就会发出通知。线程就可以处理这个连接的数据读写。
对redis来说,会向select/epoll注册读写事件和连接建立事件,系统内核一旦监听到有对应的事件发生,就会把事件放到一个队列中,由redis线程获取队列的事件处理,redis根据不同的事件会调用不同的处理器处理。
三.redis哪些功能不是单线程
redis是单线程处理客户端请求的,如果单线程处理的操作是比较耗时的,就会导致客户端的请求没办法处理,所以redis耗时的操作不能由处理客户端请求的线程来处理。
有哪些是耗时的请求呢?
持久化(自动生成RDB,客户端的bgsave命令)、异步删除(客户端异步删除命令)、集群数据同步(主节点和从节点的数据同步)
这些功能,都是主进程fork出子进程来进行操作的,不会影响到客户端请求的正常处理。当然,redis的处理客户端的请求也会存在耗时操作,导致其他客户端的请求无法处理,下节我们来看看redis有哪些耗时的操作,以及怎么优化。
四.总结
我们重点学习了 Redis 关于单线程的三个问题:“Redis 为何使用单线程?”“使用单线程为何性能那么高?”“哪些功能不是单线程?”
现在,我们知道了,Redis 单线程是指它网络 IO和键值对读写都是一个线程完成的,而 采用单线程的一个核心原因是避免多线程开发的访问共享资源的问题。单线程的 Redis 也能获得 高性能,多路复用的 IO 模型+高效的数据结构有密切相关,因为这避免了建立新连接和io读写方法 阻塞问题。