Redis为什么选择单线程?
Redis4之后开始支持多线程,Redis6/7后慢慢稳定。
Redis 4开始支持多线程异步删除指的是Redis服务器在删除大型键时可以使用多线程来进行异步删除操作,而不是指AOF(Append Only File)和RDB(Redis Database)文件的操作。这项特性主要是为了解决在删除大型键时,可能会造成的长时间阻塞问题。
在Redis中,一些操作是以单线程的方式执行的,这意味着在执行某些耗时的操作时,如删除一个包含大量元素的键(如一个大的List、Set、Sorted Set或Hash),可能会导致服务器在这段时间内无法处理其他命令,从而影响整体性能和响应时间。
为了解决这个问题,从Redis 4.0版本开始,Redis引入了UNLINK命令,它是DEL命令的非阻塞版本。UNLINK命令接受一个或多个键作为参数,并将它们从键空间中移除,但实际的内存回收操作是在后台异步完成的。这样,即使是删除大型键,也不会阻塞主线程,主线程可以继续处理其他命令,从而提高了Redis的响应性能。
这个特性利用了多线程,但需要注意的是,Redis的大多数操作仍然是单线程的,多线程主要用于一些特定的后台任务,如异步删除、某些类型的IO操作等。主线程还是负责处理所有的命令请求,保证了命令处理的原子性和一致性。
至于AOF和RDB,它们是Redis的两种持久化机制:
- AOF:Append Only File持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。AOF文件可以在Redis重启后被重新执行来恢复数据。
- RDB:Redis Database持久化是通过定期创建数据库快照来保存数据的。它会在指定的时间间隔内生成整个数据库的快照,并保存在磁盘上的一个文件中。
这两种持久化策略与多线程异步删除是两个不同的概念,多线程异步删除主要关注的是内存中键的删除操作,而AOF和RDB是为了确保数据的持久化存储。
我们平常说的Redis是单线程是什么意思?
客户端通过Socket和服务端建立起连接,输入命令,操作linux和redis内核来进行数据交换,最终写入Socket然后返回,也就是说set k1 v1不会有人来抢 天生就是单线程
Redis早期版本3.x性能快的原因:
多线程竞争保证数据要加锁
Redis是单线程如何用多核?
现在回答
早期回答
为啥4.x以前一直用单线程?
单线程的缺点是什么?
案例
在Redis中,FLUSHDB
命令用于清空当前数据库中的所有键,这是一个非常危险的操作,因为它会立刻删除所有数据。
从Redis 4.0版本开始,FLUSHDB
和FLUSHALL
命令支持 ASYNC
选项,允许异步执行清除操作。异步版本的这些命令不会阻塞Redis服务器处理其他命令,而是在后台线程中清理数据。
命令格式如下:
FLUSHDB [ASYNC]
- 不带任何选项时,
FLUSHDB
将同步清空当前数据库。 - 如果指定了
ASYNC
选项,FLUSHDB
将异步清空当前数据库。
例如:
FLUSHDB ASYNC
当使用 ASYNC
选项时,命令返回给客户端一个简单的响应,表示清空操作已经开始。实际的清空操作会在后台异步执行,这意味着在清空操作完成之前,客户端可以继续向Redis发送其他命令。
另外一个相关命令是 FLUSHALL
,它的作用是清空所有数据库(Redis可以有多个逻辑数据库):
FLUSHALL [ASYNC]
- 不带任何选项时,
FLUSHALL
将同步清空所有数据库。 - 如果指定了
ASYNC
选项,FLUSHALL
将异步清空所有数据库。
使用这些命令时需要非常谨慎,因为一旦执行,所有的数据都会丢失,并且这一操作是不可逆的。通常在测试环境或者某些特殊的情况下需要快速清理数据库时才会使用到这些命令。在生产环境中,除非你完全确定这是必要的,否则不应该轻易使用这些命令。
Redis为什么这么快?
Redis 6/7真正多线程登场
采用多个IO线程只是用来处理网络请求即Socket连接
那主线程和IO线程是如何处理请求的?
主线程阻塞,等待IO线程完成请求读取和解析
1.验证网络通不通
2.ip是否在合法的防火墙范围内
3.端口是否正确
4.是否达到最大连接等……
从socket中读取和解析的过程比较慢,主线程就不用等了,只拿解析好的去执行
IO线程是复用的类似医院分诊台
Unix网络编程中的五种IO模型
每个连接过来都会给个fd文件句柄【相当于医院挂号挂什么】
那什么是IO多路复用?
Redis从其早期版本开始就使用了IO多路复用技术。IO多路复用是Redis高性能的关键特性之一,它允许Redis服务器同时处理来自多个客户端的连接和请求,而不是为每个连接创建一个线程或进程。
IO多路复用使得单线程的Redis能够使用非阻塞I/O操作,这样Redis服务器可以在不同的连接之间高效切换,只要有数据可读或可写,就处理相关的请求。这种方式让Redis能够充分利用其单线程模型,同时保持高吞吐量和低延迟。
在底层实现上,Redis使用了不同的IO多路复用库,如select、poll、epoll(在Linux上)、kqueue(在BSD系统上)和evport(在Solaris上),具体使用哪种实现取决于运行Redis的操作系统平台的支持情况。
由于IO多路复用是Redis的基础架构组件之一,因此自Redis诞生之初就支持这一特性。随着Redis版本的更新,对IO多路复用的支持也在不断优化和改进。
说人话
小总结:只使用一个服务端进程,就可以同时处理多个Socket连接
精简版
如何打开多线程?
详解
Redis必须装在Linux相关的服务器系统上面,才能发挥出最佳性能。Windows装可以用,但发挥不出完美性能。
IO多路复用是对文件描述符的复用,而不是特定于线程或进程。它指的是操作系统的一种能力,允许单个线程或进程同时监视多个IO通道(例如,套接字或文件描述符 Socket or Channel)以检查它们是否有IO操作就绪,比如数据是否可读或可写。
IO多路复用的含义
“多路”意味着多个网络连接或IO通道,“复用”意味着同一个线程或进程可以处理多个连接或通道。在没有IO多路复用的情况下,你可能需要为每个网络连接创建一个线程或进程来独立地管理IO操作,这在大量连接的情况下会占用大量的系统资源并降低性能。
IO多路复用允许你只需一个线程或进程就能有效地管理多个IO操作。当某个IO操作就绪时(例如,一个TCP连接上有数据可以读取),操作系统会通知应用程序,应用程序随后可以进行相应的读写操作。
IO多路复用的工作方式
IO多路复用的工作依赖于操作系统提供的特定系统调用,如:
- select: 最早出现的多路复用API,但由于其一些限制(如最大文件描述符数量限制),在现代应用中使用较少。
- poll: 类似于select,但没有最大文件描述符数量的限制。
- epoll (Linux特有): 比select和poll更高效,因为它不需要每次调用时都重复传递监视列表,并且它能够在就绪描述符数量上扩展得更好。
- kqueue (BSD系统特有): 类似于epoll,但用于BSD系统,如FreeBSD或macOS。
这些系统调用允许应用程序提供一个或多个文件描述符的列表,然后等待其中任何一个就绪以进行非阻塞性的IO操作。当操作系统检测到列表中的一个或多个文件描述符就绪时,它会唤醒等待的线程或进程,然后该线程或进程可以针对就绪的文件描述符进行相应的操作。
IO多路复用的优点
使用IO多路复用的主要优点是提高了资源利用率和应用程序的性能。它允许应用程序在一个线程或进程内部高效地管理数百甚至数千个网络连接,而不必为每个连接创建额外的线程或进程。这在编写高性能的网络服务器和客户端应用程序时尤其有用,如数据库服务器、web服务器和文件传输应用程序。
谁准备好了就处理谁。
React【反应堆模式】
左边:
多个Socket连接由同一个进程进行管理,
中间:
命令放入事件队列中(FIFO)
右边:
事件派发器,根据顺序来交给不同的事件处理器处理。
文件事件分派器的队列是单线程的
吃米线
BIO
一次只能处理一个Socket连接,如果这个连接的客户端一直不发数据就会阻塞在read( )方法上,其他客户端也不能进行连接。
C语言底层函数
应用程序是用户态会发起系统调用,然后来到Linux系统是内核态,一开始是无数据报准备好的状态,等待内核唤醒进程读取数据,接着会进行数据报的复制,然后将数据从内核复制到用户空间,复制完成返回成功指示。
recvfrom 是一个用于接收数据的系统调用,通常用于基于数据报(如 UDP)的网络通信。在阻塞I/O(BIO)模式下,recvfrom 函数的工作流程大致如下:
- 系统调用:当应用程序调用 recvfrom 函数时,它会发起一个系统调用,从用户态切换到内核态。系统调用是应用程序和操作系统内核之间的接口,它允许应用程序请求操作系统的服务。
- 等待数据:如果此时内核的网络缓冲区中没有可用的数据报,调用 recvfrom 的进程将被置于等待状态(阻塞)。内核会等待直到有一个数据报到达网络接口,并被放入与套接字关联的缓冲区中。
- 唤醒进程:当一个数据报到达并存入缓冲区后,内核会唤醒之前因调用 recvfrom 而阻塞的进程。
- 数据复制:内核将数据报从内核空间缓冲区复制到用户提供的缓冲区中。这个复制过程包括了从内核空间到用户空间的数据传递。
- 返回成功指示:数据复制完成后,recvfrom 返回,通知应用程序已经收到了多少字节的数据。如果出现错误,它将返回一个错误码。
需要注意的是,在操作系统内部可能还有很多复杂的操作发生,如网络栈的处理、中断处理、上下文切换等,但从应用程序的角度看,上述步骤是它主要关心的过程。
另外,recvfrom 通常用于无连接的协议(如 UDP)。对于面向连接的协议(如 TCP),通常使用 recv 或 read 函数。而且,在非阻塞I/O或异步I/O模型中,数据的接收过程可能会有所不同,例如,应用程序可能不会阻塞,而是定期检查数据是否已经准备好,或者通过I/O多路复用(如 select, poll, epoll)来同时监控多个套接字。
服务端
启动
客户端
启动 秒结束
效果
优化
客户端
服务端
启动
启动1号客户端并发送 1号顾客1次 成功接收
继续发送正常接收
此时启动2号客户端 可以正常连接上
2号客户端发送 2号顾客1次 2号顾客2次 服务端都能成功接收
此时发送1号顾客4次 2号顾客3次 都能正常接收并处理!!!!解决了!!
Tomcat7之前采用的就是多线程BIO来解决多连接,容易把Tomcat内存打满
缺点
NIO
用轮询替代等待,
在NIO(非阻塞I/O)模式下,应用程序的工作方式与传统的阻塞I/O(BIO)模式有所不同。在非阻塞模式下,当应用程序调用诸如 recvfrom
这样的函数时,如果没有数据包准备好,函数通常不会阻塞程序的执行,而是立即返回。返回值可以是错误码,也可以是特殊的指示,表明没有数据可用。
在UNIX和类UNIX系统中(包括Linux),当一个套接字设置为非阻塞模式,并且对它执行 recvfrom
调用时:
- 如果没有数据可用,
recvfrom
通常会返回-1
,并且设置errno
为EAGAIN
或EWOULDBLOCK
(这两个错误码通常是相同的值,但是名称不同,是为了历史兼容性)。这并不是一个真正的“错误”,它只是一个信号,告诉应用程序套接字当前没有数据。 - 如果有数据可用,
recvfrom
会读取数据并返回读取的字节数。
在NIO模式下,应用程序通常会使用I/O多路复用函数(如 select
、poll
、epoll
在 Linux 上)来监控多个套接字的状态。通过这些函数,应用程序可以在多个套接字上等待事件(如可读、可写、错误等)发生,而无需为每个套接字创建单独的线程。当I/O多路复用函数指示某个套接字上有数据可读时,应用程序再对这个套接字调用 recvfrom
,此时可以确保调用不会阻塞,因为已经知道有数据等待处理。
例如,使用 select
函数的伪代码如下:
// 将套接字设置为非阻塞模式
int flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);
// 等待套接字上的事件
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(socket_fd, &read_fds);
int ret = select(socket_fd + 1, &read_fds, NULL, NULL, NULL); // 这里可以设置超时
if (ret > 0 && FD_ISSET(socket_fd, &read_fds)) {
// 套接字准备好读取
ssize_t bytes_read = recvfrom(socket_fd, buffer, sizeof(buffer), 0, NULL, NULL);
if (bytes_read > 0) {
// 读取成功,处理数据...
} else if (bytes_read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// 没有数据可读取
} else {
// 真正的错误
}
}
在这个例子中,select
函数用于等待套接字变得可读。如果 select
报告套接字准备好了,recvfrom
调用将读取数据而不会阻塞。如果没有数据,recvfrom
会返回 -1
并设置 errno
为 EAGAIN
或 EWOULDBLOCK
。
面试如何回答?
从BIO的ServerSocket【JDK1.0】===》ServerSocketChannel【JDK1.4】
accept()方法会返回null,read()方法读不到返回0。
客户端
服务端
启动服务端,客户端发送数据,能正常接收
此时开启2号客户端,都能正常连接上并且发送消息
缺点
IO多路复用【事件驱动IO】
一个Socket连接过来,耷拉上后会在Linux服务器这边开通个fd【文件描述符】,操作系统底层就会分配一个文件描述符
应用程序调用select函数从用户态到内核态,然后阻塞住,此时无数据报,等待有数据报准备好,返回可读条件,接着应用程序调用recvfrom函数然后来到Linux系统是内核态,一开始是无数据报准备好的状态,等待内核唤醒进程读取数据,接着会进行数据报的复制,然后将数据从内核复制到用户空间,复制完成返回成功指示。
IO多路复用确实涉及到一些您提到的步骤,但有一些细节需要澄清。以下是使用select进行IO多路复用的一般流程:
- 用户态到内核态:当应用程序调用 select(或其他IO多路复用的API,如 poll 或 epoll)函数时,应用程序由用户态切换到内核态。
- 注册监听事件:在调用 select 函数时,应用程序会传递一组文件描述符(file descriptors)以及它希望监听的事件类型(如可读、可写、异常等)。
- 阻塞等待:select 函数会阻塞应用程序的执行,等待一个或多个文件描述符满足监听事件的条件。在此期间,应用程序线程是阻塞的,不消耗CPU资源。
- 事件发生:当有数据报到达网络接口,或者已经存在于某个监听的文件描述符的内核缓冲区中,且满足应用程序指定的条件时,如某个套接字可以进行非阻塞读取,内核则使 select 调用返回。
- 通知应用程序:select 函数返回后,应用程序会遍历文件描述符集合,来确定哪些文件描述符已经就绪可以进行IO操作,例如哪些套接字可读或可写。
- 执行非阻塞IO操作:应用程序随后对这些就绪的文件描述符执行IO操作。因为 select 已经指示这些文件描述符就绪,所以这些IO操作(例如 recvfrom 对于可读套接字)应该是非阻塞的,不会导致应用程序线程再次阻塞。
- 数据复制到用户空间:如果应用程序执行了读操作(如 recvfrom),内核会将数据从内核空间的套接字缓冲区复制到用户空间提供的缓冲区中。
- 操作完成:一旦数据被复制,recvfrom 等函数将返回,告知应用程序已读取的数据量。
IO多路复用的主要优势在于它可以允许单个进程或线程高效地监视多个套接字(或其他类型的文件描述符),而无需为每个套接字创建多个线程或进程。当系统中有大量并发网络连接时,这可以大大提高效率。
说人话
Redis为什么这么快?
Redis底层是Reactor反应模型【IO多路复用的思想体现】
select源码
缺点
POLL源码【1997年】
系统内核函数
两大优点:
如果有数据将对应的fd对应的revents置为POLLIN,遍历后又将其revents重新置为0,便于复用,使用数组替代bitmap。
poll使用pollfd数组替代select的bitmap
缺点
epoll源码【2002年】
1分为3