用户空间和内核空间
- 服务器大多都采用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称为内核空间:
- 用户空间只能执行非特权指令,而且不能直接调用系统资源,必须通过内核提供的接口来访问内核空间,从而来执行特权命令,调用一切系统资源,所以一般情况下,用户的操作是运行在用户空间,而内核运行的数据是在内核空间的,而有的情况下,一个应用程序需要去调用一些特权资源,去调用一些内核空间的操作,此时它就需要在用户态和内核态之间进行切换。?
- 当一个进程运行在用户空间的时候,我们就把它称之为用户态;
- 当一个进程运行在内核空间的时候,我们就把它称之为内核态。
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架构的软件,通信一般分为两步:
- Client客户端向Server服务端发送一条命令
- 服务端解析并执行命令,返回响应给客户端
因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议。?
Redis使用什么协议进行通信?
- Redis使用自己设计的一种文本协议进行客户端与服务端之间的通信 - RESP(REdis Serialization Protocol),这种协议简单、高效、易于解析,被广泛使用。
- RESP协议基于TCP协议,采用请求-响应模式,请求和响应都以行结束符(\r\n)作为分隔符。