美文网首页
3:高性能IO模型:为什么单线程Redis能那么快?

3:高性能IO模型:为什么单线程Redis能那么快?

作者: _River_ | 来源:发表于2021-04-27 12:29 被阅读0次
    1:Redis单线程的概念?
    Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。
    但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
    
    2:Redis使用单线程和多线程对比?
    先看一下多线程的作用:
    在有合理的资源分配的情况下, 可以增加系统中处理同时多个请求操作的资源实体,
    进而提升系统能够同时处理的请求数,即吞吐率。
    
    但如果没有一个良好的设计,实际上会导致进一步新增线程时,系统吞吐率就增长缓慢了。
    
    原因主要归结于:多线性对共同资源执行操作时,需要解决并发的问题, 
    比如说加锁,这也就导致了实际上多线程也会变成串行执行。
    
    3:Redis使用单线程为什么这么快?
    第一:Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。
    第二:就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。
    
    以一个SimpleKV(简陋版本的Redis)为例子:
    
    以 Get 请求为例,SimpleKV 为了处理一个 Get 请求,
        1:需要监听客户端请求(bind/listen),
        2:客户端建立连接(accept),
        3:从 socket 中读取请求(recv),
        4:解析客户端发送请求(parse),
        5:根据请求类型读取键值数据(get),
        6:最后给客户端返回结果,即向 socket 中写回数据(send)。
    
     由于在这里的网络 IO 操作中,有潜在的阻塞点,分别是 accept() 和 recv()。
     
     1:当 Redis 监听到一个客户端有连接请求,但一直未能成功建立起连接时,
          会阻塞在 accept() 函数这里,导致其他客户端无法和 Redis 建立连接。
     
     2:当 Redis 通过 recv() 从一个客户端读取数据时,如果数据一直没有到达,
         Redis 也会一直阻塞在 recv()。
    
    4:网络IO操作的解决方案(Socket的NIO)
    Socket 网络模型的非阻塞模式设置,主要体现在三个关键的函数调用上。
    
    三个函数的调用返回类型和设置模式概念
    
    在 socket 模型中,不同操作调用后会返回不同的套接字类型。
    
    1:socket() 方法会返回主动套接字(生成主动套字节)
    2:然调用 listen() 方法,将主动套接字转化为监听套接字,
        此时,可以监听来自客户端的连接请求。
    3:调用 accept() 方法接收到达的客户端连接,并返回已连接套接字。
    
    理解:
            一个线程可以生成多个套接字   下面实际上只有套接字A 与套接字B
            一个线程生成的套接字会经过 请求事件的转换  分别经历下面的阶段
            主动套接字(A)  监听套接字(A)  已连接套接字(A)
            主动套接字(B)  监听套接字(B)  已连接套接字(B)
    
    针对监听套接字,我们可以设置非阻塞模式:
    当 Redis 调用 accept() 但一直未有连接请求到达时,
    Redis 线程可以返回处理其他操作,而不用一直等待。
    
    特别注意:
        调用 accept() 时,已经存在监听套接字了。    
        因为虽然 Redis 线程可以不用继续等待,
        但是总得有机制继续在监听  监听套接字  等待后续连接请求并在有请求时通知 Redis。
        
        调用 recv() 后,如果已连接套接字上一直没有数据到达,
        Redis 线程同样可以返回处理其他操作。
        我们也需要有机制继续监听该  已连接套接字 并在有数据达到时通知 Redis。
             
     这样才能保证 Redis 线程,既不会像基本 IO 模型中一直在阻塞点等待,
     也不会导致 Redis 无法处理实际到达的连接请求或数据。   
    
    5:基于多路复用的高性能 I/O 模型
    Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。
    简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。
    
    内核会一直监听这些套接字上的连接请求或数据请求。
    一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。
        
    图中的多个 FD 就是刚才所说的多个套接字。
    Redis 网络框架调用 epoll 机制,让内核监听这些套接字。(由于sokcet产生的套接字)
    总点:
            当这些套接字发生事件之后存入队列中 同时有可能该套接字的本身会变化类型
    
    Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,
    正因为此,可以同时和多个客户端连接并处理请求,从而提升并发性。
    
    为了在请求到达时能通知到 Redis 线程(通过回调机制重新通知Redis)
    select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。
    
    那么,回调机制是怎么工作的呢?
    当select/epoll 监听到事件之后 这些事件会被放进一个事件队列;
    
    Redis单线程 无需一直轮询是否有请求实际发生,只需要对该事件队列不断进行处理。
    这就可以避免造成 CPU 资源浪费。
    
    然后Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,
    这就实现了基于事件的回调,能及时响应客户端请求,提升 Redis 的响应性能。
    
    6:基于多路复用的高性能 I/O 模型 小总结
    关键点在于accpet和recv时可能会阻塞线程
    
    1:使用IO多路复用技术可以让线程先处理其他事情,等需要accpet资源或者recv资源到位
    2:Redis运行在其内核中的epoll会去监听多个套接字
    2:epoll会调用回调函数通知线程,然后线程再去处理存/取数据;
    
    redis的单线程既生成socket(主动套接字) 又处理 事件处理队列
    redis的内核使用 epoll_wait 来进行套接字的监听 当事件发生时把事件存入 事件处理队列
    
    6: 简单介绍下select poll epoll的区别
    select和poll本质上没啥区别,就是文件描述符数量的限制,select根据不同的系统,文件描述符限制为1024或者2048,poll没有数量限制。
    他两都是把文件描述符集合保存在用户态,每次把集合传入内核态,内核态返回ready的文件描述符。
    
    epoll是通过epoll_create和epoll_ctl和epoll_await三个系统调用完成的,
    每当接入一个文件描述符,通过ctl添加到内核维护的红黑树中,通过事件机制,
    当数据ready后,从红黑树移动到链表,通过await获取链表中准备好数据的fd,程序去处理。

    相关文章

      网友评论

          本文标题:3:高性能IO模型:为什么单线程Redis能那么快?

          本文链接:https://www.haomeiwen.com/subject/kkvurltx.html