zoukankan      html  css  js  c++  java
  • Redis为什么这么快

    QA:Redis到底有多快?

    使用redis自带的benchmark脚本测试:

    cd /usr/local/soft/redis-6.0.9/src
    redis-benchmark -t set,lpush -n 100000 -q
    

    结果:
    SET: 136239.78 requests per second 每秒钟处理 13 万多次 set 请求
    LPUSH: 132626.00 requests per second 每秒钟处理 13 万多次 Ipush 请求

    redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"
    

    结果:

    script load redis.call('set','foo','bar'): 125628.14 requests per second---每秒钟125628.14次lua脚本调用

    从测试结果上看,说明Redis的QPS 10万还是比较准确的,在高性能的服务器上性能还能更强。

    Redis为什么这么快原理

    总结起来主要是三点:
    1、 纯内存结构
    2、 请求处理单线程
    3、 多路复用机制

    内存

      KV结构的内存数据库,时间复杂度O(1)。
      第二个,按照正常的思路来讲,要实现这么高的并发性能,是不是要创建非常多的线程?为什么我们说Redis是单线程的呢?这个单线程说的到底是什么?
    

    单线程

      这里说的单线程其实指的是处理客户端的请求是单线程的,可以把它叫做主线程。
      从4.0的版本之后,还引入了一些线程处理其他的事情,比如清理脏数据、无用连接的释放、大key的删除。
    

    把处理请求的主线程设置成单线程有什么好处呢?

    1、 没有创建线程、销毁线程带来的消耗
    2、 避免了上线文切换导致的CPU消耗
    3、 避免了线程之间带来的竞争问题,例如加锁释放锁死锁等等

    那么这里有一个问题,就算单线程确实有你说的这些好处,但是会不会白白浪费了 CPU 的资源吗?也就是说只能用到单核。

    官方的解释是这样的:
    在Redis中单线程已经够用了,CPU不是redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,又不需要处理线程并发的问题,那就顺理成章地采用单线程的方案了。

      注意,因为请求处理是单线程的,不要在生产环境运行长命令,比如keys, flushall, flushdb。否则会导致请求被阻塞。
    

    同步非阻塞I/O

    同步非阻塞I/O,多路复用处理并发连接——什么是多路复用?

    单线程为什么这么快?

    因为Redis是基于内存的操作。

    虚拟存储器(虚拟内存Virtual Memory)

    计算机里面的内存我们叫做主存,硬盘叫做辅存。
    主存可看作一个很长的数组,一个字节一个单元,每个字节有一个唯一的地址,这个地址叫做物理地址(Physical Address)。

    早期的计算机中,如果CPU需要内存,使用物理寻址,直接访问主存储器。

    但是这种方式有几个弊端:
    1、一般的操作系统都是多用户多任务的,所有的进程共享主存。如果每个进程都独占一块物理地址空间主存很快就会被用完。我们希望在不同的时刻,不同的进程可以共用同一块物理地址空间。
    2、如果所有进程都是直接访问物理内存,那么一个进程就可以修改其他进程的内存数据,导致物理地址空间被破坏,程序运行就会出现异常。
    咋办呢?对于物理内存的使用,应该有一个角色来协调和指挥。
    我们想了一个办法,在CPU和主存之间增加一个中间层。CPU不再使用物理地址访问主存,而是访问一个虚拟地址,由这个中间层把地址转换成物理地址,最终获得数据。 这个中间层叫做MMU (Memory Management Unit),内存管理单元。

    我们访问MMU就跟访问物理内存一样,所以把虚拟出来的地址叫做虚拟内存(Virtual Memory)。在每一个进程开始创建的时候,都会分配一段虚拟地址,然后通过虚拟地址和物理地址的映射来获取真实数据,这样进程就不会直接接触到物理地址,甚至不知道自己调用的哪块物理地址的数据。
    目前,大多数操作系统都使用了虚拟内存,如Windows系统的虚拟内存、Linux系统的交换空间等等。Windows的虚拟内存(pagefile.sys)是磁盘空间的一部分。
    在32位的系统上,虚拟地址空间大小是232=4G。在64位系统上,最大虚拟地址空间大小是多少?是不是264=1024*1024TB?实际上没有用到64位,因为用不到这么大的空间,而且会造成很大的系统开销。Linux—般用低48位来表示虚拟地址空间, 也就是2^48=256T。

    cat /proc/cpuinfo
    # address sizes : 43 bits physical, 48 bits virtual
    

    实际的物理内存可能远远小于虚拟内存的大小。
    总结:引入虚拟内存的作用:
    1、通过把同一块物理内存映射到不同的虚拟地址空间实现内存共享
    2、对物理内存进行隔离,不同的进程操作互不影响
    3、虚拟内存可以提供更大的地址空间,并且地址空间是连续的,使得程序编写、链接更加简单。
    Linux/GNU的虚拟内存又进一步划分成了两块:

    4. 3. 2用户空间和内核空间

    —部分是内核空间(Kernel-space), 一部分是用户空间。

    在Linux系统中,虚拟地址布局如下:

    这两块空间的区别是什么呢?
    进程的用户空间中存放的是用户程序的代码和数据,内核空间中存放的是内核代码和数据。不管是内核空间还是用户空间,它们都处于虚拟内存空间中,都是对物理地址的映射。
    当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。
    进程在内核空间可以访问受保护的内存空间,也可以访问底层硬件设备。也就是可以执行任意命令,调用系统的一切资源。在用户空间只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称system call),才能向内核发出指令。
    所以,这样划分的目的是为了避免用户进程直接操作内核,保证内核安全。

    进程切换(上下文切换)

    多任务操作系统是怎么实现运行远大于CPU数量的任务个数的?当然,这些任务实际上并不是真的在同时运行,而是因为系统通过时间片分片算法,在很短的时间内,将 CPU轮流分配给它们,造成多任务同时运行的错觉。
    在这个交替运行的过程里面,为了控制进程的执行,内核必须有能力挂起正在CPU 上运行的进程,以及恢复以前挂起的某个进程的执行。这种行为被称为进程切换。

    什么叫上下文(Context)?

    在每个任务运行前,CPU都需要知道任务从哪里加载、又从哪里开始运行。也就是说,需要系统事先帮它设置好CPU寄存器和程序计数器(Program Counter),这个叫做CPU的上下文。
    而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。
    在切换上下文的时候,需要完成一系列的工作,这是一个很消耗资源的操作。

    进程的阻塞

    正在运行的进程由于提出系统服务请求(如I/O操作),但因为某种原因未得到操作系统的立即响应,该进程只能把自己变成阻塞状态,等待相应的事件出现后才被唤醒。 进程在阻塞状态不占用CPU资源。

    文件描述符FD

    Linux系统将所有设备都当作文件来处理,而Linux用文件描述符来标识每个文件对象。文件描述符(File Descriptor)是内核为了高效管理已被打开的文件所创建的索引,用于指向被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。
    文件描述符是一个简单的非负整数,用以表明每个被进程打开的文件。Linux系统 里面有三个标准文件描述符。
    0:标准输入(键盘);1:标准输出(显示器);2:标准错误输出(显示器)。

    传统I/O数据拷贝

    以读操作为例:
    当应用程序执行read系统调用读取文件描述符(FD)的时候,如果这块数据已经存在于用户进程的页内存中,就直接从内存中读取数据。如果数据不存在,则先将数据从磁盘加载数据到内核缓冲区中,再从内核缓冲区拷贝到用户进程的页内存中。(两次 拷贝,两次user和kernel的上下文切换)。

    I/O的阻塞到底阻塞在哪里?

    Blocking I/O

    当使用read或write对某个文件描述符进行过读写时,如果当前FD不可读,系统就不会对其他的操作做出响应。从硬件设备复制数据到内核缓冲区是阻塞的,从内核缓冲区拷贝到用户空间,也是阻塞的,直到copy complete,内核返回结果,用户进程才
    解除block的状态。

    为了解决阻塞的问题,我们有几个思路。

    1、在服务端创建多个线程或者使用线程池--但是在高并发的情况下需要的线程会很多,系统无法承受,而且创建和释放线程都需要消耗资源。
    2、由请求方定期轮询,在数据准备完毕后再从内核缓存缓冲区复制数据到用户空间 (非阻塞式I/O),这种方式会存在一定的延迟。

    能不能用一个线程处理多个客户端请求?

    I/O 多路复用(I/O Multiplexing)

    I/O指的是网络I/O。
    多路指的是多个TCP连接(Socket或Channel)。
    复用指的是复用一个或多个线程。

    它的基本原理就是不再由应用程序自己监视连接,而是由内核替应用程序监视文件描述符。
    客户端在操作的时候,会产生具有不同事件类型的socket。在服务端,I/O多路复用程序(I/O Multiplexing Module)会把消息放入队列中,然后通过文件事件分派器(File event Dispatcher),转发到不同的事件处理器中。

    多路复用有很多的实现,以select为例,当用户进程调用了多路复用器,进程会被阻塞。内核会监视多路复用器负责的所有socket,当任何一个socket的数据准备好了,多路复用器就会返回。这时候用户进程再调用read操作,把数据从内核缓冲区拷贝到用
    户空间。

    所以,I/O多路复用的特点是通过一种机制让一个进程能同时等待多个文件描述符, 而这些文件描述符其中的任意一个进入读就绪(readable)状态,select()函数就可以返回。

    多路复用需要操作系统的支持。Redis的多路复用,提供了 select, epoll, evport, kqueue几种选择,在编译的时候来选择一种。源码ae.c

    /* Include the best multiplexing layer supported by this system. 
    * The following should be ordered by performances, descending. */ 
    #ifdef HAVE_EVPORT 
    #include "ae_evport.c" 
    #else 
        #ifdef HAVE_EPOLL 
        #include "ae_epoll.c" 
        #else 
         #ifdef HAVE_KQUEUE 
         #include "ae_kqueue.c" 
         #else 
         #include "ae_select.c" 
         #endif 
        #endif 
    #endif 
    

    evport是Solaris系统内核提供支持的;
    epoll是LINUX系统内核提供支持的;
    kqueue是Mac系统提供支持的;
    select是POSIX提供的,一般的操作系统都有支撑(保底方案);
    源码 ae_epoll.c、ae_select.c、ae_kqueue.c、ae_evport.c

    总结:

    Redis抽象了一套AE事件模型,将IO事件和时间事件融入一起,同时借助多路复用机制的回调特性(Linux上用epoll),使得IO读写都是非阻塞的,实现高性能的网络处理能力。
    我们一直在说的Redis新版本多线程的特性,意思并不是服务端接收客户端请求变成多线程的了,它还是单线程的。
    严格意义上来说,Redis从4.0之后就引入了多线程用来处理一些耗时长的工作和后台工作,那不然的话,如果真的只有一个线程,那些耗时的操作肯定会导致客户端请求被阻塞。我们这里说的多线程,确切地说,叫做多线程I/O。

    为什么要用多线程来处理I/O呢?

    多线程I/O

    回到多路复用的图:
    服务端的数据返回给客户端,需要从内核空间copy数据到用户空间,然后回写到 socket (write调用),这个过程是非常耗时的。所以多线程I/O指的就是把结果写到socket的这个环节是多线程的。处理请求依然是单线程的,所以不存在线程并发安全问
    题。

  • 相关阅读:
    关于解决Python中requests模块在PyCharm工具中导入问题
    arcgis javascript api 事件的监听及移除
    零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(代码篇)
    python 爬取全量百度POI
    Webpack安装使用总结
    开源协议之间的区别
    npm指令后缀
    nrm的作用(及安装)
    Vuejs中关于computed、methods、watch的区别
    VUE参考---watch、computed和methods之间的对比
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14347853.html
Copyright © 2011-2022 走看看