1. | Redis 是单线程还是多线程 | 最早的版本3.x是单线程。 版本4.x,严格意义不是单线程。负责处理客户端请求的线程是单线程,开始加入异步删除。 6.0.x版本后明确使用全新的多线程来解决问题 | |
2. | 说说IO多路复用 | ||
3. | Redis 为什么快 | IO多路复用+epoll函数使用 |
Redis 的网络IO和键值对读写由一个线程完成,Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回(socket写)等都是有一个顺序串行的主线程处理。这也是Redis 对外提供键值存储服务的主要流程。
Redis 的其他功能,如持久化AOF、RDB、异步删除、集群数据同步等,由额外的线程执行。
Redis 命令工作线程是单线程的,对整个Redis 来说是多线程的。
基于内存操作 | Redis 的所有数据都存在内存中,所有的运算都是内存级别的。 |
数据结构简单 | Redis 的数据结构是专门设计的,简单的的数据结构查找和操作的大部分时间复杂度O(1)。 |
多路复用和非阻塞I/O | Redis 使用I/O多路复用功能来监听多个socket连接客户端,可以使用一个线程连接处理多个请求,减少线程切换带来的开销。 |
避免上下文切换 | 单线程模式,避免了不必要的上下文切换和多线程竞争,节省了多线程带来的时间和性能的消耗,单线程不会发生线程死锁。 |
问题 | 说明 | 解决方法 |
大key删除问题 | 正常情况下,使用 del 指令很快的删除数据,当需要删除的 key 是非常大的对象时,例如成千上万个元素的 hash 集合, del 指令就会造成 Redis 主线程卡顿。 | 惰性删除 unlink key flushdb async |
多线程异步惰性删除。 主线程处理读写请求,开启子线程进行异步删除操作。 从redis主线程剥离让bio子线程来处理,极大地减少主线阻塞时间。从而减少删除导致性能和稳定性问题。 | ||
Redis的瓶颈来自内存和网络I/O,官网说明如下
Redis FAQ | RedisCommonly asked questions when getting started with Redishttps://redis.io/docs/getting-started/faq/1. Redis 6/7 如何解决单个主线程处理网络请求的速度跟不上底层网络硬件的速度?
采用多个I/O 线程来处理网络请求,提高网络请求处理的并行度。对于读写操作命令仍使用单线程处理,不必为保证Lua脚本、事务的原子性,额外开发多线程互斥机制。
2. 主线程和I/O线程如何协作完成请求处理
阶段一: 服务端和客户端建立socket连接,并分配处理线程
主线程负责接收建立连接的请求。当客户端请求和实例建立Socket连接时,主线程会创建和客户端的连接,并把Socket放入全局等待队列中。主线程通过轮询的方式把Socket连接分配给I/O线程。 | |
阶段二: I/O线程读取并解析请求
主线程把Socket分给I/O线程,就会进入阻塞状态,等待I/O线程完客户端的读取和解析。多个I/O线程在并行处理,过程很快完成。 | |
阶段三: 主线程执行请求操作
等待I/O线程解析完成请求,主线程会以单线程的方式执行这些命令操作 | |
阶段四: I/O线程回写Socket和主线程清空全局队列
当主线程完成请求操作后,把需要返回的结果写入缓冲区,主线程会阻塞等待I/O线程把结果回写到Socket中,并返回客户端。和I/O线程读取解析请求一样,I/O线程回写Socket时,多个线程并发执行,因此回写速度也很快。等I/O线程回写Socket完毕,主线程会清空全局队列,等待客户端的后续请求。 |
3. 五种网络IO模型
Blocking I/O (BIO) 阻塞I/O | NoneBlocking I/O (NIO) 非阻塞I/O | IO Multiplexing I/O IO多路复用 | Signal Driven I/O 信号驱动I/O | Asynchronous I/O 异步I/O |
IO 多路复用是 一种同步I/O模型。实现一个线程监视多个文件句柄,一旦某个文件句柄就绪时就能够通知到对应应用程序进行相应的读写操作,没有文件句柄就绪时就会阻塞应用程序,从而释放CPU资源。
I/O | 网络I/O,在操作系统层面指数据在内核态和用户态之间的读写操作。 |
多路 | 多个客户端连接(连接就是套接字描述符,即socket或者channel) |
复用 | 复用一个或者多个线程 |
IO多路复用 | 一个或一组线程处理多个TCP连接,使用单线程就能够实现同时处理多个客户端连接,无需创建或维护过多的进程或线程。 也就是一个服务端进程可以同时处理多个套接字描述符。 |
实现IO多路复用的模型 | 可以分select->poll->epoll 三个阶段 |
将用户Socket对应的文件描述符(FileDescriptor)注册进epoll,然后epoll监听Socket消息到达,避免无用操作。此时Socket采用非阻塞模式。整个过程旨在调用select、poll、epoll的时才会阻塞,收发客户消息时不会阻塞,整个进程或线程就被充分利用,这就是事件驱动(Reactor反应模式)
在单个线程通过记录跟踪每一个Sockek(I/0流)的状态来同时管理多个I/0流,一个服务端进程可以同时处理多个套接字描述符。目的是尽量多的提高服务器的吞吐能力。
类似nginx、redis
只是用一个服务端进程可以同时处理多个套接字描述符连接
Redis 工作线程是单线程的,但是整个Redis来说是多线程的 | ||
单线程 | I/O的读和写本身是阻塞的,当Socket中有数据时,Redis会通过调用先将数据从内核态空间拷贝到用户态空间,再交给epoll调用,拷贝过程是阻塞的,当数据量越大拷贝时间越久,这些操作都是基于单线程完成的。 | |
多线程 | 主线程IO读写任务拆分给一组独立线程去执行,使多个Socket的读写并行化,采用多路I/O复用技术让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),将最耗时的Socket的读取、请求解析、写入单独拆分出去独立执行,剩下的命令仍由主线程串行执行和内存的数据交互 |
- #设置io-thread-do-reads配置项为yes,表示启动多线程。
- io-threads-do-reads yes
- # 设置线程个数。关于线程数的设置,方的建议是如果为 4 核的 CPU,建议线程数设置为2或3,如果为 8 CPU 建议线程数设置为 6,线程数一定要小千机器核数,线程数并不是越大越好
- io-threads 4
- 🌹 以上分享 Redis 单线程和多线程的相关知识,请指教🤝。
-
- 🌹🌹 如你对技术也感兴趣,欢迎交流。
-
- 🌹🌹🌹 如有需要,请👍点赞💖收藏🐱🏍分享