首先我们需要知道影响Redis性能的因素有哪些?
官网原话说到,影响Redis性能的主要瓶颈并不是CPU而是内存和网络IO,而内存是可以从硬件角度优化的,所以
优化的关键就是在网络IO
在Redis6/7中,非常受关注的一个新特性就是多线程,这是因为,Redis一致被大家熟知的就是它的单线程架构,虽然有些命令操作可以用后台线程或子线程执行(比如数据删除、快照生成、AOF重写)。但是,从网络IO处理到实际的读写命令处理,都是单个线程完成的。随着网络硬件的性能提升,Redis的性能瓶颈有时会出现在网络IO上,也就是说,单个线程处理网络请求的速度跟不上底层网路硬件的速度。
为了应对上面的问题。采用多个IO线程来处理网络请求,提高网络请求处理的并行度,Redis6/7就是采用的这种方法
但是Redis的IO多线程只是用来处理网络请求的,对于读写操作命令Redis仍然采用的是单线程来处理。这是因为,Redis在处理请求时,网络处理经常时瓶颈,通过多个IO线程并行处理网络操作,可以提升实例的真整体处理性能。而继续使用单线程命令操作,就不用为了保证lua脚本、事务原子性,额外开发多线程互斥加锁机制了,,这样依赖,Redis线程模型实现就简单了。
主线程和IO线程是怎么完成请求处理的?
- 阶段一:服务端和客户端建立Socket连接
首先主线程复制接受建立连接请求。当有客户端请求和实例建立Socket连接时,主线程会创建和客户端的连接,并把Socket放入全局等待队列中,紧接着,主线程通过轮询方法把Socket连接分配给IO线程。
- 阶段二:IO线程读取并解析请求
主线程一旦把Socket分配给IO线程,就会进入阻塞状态,等待IO线程完成客户端请求读取和解析,因为有多个IO线程在并行处理,所以这个过程很快
- 阶段三:主线程执行请求操作
等到IO线程解析完请求,主线程还是会以单线程的方式执行这些命令
- 阶段四:IO线程会写Socket和主线程清空全局队列
当主线程执行完请求操作后,会把需要返回的结果写入缓存区,然后,主线程会阻塞IO线程,把这些结果写会到Socket中,并返回给客户端,和IO线程读取和解析请求一样,IO线程回写Socket时,也是有多个线程在并发执行,所以回写Socket的速度也很快,等到IO线程回写Socket完毕,主线程回清空全局队列,等待客户端后序请求。
那么这些IO线程到底是什么?
Unix网络编程中五种IO模型:
- Blocking IO:阻塞IO
- NoneBlikong IO:非阻塞IO
- IO Multiplexing:IO多路复用
- singnal driven IO:信号驱动IO
- asynchronous IO:异步IO
上面五种IO模型中,IO多路复用对于Redis来说是最重要的。在Linux世界中一切皆文件,在这之前先了解一下文件描述符(FileDescriptor,句柄):
文件描述符是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象概念。文件描述符在形式上是一个非负整数。实际上,他是一个索引值,指向内核为每一个进程所维护的该进程打开的记录表。当程序打开一个现有文件或者创建一个新文件的时候,内核想进程返回一个文件描述符。在程序设计中,文件描述符这一概念往往只适合于UNIX、Linux这样的系统。
什么是IO多路复用?
IO多路复用指定是一种同步的IO模型,实现一个线程监视多个文件句柄(文件描述符),一旦某个文件句柄准备就绪就能够通知到对应的应用程序进行相应的读写操作,没有文件句柄就会阻塞应用程序,从而释放CPU资源。其概念介绍如下:
I/O:网络IO,尤其在操作系统层面指数据在内核态和用户态之间的读写操作
多路:多个客户端连接
复用:复用一个活多个线程
IO多路复用:也就是说一个或一组线程处理多个TCP连接,使用单进程就能同时处理多个客户端连接,无需创建或者维护过多的进程/线程
一句话来说就是一个服务端进程可以同时处理多个套接字描述符,实现IO多路复用的模型有三种,可以分为select->poll->epoll三个阶段来描述。
每个客户端通过Socket连接到Redis服务器时,Redis服务器都会给客户端一个文件描述符(FD)。
将用户scoket对应的文件描述符注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量无用的操作,此时Socket应该采用非阻塞模式。这样,整个过程只在调用select、Poll、epoll这些调用时才会阻塞,收发客户消息时不会阻塞的,整个过程线程就被充分利用起来,这就是事件驱动的,所谓的reactor反应模式。
在单个线程通过记录跟踪每一个Socket的状态来同时管理多个IO流,一个服务端进程可以同时处理多个套接字描述符。目的是尽可能提高服务器的吞吐能力。
大家都用过nginx,nginx使用epoll接收请求,ngnix回有很多链接进来,epoll会把它们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理,redis类似同理,这就是IO多路复用原理,有请求就响应,没请求不打扰。
现在回答redis为什么这么快?
IO多路复用—+epoll函数的使用,才是redis为什么这么快的原理,而不是简单单线程命令+redis安装在内存中。