企业推广网站,app制作软件下载官网,wordpress 把账号名改成昵称,网站找百度做可以嘛1.概述
这里我们先给出问题的全面回答#xff1a;Redis到底是多线程还是单线程程序要看是针对哪个功能而言#xff0c;对于核心业务功能部分(命令操作处理数据)#xff0c;Redis是单线程的#xff0c;主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的#xff…1.概述
这里我们先给出问题的全面回答Redis到底是多线程还是单线程程序要看是针对哪个功能而言对于核心业务功能部分(命令操作处理数据)Redis是单线程的主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的这也是 Redis 对外提供键值存储服务的主要流程所以一般我们认为Redis是个单线程程序。但是从整个框架层面出发严格来说Redis是多线程的。
在Redis版本迭代过程中在两个重要的时间节点上引入了多线程的支持
Redis v4.0引入多线程异步处理一些耗时较旧的任务例如异步删除命令unlink异步持久化等等Redis v6.0在核心网络模型中引入多线程进一步提高对于多核CPU的利用率
2.Redis为什么采用单线程处理命令操作
Redis的大部分操作都在内存中完成的执行速度非常快所以它的性能瓶颈是网络延迟而不是执行速度因此多线程并不会带来巨大的性能提升。并且多线程会导致过多的上下文切换带来不必要的性能开销。
同时多线程编程模式面临共享资源并发访问控制问题。并发访问控制一直是多线程开发中的一个难点问题如果没有精细的设计比如说只是简单地采用一个粗粒度互斥锁就会出现不理想的结果即使增加了线程大部分线程也在等待获取访问共享资源的互斥锁并行变串行系统吞吐率并没有随着线程的增加而增加。而且采用多线程开发一般会引入同步原语来保护共享资源的并发访问这也会降低系统代码的易调试性和可维护性。为了避免这些问题Redis 直接采用了单线程模式。
3.Redis采用单线程为什么还那么快
通常来说单线程的处理能力要比多线程差很多但是 Redis 却能使用单线程模型达到每秒数十万级别的处理能力这是为什么呢其实这是 Redis 多方面设计选择的一个综合结果。一方面Redis 的大部分操作在内存上完成再加上它采用了高效的数据结构例如哈希表和跳表这是它实现高性能的一个重要原因。另一方面就是 Redis 采用了多路复用机制使其在网络 IO 操作中能并发处理大量的客户端请求实现高吞吐率这也是Redis单线程如何处理那么多的并发客户端连接的核心所在。
在讲Redis网络模型之前先来看看应用是怎么和系统硬件进行交互的用户应用如RedisMySQL等其实是没有办法直接访问我们操作系统硬件的只能先访问内核linux再通过内核去访问计算机硬件。计算机硬件包括cpu内存网卡等等内核通过寻址空间可以操作硬件的但是内核需要不同设备的驱动有了这些驱动之后内核就可以去对计算机硬件去进行内存管理文件系统的管理进程的管理等等。
我们想要用户的应用来访问计算机就必须要通过对外暴露的一些接口才能访问到从而简单的实现对内核的操控但是内核本身上来说也是一个应用所以他本身也需要一些内存cpu等设备资源用户应用本身也在消耗这些资源如果不加任何限制用户去操作随意的去操作我们的资源就有可能导致一些冲突甚至有可能导致我们的系统出现无法运行的问题因此我们需要把用户和内核隔离开
进程的寻址空间划分成两部分内核空间、用户空间
Linux系统为了提高IO效率会在用户空间和内核空间都加入缓冲区
写数据时要把用户缓冲数据拷贝到内核缓冲区然后写入设备
读数据时要从设备读取数据到内核缓冲区然后拷贝到用户缓冲区
针对这个操作我们的用户在读读数据时会去向内核态申请想要读取内核的数据而内核数据要去等待驱动程序从硬件上读取数据当从磁盘上加载到数据之后内核会将数据写入到内核的缓冲区中然后再将数据拷贝到用户态的buffer中然后再返回给应用程序整体而言速度慢我们希望read也好还是wait for data也最好都不要等待或者时间尽量的短。 当我们发送请求调用网络套接字socket的读写方法默认它们是阻塞的比如 read 方法要传递进去一个参数n表示读取这么多字节后再返回如果没有读够线程就会卡在那里直到新的数据到来或者连接关闭了read 方法才可以返回线程才能继续处理。而 write 方法一般来说不会阻塞除非内核为套接字分配的写缓冲区已经满了write 方法就会阻塞直到缓存区中有空闲空间挪出来了。这就导致 Redis 整个线程阻塞无法处理其他客户端请求效率很低。不过幸运的是socket 网络模型本身支持非阻塞模式这时候IO多路复用事件驱动机制就闪亮登场了。
在单线程情况下依次处理IO事件如果正在处理的IO事件恰好未就绪数据不可读或不可写线程就会被阻塞所有IO事件都必须等待性能自然会很差。这里先来举个生动形象的例子来便于大家理解众多客户端访问Redis服务就好比去一家餐厅吃饭柜台就一个服务员点餐每个顾客都要想一下吃什么(等待数据就绪)想好之后开始点餐(读取数据)这个过程后面的顾客(客户端)即使想好了点什么也只能白白干等着可见这种方式能快起来吗所以采取了另一种方式进餐厅告诉想吃饭不用排队谁想好了吃什么数据就绪了就通知服务员给谁点餐用户应用就去读取数据。这样就有效提高了资源利用率。
本质来说事件驱动是一种思想事实上它不仅仅局限于编程 事件驱动思想是实现 异步非阻塞特性 的一个重要手段。对于web服务器来说造成性能拉胯不支持高并发的常见原因就是由于使用了传统的I/O模型造成在内核没有可读/可写事件或者说没有数据可供用户进程读写时用户线程 一直在等待其他事情啥也干不了就是干等等待内核上的数据可读/可写这样的话其实是一个线程ps:线程在Linux系统也是进程对应一个请求请求是无限的而线程是有限的从而也就形成了并发瓶颈。而大佬们为了解决此类问题运用了事件驱动思想来对传统I/O模型做个改造即在客户端发起请求后用户线程不再阻塞等待内核数据就绪而是立即返回可以去执行其他业务逻辑或者继续处理其他请求。当内核的I/O操作完成后内核系统会向用户线程发送一个事件通知用户线程才来处理这个读/写操作之后拿到数据再做些其他业务后响应给客户端从而完成一次客户端请求的处理。事件驱动的I/O模型中程序不必阻塞等待I/O操作的完成也无需为每个请求创建一个线程从而提高了系统的并发处理能力和响应速度。事件驱动型的I/O模型通常也被被称为I/O多路复用即这种模型可以在一个线程中处理多个连接复用就是指多个连接复用一个线程多路也即所谓的 多个连接通过这种方式避免了线程间切换的开销同时也使得用户线程不再被阻塞提高了系统的性能和可靠性。Redis支持事件驱动是因为他利用了操作系统提供的I/O多路复用接口如Linux系统中常用的I/O多路复用接口有select/pollepoll。这些接口可以监视多个文件描述符FD的状态变化当文件描述符可读或可写时就会向用户线程发送一个事件通知。用户线程通过事件处理机制读取/写入数据来处理这个事件之后进行对应的业务逻辑完了进行响应。简单一句话概括 事件驱动机制就是指当有读/写/连接事件就绪时 再去做读/写/接受连接这些事情而不是一直在那里傻傻的等也正应了他的名词 【事件驱动】基于事件驱动思想设计的多路复用I/O如select/pollepoll相对于传统I/O模型达到了异步非阻塞的效果 linux采用的epoll机制接下来就让我们详细看看。
文件描述符File Descriptor简称FD是一个从0 开始的无符号整数用来关联Linux中的一个文件。在Linux中一切皆文件例如常规文件、视频、硬件设备等当然也包括网络套接字Socket。
**IO多路复用**是利用单个线程来同时监听多个FD并在某个FD可读、可写时得到通知从而避免无效的等待充分利用CPU资源。
epoll 是 Linux 上高效的多路复用机制它与传统的 select 和 poll 相比有更好的性能。 注册事件 程序通过 epoll_create 创建一个 epoll 对象然后使用 epoll_ctl 向其中注册需要监视的文件描述符和关注的事件如读或写事件。 int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);等待事件 使用 epoll_wait 等待文件描述符上的事件发生。epoll_wait 会阻塞直到注册的文件描述符中的事件发生或者超时。 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);处理事件 当 epoll_wait 返回时程序可以迭代处理发生的事件执行相应的读或写操作。 for (int i 0; i nfds; i) {if (events[i].events EPOLLIN) {// 处理读事件}if (events[i].events EPOLLOUT) {// 处理写事件}
}epoll 采用了事件通知的机制只有真正发生事件时才进行处理避免了轮询的开销因此在处理大量并发连接时性能更好。
总的来说多路复用通过允许单一进程或线程同时监视多个文件描述符提高了 I/O 操作的效率特别适用于高并发的网络应用场景 select模式存在的三个问题能监听的FD最大不超过1024。每次select都需要把所有要监听的FD都拷贝到内核空间每次都要遍历所有FD来判断就绪状态。
poll模式的问题poll利用链表解决了select中监听FD上限的问题但依然要遍历所有FD如果监听较多性能会下降epoll模式中如何解决这些问题的
基于epoll实例中的红黑树保存要监听的FD理论上无上限而且增删改查效率都非常高每个FD只需要执行一次epoll_ctl添加到红黑树以后每次epol_wait无需传递任何参数无需重复拷贝FD到内核空间利用ep_poll_callback机制来监听FD状态无需遍历所有FD因此性能不会随监听的FD数量增多而下降。
下面就来看看Redis基于IO多路复用事件驱动机制实现的网络模型 当我们的客户端想要去连接我们服务器会去先到IO多路复用模型去进行排队会有一个连接应答处理器他会去接受读请求然后又把读请求注册到具体模型中去此时这些建立起来的连接如果是客户端请求处理器去进行执行命令时他会去把数据读取出来然后把数据放入到client中 clinet去解析当前的命令转化为redis认识的命令接下来就开始处理这些命令从redis中的command中找到这些命令然后就真正的去操作对应的数据了当数据操作完成后会去找到命令回复处理器再由他将数据写出。大体流程如下
1.Redis服务启动之后就会创建一个server Socket服务器套接字得到对应文件描述符FD调用epoll机制进行注册监听
2.客户端client进行连接服务器套接字的FD会进入就绪进行回调事件tcpAccepthandler处理调用accept()接收客户端socket得到对应FD进行注册监听。
3.当客户端socket FD就绪会调用相应的可读readQueryFromClient读取请求数据或者可写事件sendReplyToClient写出相应数据。 项目推荐基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装解决业务开发时常见的非功能性需求防止重复造轮子方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化做到可插拔。严格控制包依赖和统一版本管理做到最少化依赖。注重代码规范和注释非常适合个人学习和企业使用 Github地址https://github.com/plasticene/plasticene-boot-starter-parent Gitee地址https://gitee.com/plasticene3/plasticene-boot-starter-parent 微信公众号Shepherd进阶笔记 交流探讨qunShepherd_126 4.Redis6.0为什么采用了多线程
上文说的单线程指的是从网络 IO 处理到实际的读写命令处理都是由单个线程完成的。
随着网络硬件的性能提升Redis 的性能瓶颈有时会出现在网络 IO 的处理上也就是说单个主线程处理网络请求的速度跟不上底层网络硬件的速度。采用多线程 I/O 可以让 Redis 在一个单一进程内同时处理多个客户端的请求充分利用多核处理器的优势提高系统的并发性能提升系统吞吐量。
Redis 的多线程 I/O类似于一个餐厅只有一名服务员负责接受客人点餐处理命令的执行而多名厨师则负责烹饪和准备食物处理 I/O 操作如读取和写入。在这个场景中服务员负责与客人直接交互接受点餐信息这相当于 Redis 的主线程负责处理命令。而厨师在后厨专注于将点餐信息转化为实际的菜品这就类似于 Redis 的工作线程专注于处理 I/O 操作如读取和写入数据。
我们来看下在 Redis 6.0 中主线程和 IO 线程具体是怎么协作完成请求处理的。掌握了具体原理你才能真正地会用多线程。为了方便你理解我们可以把主线程和多 IO 线程的协作分成四个阶段。
阶段一服务端和客户端建立 Socket 连接并分配处理线程
首先主线程负责接收建立连接请求。当有客户端请求和实例建立 Socket 连接时主线程会创建和客户端的连接并把 Socket 放入全局等待队列中。紧接着主线程通过轮询方法把 Socket 连接分配给 IO 线程。
阶段二IO 线程读取并解析请求主线程一旦把 Socket 分配给 IO 线程就会进入阻塞状态等待 IO 线程完成客户端请求读取和解析。因为有多个 IO 线程在并行处理所以这个过程很快就可以完成。
阶段三主线程执行请求操作等到 IO 线程解析完请求主线程还是会以单线程的方式执行这些命令操作。下面这张图显示了刚才介绍的这三个阶段你可以看下加深理解。 阶段四IO 线程回写 Socket 和主线程清空全局队列当主线程执行完请求操作后会把需要返回的结果写入缓冲区然后主线程会阻塞等待 IO 线程把这些结果回写到 Socket 中并返回给客户端。和 IO 线程读取和解析请求一样IO 线程回写 Socket 时也是有多个线程在并发执行所以回写 Socket 的速度也很快。等到 IO 线程回写 Socket 完毕主线程会清空全局队列等待客户端的后续请求。我也画了一张图展示了这个阶段主线程和 IO 线程的操作你可以看下。 了解了 Redis 主线程和多线程的协作方式我们该怎么启用多线程呢在 Redis 6.0 中多线程机制默认是关闭的如果需要使用多线程功能需要在 redis.conf 中完成两个设置。
设置 io-threads-do-reads 配置项为 yes表示启用多线程。
io-threads-do-reads yes2.设置线程个数。一般来说线程个数要小于 Redis 实例所在机器的 CPU 核个数例如对于一个 8 核的机器来说Redis 官方建议配置 6 个 IO 线程。
io-threads 6如果你在实际应用中发现 Redis 实例的 CPU 开销不大吞吐量却没有提升可以考虑使用 Redis 6.0 的多线程机制加速网络处理进而提升实例的吞吐量。
5.总结
Redis 的网络模型主要使用 epoll在 Linux 上作为事件通知机制并且在版本 6.0 中引入了多线程 I/O 模型。
epoll
设计目标 主要用于实现单线程的事件驱动模型处理大量的并发连接。机制 Redis 使用 epoll 机制通过单个线程监听多个文件描述符上的事件实现非阻塞 I/O。当某个连接有数据到达时通过事件通知机制触发相应的处理。适用场景 适用于 I/O 密集型的场景其中大量的连接需要同时被高效处理例如网络通信密集型的应用场景。
多线程 I/O
设计目标 引入多线程用于更好地利用多核处理器提高系统的并发性能。机制 Redis 6.0 引入了多线程 I/O 模型其中一个线程负责处理命令的执行而其他的工作线程则负责处理 I/O 操作如读取和写入。这样可以同时处理多个连接的 I/O 操作。适用场景 适用于需要更好地利用多核处理器、同时处理大量 I/O 操作的场景。特别在 CPU 密集型的情况下通过多线程可以提高性能。
总体来说epoll负责从网络接收数据到内核多线程io从epoll记录的socket中将数据从内核读取到用户态