Redis网络模型

用户空间和内核空间

  • 服务器大多都采用Linux系统,任何Linux发行版,其系统内核都是Linux,比如ubuntu、CentOS等Linux的发行版,发行版可以看作是对Linux包了一层壳,我们的应用都需要通过Linux内核与硬件交互。
  • 用户的应用,比如Redis、MySQL等其实都是没有办法去执行访问我们操作系统的硬件的,所以我们需要通过发行版这个壳子去访问内核,再通过内核去访问计算机硬件
  • 计算机硬件包括:CPU、RAM内存、Network Adapter网卡等等内核通过寻址空间可以操作计算机硬件,但是内核需要不同设备的驱动,有了这些驱动之后,内核就可以去对计算机硬件去进行内存管理、文件系统的管理、进程管理、网络管理等等。
  • 内核本身上来说也是一个应用,所以它本身需要一些内存、CPU等设备资源,而用户应用本身也在消耗这些资源,如果不加任何限制,用户去随意的操作我们的设备资源,就有可能导致一些冲突,甚至有可能导致我们的系统无法运行的情况,所以为了避免用户应用导致冲突甚至内核崩溃,我们需要把用户应用和内核隔离开来。

进程的寻址空间划分为两部分:内核用户 & 用户空间

什么是寻址空间呢?

  • 不论是我们的应用程序也好,还是内核空间也好,都是没有办法直接去操作物理内存的,而是通过分配一些虚拟内存映射到物理内存中,我们的内核和应用程序去访问虚拟内存时,都需要一个虚拟地址,这个虚拟地址是一个无符号的整数,从0开始,它的最大值取决于我们CPU的地址总线和寄存器的带宽。
  • 比如一个32位的操作系统,它的带宽是32,它的虚拟地址就是2的32次方,也就是说它寻址的范围就是从0~2的32次方个字节(4GB)这么一段空间,这个空间就是所谓的寻址空间了。
  • 我们的内存地址它的每一个值代表的其实就是一个存储单元,也就是一个字节。

而进程的寻址空间又划分为两部分:内核空间和用户空间。

低位的3GB称为用户空间,高位的1GB称为内核空间:

  • 用户空间只能执行非特权指令,而且不能直接调用系统资源,必须通过内核提供的接口来访问内核空间从而来执行特权命令,调用一切系统资源,所以一般情况下,用户的操作是运行在用户空间,而内核运行的数据是在内核空间的,而有的情况下,一个应用程序需要去调用一些特权资源,去调用一些内核空间的操作,此时它就需要在用户态和内核态之间进行切换。 
  • 当一个进程运行在用户空间的时候,我们就把它称之为用户态;
  • 当一个进程运行在内核空间的时候,我们就把它称之为内核态。

Redis网络模型_第1张图片

Linux系统为了提高IO效率,会在用户空间和内核空间都加入缓冲区:

  • 写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入磁盘 / 设备
  • 读数据时,要从设备 / 磁盘读取数据到内核缓冲区,然后拷贝到用户缓冲区 

应用程序想要去读取数据,它是无法直接去读取磁盘数据的,而是需要先尝试从内核上加载数据,等到内核从磁盘上把数据加载出来之后,再把这个数据写给用户的缓冲区,即内核读取数据之后,会把数据拷贝到用户态,此时才完成用户数据的读取。 

Linux五种不同的IO模型

在《UNIX网络编程》一书中,总结归纳了5种IO模型:

  • 阻塞IO(Blocking IO)

  • 非阻塞IO(Nonblocking IO)

  • IO多路复用(IO Multiplexing)

  • 信号驱动IO(Signal Driven IO)

  • 异步IO(Asynchronous IO)

在Linux中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。

文件描述符(File Descriptor):简称FD,是一个从0 开始的无符号整数,用来关联Linux中的一个文件。  

用IO多路复用模型,可以确保去读数据的时候,数据是一定存在的,它的效率比原来的阻塞IO和非阻塞IO性能都要高!  

Redis网络模型

Redis到底是单线程还是多线程?

  • Redis的单线程指的是Redis的工作线程采用的是单线程,Redis的单线程指的是「接收客户端请求 -> 解析请求  -> 进行数据读写等操作 -> 发送数据给客户端」这个过程是由一个线程(主线程)来完成的,这也是我们常说 Redis 是单线程的原因。 

但是,Redis 程序并不是单线程的,Redis 在启动的时候,是会启动后台线程(BIO)的:

  • Redis 在 2.6 版本,会启动 2 个后台线程,分别处理关闭文件、AOF 刷盘这两个任务;
  • Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。例如执行 unlink key / flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key。

之所以 Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理,是因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了。

Redis 6.0之前为什么使用单线程?

  • 主要是因此,Redis的操作基本都是基于内存的,CPU 并不是制约 Redis 性能表现的瓶颈所在! 

Redis 6.0引入多线程

  • 在Redis 6.0中就有一个被说了很久的多线程IO  =>  Thread I/O,这个Thread I/O指的是在网络IO处理方面上了多线程,如网络数据的读写和协议解析等,需要注意的是,执行命令的核心模块还是单线程的(对于数据的读写或读写命令,Redis仍然使用单线程来处理)。 

Redis 6.0 之后为什么引入了多线程?为什么网络处理要引入多线程?

  • 因为一般来说Redis的瓶颈并不在CPU,而在内存和网络,内存不够的话,可以加内存或者做数据结构优化和其它优化等,但网络的性能优化才是大头,网络IO的读写在Redis整个执行期间占用了大部分的CPU时间,如果把网络处理这部分做成多线程处理方式,那对整个Redis的性能会有很大的提升,可以进一步提高对多核CPU的利用率。
  • 限制Redis的性能的主要瓶颈出现在网络IO的处理上,虽然之前采用了多路复用技术,但多路复用IO模型的本质上仍然是同步阻塞型IO模型  =>  在处理网络请求时,调用select函数的过程是阻塞的,也就是说这个过程会阻塞线程,如果并发量很高,此处可能会成为瓶颈。
  • Redis 官方表示,Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上。 

为什么Redis要选择单线程?

  • Redis是纯内存操作,执行速度非常快,它的性能瓶颈在于网络延迟而不是执行速度,因此多线程并不会带来巨大的性能提升
  • 多线程会导致过多的上下文切换,带来不必要的开销 => 使用单线程模型,无锁竞争,避免了频繁的上下文切换所带来的性能开销
  • 引入多线程会面临线程安全问题,必然要引入锁这样的安全手段,实现复杂度增高,而且性能也会大打折扣 

Redis网络模型

  • Redis通过IO多路复用来提高网络性能  =>  在提升I/O利用率这个方面上,Redis并没有使用多线程技术,而是选择了I/O多路复用技术。 

Redis通信协议

什么是通信协议? 

Redis是一个C-S架构的软件,通信一般分为两步:

  1. Client客户端向Server服务端发送一条命令
  2. 服务端解析并执行命令,返回响应给客户端

因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议。 

Redis使用什么协议进行通信?

  • Redis使用自己设计的一种文本协议进行客户端与服务端之间的通信 - RESPREdis Serialization Protocol),这种协议简单、高效、易于解析,被广泛使用。
  • RESP协议基于TCP协议,采用请求-响应模式,请求和响应都以行结束符(\r\n)作为分隔符。

你可能感兴趣的:(网络,redis,分布式,缓存,linux)