美文网首页
RPC(四)

RPC(四)

作者: supremecsp | 来源:发表于2022-06-06 14:38 被阅读0次

    网络通信是整个 RPC 调用流程的基础。
    简单描述下各种IO的区别:

    所有的 IO 都分为两个阶段:"等待数据就绪" 和 "拷贝到用户空间"。
    等待数据就绪:以 socket.read 为例,不管我们使用什么编程语言,这个操作都是去交给操作系统完成的(调用系统函数)。在执行系统函数时 CPU 向网卡发出 IO 请求。网卡在接收到数据之后,由网卡的 DMA 把的数据写到操作系统的内核缓冲区,完成后通知 CPU,之后 CPU 会将内核缓冲区的数据拷贝到用户空间
    拷贝到用户空间:操作系统有自己的内存区域,叫做内核空间。我们平常跑的程序都在用户空间,用户空间无法直接访问内核空间的数据,所以需要拷贝。拷贝到用户空间这个过程可以理解为纯 CPU 操作,非常地快,可以认为基本不耗时。

    当 IO 阻塞时,CPU 处于空闲状态。想象一下,你一条 SQL 发给数据库,这个时候必须等到数据库给你响应才能继续往下执行。而 IO 本身并不是时刻都需要 CPU 的参与(例如我们上面说的等待就绪过程不需要 CPU 参与)。虽然这个过程对于人类来说可能很快,但是 CPU 确实在摸鱼。
    BIO:请求后阻塞等结果返回,就不能很好的压榨CPU性能

    NIO
    相对于 BIO 来说,NIO 调用可以立刻得到反馈,不需要再傻等了。以 read 为例,如果数据已经就绪则返回给用户;反之返回 0,永远不会阻塞。
    NIO 特性貌似可以解决 BIO 的痛点,我们通过一个线程来监听所有的 socket,当 socket 就绪时,再进行读写操作,这样做可以吗?
    可以,但是需要不断的遍历所有的 socket,(也就是自己不阻塞,派另一个线程去不断的询问数据过来了吗?)这样做的话效率还是有点低,有更好的办法吗?

    IO 多路复用
    IO 多路复用可以在一个线程中监听多个文件描述符(Linux 中万物皆是文件描述符),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
    多个网络连接的 IO 可以注册到一个复用器(select)上,当用户进程调用了 select,那么整个进程会被阻塞。同时,内核会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从内核中拷贝到用户进程。
    这里我们可以看到,当用户进程发起了 select 调用,进程会被阻塞,当发现该 select 负责的 socket 有准备好的数据时才返回,之后才发起一次 read,整个流程要比阻塞 IO 要复杂,似乎也更浪费性能。但它最大的优势在于,用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 IO 请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
    同样好比我们去餐厅吃饭,这次我们是几个人一起去的,我们专门留了一个人在餐厅排号等位,其他人就去逛街了,等排号的朋友通知我们可以吃饭了,我们就直接去享用了。

    select poll epoll
    这三个函数都可以用于实现 IO 多路复用,简单来聊聊这三个函数
    select:当被监听的 fd(文件描述符)就绪后会返回,但是我们无法知道具体是哪些 fd 就绪了,只能遍历所有的 fd。通常来说某一时刻,就绪的 fd 并不会很多,但是使用 select 必须要遍历所有的 fd,这就造成了一定程度上的性能损失。select 最多可监听的 fd 是有限制的,32位操作系统默认1024个,64位默认2048
    poll:和 select 一样,使用 poll 时也无法知道具体哪些 fd 就绪了,还是需要遍历。poll 最大的改进就是没有了监听数量的限制,但是监听了过多的 fd 会导致性能不佳
    epoll:通常在 Linux 系统中使用 IO 多路复用,都是在使用 epoll 函数。epoll 是 select 和 poll 的增强,可以通知我们哪些 fd 已经就绪了,并且没有监听数量的限制。所以使用 epoll 的性能要远远优于 select 和 poll



    使用 IO 多路复用框架能带来什么
    IO 多路复用这么强,如果把业务开发全部改造成这种模型是不是性能会大幅度提升?实则不然,IO 多路复用的优势是使用更少的线程处理更多的连接,例如 Nginx,网关,这种可能需要处理海量连接转发的服务,它们就非常适合使用 IO 多路复用。IO 多路复用并不能让你的业务系统提速,但是它可以让你的系统支撑更多的连接。如果用BIO的话需要更多的线程才能实现这个效果,而线程过多带来的副作用就是占用大量内存,线程上下文切换占用大量 CPU 时间片等等。

    在编程语言上,无论 C++ 还是 Java,在高性能的网络编程框架的编写上,大多数都是基于 Reactor 模式,其中最为典型的便是 Java 的 Netty 框架,而 Reactor 模式是基于 IO 多路复用的。当然,在非高并发场景下,同步阻塞 IO 是最为常见的。 而如果每个等待的时间比较短暂,则直接使用多线程好一些。 但单线程有不用考虑锁竞争、线程上下文切换、代码容易维护的优势

    RPC 调用在大多数的情况下,是一个高并发调用的场景,考虑到系统内核的支持、编程语言的支持以及 IO 模型本身的特点,在 RPC 框架的实现中,在网络通信的处理上,我们会选择 IO 多路复用的方式。开发语言的网络通信框架的选型上,我们最优的选择是基于 Reactor 模式实现的框架,如 Java 语言,首选的框架便是 Netty 框架(Java 还有很多其他 NIO 框架,但目前 Netty 应用得最为广泛),并且在 Linux 环境下,也要开启 epoll 来提升系统性能(Windows 环境下是无法开启 epoll 的,因为系统内核不支持)。

    来源:聊聊 IO 多路复用网络通信:RPC框架在网络通信上更倾向于哪种网络IO模型?

    相关文章

      网友评论

          本文标题:RPC(四)

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