c++linux网络编程八股复习

代码

1.select服务端编写完成连接客户端接收消息

2.将select服务端的代码改成poll代码

八股

1.阻塞非阻塞I/O,同步异步I/O

        一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪”和“数据读写”( 数据从内核态拷贝到用户态 )。

        阻塞 I/O: 当用户程序执行 read ,线程会被阻塞,程序不能往下执行。一直等到内核数据准备好,并把数据从内核缓冲区拷贝到应用程序的缓冲区中,当拷贝过程完成,read 才会返回。 阻塞等待的是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程

        非阻塞IO:是在 I/O 操作时不会阻塞程序的执行。发起 I/O 操作后,如果操作无法立即完成,系统会返回一个错误或状态,程序可以继续往下执行,此时应用程序不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲区,read 调用才可以获取到结果。非阻塞IO没有等待内核数据准备的过程但是等待了数据从内核态拷贝到用户态的过程。

  • 特征

    • I/O 操作不会阻塞程序,可以立刻返回。
    • 如果 I/O 操作无法完成,程序可以继续做其他事情,或者稍后再试。
    • 程序需要轮询检查 I/O 是否完成。

同步IO:

        在同步操作中,调用者发起一个请求后,需要等待被调用者处理完毕并返回结果,期间调用者不能进行其他操作。

        无论是阻塞 I/O、非阻塞 I/O,还是基于非阻塞 I/O 的多路复用都是同步调用。因为它们在 read 调用时,内核将数据从内核空间拷贝到应用程序空间,过程都是需要等待的,也就是说这个过程是同步的,如果内核实现的拷贝效率不高,read 调用就会在这个同步过程中等待比较长的时间。

异步IO:异步 I/O 是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程都不用等待。例如:当程序调用aio_read 之后,就立即返回,内核自动将数据从内核空间拷贝到应用程序空间。

2.五种IO模型

  • 阻塞 I/O(Blocking I/O)
  • 非阻塞 I/O(Non-blocking I/O)
  • I/O 多路复用(I/O Multiplexing)

        I/O 多路复用 用户可以在一个线程内同时处理多个 socket 的 IO 请求。允许程序同时监视多个 I/O 通道,并在一个或多个 I/O 操作准备好时通知程序。这可以在单线程中同时处理多个 I/O 操作,避免阻塞。

  • 信号驱动 I/O(Signal-driven I/O)

        信号驱动 I/O 是一种较少使用的 I/O 模型,在该模型下,程序通过信号来获取 I/O 操作的状态。当 I/O 内核数据准备好时,操作系统会向程序发送一个信号,程序收到信号后才会去执行 I/O 操作调用相关IO函数。

  • 特征:程序不主动检查 I/O 状态,而是通过信号通知进行 I/O 操作。
  • 异步 I/O(Asynchronous I/O)

3.select poll epoll?

  • select

  select() 是一种 I/O 多路复用 技术,用于监视多个文件描述符(通常是网络套接字)的状态,以便程序可以在一个线程中处理多个 I/O 操作。 

  fd_set 是一个位图,用于保存多个文件描述符。select() 函数通过它来传递需要监听的文件描述符集合。在使用 select() 之前,必须初始化 fd_set,并在调用后检查哪些文件描述符已经准备好。

     select 实现多路复用的方式是,将已连接的 Socket 都放到fd_set ,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核通过遍历文件描述符集合来检查是否有网络事件产生,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

缺点:

  • 由于需要遍历所有文件描述符,select() 的性能在处理大量连接时会降低,尤其是当监视的文件描述符数量非常多时。
  • select() 的最大文件描述符数是有限制的(通常为 1024),如果需要处理更多连接时,会遇到限制。
  • poll

poll是为了克服select的限制而引入的一种I/O多路复用技术。poll使用一个文件描述符数组(通常是一个结构体数组)来表示要监视的文件描述符。与select类似,poll可以监视多个文件描述符的I/O状态。

poll的优点如下:

  • 文件描述符数量不受限制:由于poll使用一个动态数组来表示文件描述符,因此它可以处理任意数量的文件描述符。
  • 效率相对较高:poll在查找就绪的文件描述符时,只需要遍历实际使用的文件描述符数组,而不是整个文件描述符集合。

然而,poll仍然存在一些问题:

  • 效率问题:尽管poll相对于select具有较高的效率,但当文件描述符数量很大时,它仍然需要遍历整个文件描述符数组。
  • 非实时性:与select类似,每次调用poll时,需要重新设置文件描述符数组。
  • epoll

int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

eventfd与epoll

​**0**初始计数器值。eventfd 内部维护一个 64 位无符号整数计数器,初始值为 0。
​**EFD_NONBLOCK**将文件描述符设为 ​非阻塞模式。读写操作不会阻塞当前线程(直接返回 EAGAIN 错误,而不是等待)。
​**EFD_CLOEXEC**设置 ​close-on-exec 标志。确保该文件描述符在 exec() 家族函数执行时自动关闭,避免泄漏到子进程。

epoll的三个接口

int epoll_create(int size);
参数

  • size:这个参数在早期的 Linux 版本中用来告诉内核这个 epoll 实例预计要监视多少个文件描述符。现在这个参数不再有意义(内核会动态地管理),但为了向后兼容,这个参数必须大于 0。

返回值

  • 成功:返回一个非负的文件描述符,代表 epoll 实例。
  • 失败:返回 -1 并设置 errno。

int epoll_wait(int epfd, struct epoll_event events, int maxevents, int timeout);
参数

  • epfd: 通过 epoll_create 创建的 epoll 实例的文件描述符。
  • events: 是一个指向 struct epoll_event 数组的指针,该数组用于从内核接收发生的事件。
  • maxevents: 告诉内核这个事件数组可以接受的最大事件数,也是 epoll_wait 可以返回的最大事件数。
  • timeout: 等待 I/O 事件发生的最大毫秒数。特殊值为 -1 表示无限等待,0 表示立即返回,即使没有事件发生。

返回值

  • 成功: 返回准备就绪的文件描述符的数量。
  • 失败: 返回 -1 并设置 errno。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event event);
参数

  • epfd: 通过 epoll_create 创建的 epoll 文件描述符。
  • op: 操作类型,决定对 epoll 实例的操作。
    常用的操作包括:
    EPOLL_CTL_ADD:向 epoll 实例注册新的文件描述符及其关联的事件。
    EPOLL_CTL_MOD:修改已注册文件描述符的监听事件。
    EPOLL_CTL_DEL:从 epoll 实例中删除文件描述符及其事件。
  • fd: 关联到特定事件的文件描述符。
  • event: 指向 epoll_event 结构的指针,该结构描述了哪些事件将被关联到文件描述符 fd上。当操作是 EPOLL_CTL_DEL 时,此参数可以被设为 NULL。

返回值

  • 成功: 返回 0。
  • 失败: 返回 -1 并设置 errno。

事件:

  • EPOLLIN:表示当该 socket 有可读数据时,内核会通知用户。
  • EPOLLOUT:表示当该 socket 可以写入数据时,内核会通知用户。
  • EPOLLERR:表示当 socket 发生错误时,内核会通知用户。

epoll的原理

        第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn)。epoll 只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。select/poll 内核里没有类似 epoll 红黑树这种保存所有待检测的 socket 的数据结构,所以 select/poll 每次操作时都传入整个 socket 集合给内核。

        红黑树记录的是 文件描述符 以及与之关联的 事件,而不仅仅是事件本身。具体来说,红黑树节点包含以下信息:

  1. 文件描述符(fd):每个注册到 epoll 实例中的文件描述符(如 socket)会在红黑树中有一个对应的节点。
  2. 事件(events):每个文件描述符会与一个或多个事件(如 EPOLLINEPOLLOUT 等)关联,表示对该文件描述符感兴趣的 I/O 事件。

就绪队列

  1. 就绪队列(ready list)用于存储已经准备好进行 I/O 操作的文件描述符。也就是说,当某个文件描述符的事件(如可读、可写)发生时,内核会将它从红黑树中移到就绪队列。
  2. 就绪队列的管理通常使用链表或其他类似的数据结构,它是 先进先出(FIFO) 的,保证了就绪文件描述符的顺序。

        第二点, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,具体的文件描述符会通过传入的 epoll_event 数组 来返回。不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

epoll_create创建的是什么? 

  epoll_create 创建的 epoll 对象 其实是一个内核内部的 epoll 文件描述符,它代表了一个 epoll 实例,用于管理和监控多个文件描述符的 I/O 事件。应用程序通过这个文件描述符来与 epoll 系统进行交互。

4.边缘触发(ET)、水平触发(LT)

  • 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完;
  • 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;

        边缘触发是指在事件状态发生变化的时刻触发一次,例如从无事件变为有事件。在I/O多路复用中,边缘触发意味着当某个文件描述符发生I/O事件(如变为可读或可写)时,我们只会收到一次通知。当收到通知后,我们需要处理该文件描述符上的所有数据,直到数据全部处理完毕,否则不会再收到通知。

        边缘触发的优点是只在事件状态改变时触发,可以减少事件通知的次数。然而,边缘触发的缺点是我们需要确保在收到通知后处理所有相关数据,否则可能会遗漏某些事件。

边缘触发(ET)和水平触发(LT)的区别。用户可能已经知道这两种模式的基本概念,但需要更深入的解释为什么在ET模式下必须使用非阻塞I/O。这里的关键点在于ET模式只在状态变化时通知一次,而LT模式会持续通知直到状态改变。ET模式需要确保在每次事件触发时尽可能多地处理数据,否则可能会丢失后续的事件,因为epoll_wait不会再次通知,除非有新的状态变化。

接下来思考为什么在ET模式下必须使用非阻塞I/O。假设在ET模式下使用阻塞I/O,当epoll_wait通知有数据可读时,应用程序开始读取数据。如果数据没有一次性读完,剩下的数据留在缓冲区里,但由于ET模式不会再次触发,epoll_wait不会再次通知,导致剩下的数据无法被处理,形成阻塞。因此,必须将文件描述符设置为非阻塞模式,这样在读取或写入时如果没有数据或缓冲区满,会立即返回错误(如EAGAIN或EWOULDBLOCK),从而避免进程阻塞,可以继续处理其他事件。

然后要考虑用户可能的疑问点:为什么在LT模式下可以使用阻塞I/O?因为LT模式会持续通知,直到数据被处理完毕,应用程序可以多次读取,即使每次读取没有处理完,下次epoll_wait仍然会通知,所以即使使用阻塞I/O,程序也能正确处理,不会有数据残留的问题。

另外,用户可能混淆了epoll_wait本身的阻塞与文件描述符的阻塞模式。需要明确epoll_wait的阻塞是等待事件发生,而文件描述符的阻塞模式是指I/O操作是否会立即返回。这两个是不同的概念,但在ET模式下,为了防止处理事件时的阻塞导致事件丢失,必须将文件描述符设置为非阻塞。

5.reactor和proactor

reactor

Reactor 模式主要由 Reactor 和处理资源池这两个核心部分组成,它俩负责的事情如下: 

  • Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
  • 处理资源池负责处理事件,如 read -> 业务逻辑 -> send;

单reactor单进程/线程

  • Reactor 对象的作用是监听和分发事件;
  • Acceptor 对象的作用是获取连接;
  • Handler 对象的作用是处理业务;

        对象里的 select、accept、read、send 是系统调用函数,dispatch 和 「业务处理」是需要完成的操作,其中 dispatch 是分发事件操作。

接下来,介绍下「单 Reactor 单进程」这个方案:

  • Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
  • 如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
  • 如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;
  • Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。

单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。

但是,这种方案存在 2 个缺点:

  • 第一个缺点,因为只有一个进程,无法充分利用 多核 CPU 的性能
  • 第二个缺点,Handler 对象在业务处理时,整个进程是无法处理其他连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟

所以,单 Reactor 单进程的方案不适用计算机密集型的场景,只适用于业务处理非常快速的场景

单reactor多进程/多线程

  • Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
  • 如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
  • 如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;

上面的三个步骤和单 Reactor 单线程方案是一样的,接下来的步骤就开始不一样了:

  • Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;
  • 子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;

        单 Reator 多线程的方案优势在于能够充分利用多核 CPU 的能,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。

        例如,子线程完成业务处理后,要把结果传递给主线程的 Handler 进行发送,这里涉及共享数据的竞争。

        要避免多线程由于竞争共享资源而导致数据错乱的问题,就需要在操作共享资源前加上互斥锁,以保证任意时间里只有一个线程在操作共享资源,待该线程操作完释放互斥锁后,其他线程才有机会操作共享数据。

        单reactor缺点:因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。

多 Reactor 多进程 / 线程

  • 主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
  • 子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。
  • 如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。
  • Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
proactor

         Proactor 可以理解为「来了事件操作系统来处理,处理完再通知应用进程」。这里的「事件」就是有新连接、有数据可读、有数据可写的这些 I/O 事件这里的「处理」包含从驱动读取到内核以及从内核读取到用户空间。

        无论是 Reactor,还是 Proactor,都是一种基于「事件分发」的网络编程模式,区别在于 Reactor 模式是基于「待完成」的 I/O 事件,而 Proactor 模式则是基于「已完成」的 I/O 事件

        在 Linux 下的异步 I/O 是不完善的, aio 系列函数是由 POSIX 定义的异步操作接口,不是真正的操作系统级别支持的,而是在用户空间模拟出来的异步,并且仅仅支持基于本地文件的 aio 异步操作,网络编程中的 socket 是不支持的,这也使得基于 Linux 的高性能网络程序都是使用 Reactor 方案。

        而 Windows 里实现了一套完整的支持 socket 的异步编程接口,这套接口就是 IOCP,是由操作系统级别实现的异步 I/O,真正意义上异步 I/O,因此在 Windows 里实现高性能网络程序可以使用效率更高的 Proactor 方案。

6.零拷贝原理

DMA之前的文件拷贝

        进程调用read(),os从用户态切换到内核态,CPU 发出IO请求给磁盘,磁盘把数据放入磁盘缓冲区发起中断,cpu收到中断,把数据从缓冲区拷贝到内存中。

        数据的传输过程,都要需要 CPU 亲自参与搬运数据的过程,而且这个过程,CPU 是不能做其他事情的。

DMA作用

        DMA 技术,也就是直接内存访问(Direct Memory Access

        在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务

网络中传统的文件传输拷贝(有了DMA之后)

四次用户态切换到内核态,四次数据拷贝过程

        首先,期间共发生了 4 次用户态与内核态的上下文切换,因为发生了两次系统调用,一次是 read() ,一次是 write(),每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。

        其次,还发生了 4 次数据拷贝,其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝的,下面说一下这个过程:

  • 第一次拷贝,把磁盘上的数据拷贝到操作系统内核的缓冲区里,这个拷贝的过程是通过 DMA 搬运的。
  • 第二次拷贝,把内核缓冲区的数据拷贝到用户的缓冲区里,于是我们应用程序就可以使用这部分数据了,这个拷贝到过程是由 CPU 完成的。
  • 第三次拷贝,把刚才拷贝到用户的缓冲区里的数据,再拷贝到内核的 socket 的缓冲区里,这个过程依然还是由 CPU 搬运的。
  • 第四次拷贝,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程又是由 DMA 搬运的。
sendifile的作用和mmap的作用

sendfile用来代替read和write.

sendfile+SG DMA(网卡支持)
真正的零拷贝技术
  • 第一步,通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
  • 第二步,缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;

        零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。

        零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。

        所以,总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上

7.通信双方API调用过程?

服务端

  1. 创建套接字(socket):服务器端首先创建一个套接字。在 C 语言中,可以使用 socket() 函数创建一个新的套接字。
  2. 绑定地址(bind):然后,服务器将套接字绑定到指定的 IP 地址和端口。这可以使用 bind() 函数完成。绑定端口的目的:当内核收到 TCP 报文,通过 TCP 头里面的端口号,来找到我们的应用程序,然后把数据传递给我们。绑定 IP 地址的目的:一台机器是可以有多个网卡的,每个网卡都有对应的 IP 地址,当绑定一个网卡时,内核在收到该网卡上的包,才会发给我们;
  3. 监听连接(listen):服务器将套接字设置为监听模式,以便接受来自客户端的连接请求。这可以通过调用 listen() 函数实现。(此时,第一次创建的套接字用来监听。并且一直保持监听状态。)
  4. 接受连接(accept):当客户端发起连接请求时,服务器使用 accept() 函数接受该连接。服务器从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket。
  5. 接收数据(recv):服务器使用 recv() 或类似的函数从客户端接收数据。这些函数通常会阻塞,直到收到数据。
  6. 发送数据(send):服务器根据客户端的请求处理数据并生成响应。然后,服务器使用 send() 或类似的函数将响应数据发送回客户端。
  7. 关闭连接(close):完成通信后,服务器使用 close() 或类似的函数关闭与客户端的连接。服务器可以继续接受其他客户端的连接。

客户端

  1. 创建套接字(socket):与服务器类似,客户端使用 socket() 函数创建一个新的套接字。
  2. 连接服务器(connect):客户端使用 connect() 函数发起对服务器的连接请求。这需要指定服务器的 IP 地址和端口。
  3. 发送数据(send):连接建立后,客户端使用 send() 或类似的函数向服务器发送请求数据。
  4. 接收数据(recv):客户端使用 recv() 或类似的函数接收来自服务器的响应数据。这些函数通常会阻塞,直到收到数据。
  5. 关闭连接(close):通信完成后,客户端使用 close() 或类似的函数关闭与服务器的连接。

8.网络相关linux查看指令

网络出现问题怎么查看
性能指标

通常是以 4 个指标来衡量网络的性能,分别是带宽、延时、吞吐率、PPS(Packet Per Second),它们表示的意义如下:

  • 带宽,表示链路的最大传输速率,单位是 b/s (比特 / 秒),带宽越大,其传输能力就越强。
  • 延时,表示请求数据包发送后,收到对端响应,所需要的时间延迟。不同的场景有着不同的含义,比如可以表示建立 TCP 连接所需的时间延迟,或一个数据包往返所需的时间延迟。
  • 吞吐率,表示单位时间内成功传输的数据量,单位是 b/s(比特 / 秒)或者 B/s(字节 / 秒),吞吐受带宽限制,带宽越大,吞吐率的上限才可能越高。
  • PPS,全称是 Packet Per Second(包 / 秒),表示以网络包为单位的传输速率,一般用来评估系统对于网络的转发能力。
网络配置

ip -s addr show dev eth0        

查看网口的连接状态标志。 MTU 大小。  IP 地址、子网掩码、MAC 地址 

socket信息查看

netstat 或者 ss,这两个命令查看 socket、网络协议栈、网口以及路由表的信息。

 网络吞吐率和 PPS 如何查看?

sar 命令当前网络的吞吐率和 PPS,用法是给 sar 增加 -n 参数就可以查看网络的统计信息,比如

  • sar -n DEV,显示网口的统计数据;
  • sar -n EDEV,显示关于网络错误的统计数据;
  • sar -n TCP,显示 TCP 的统计数据
 连通性和延时如何查看?

要测试本机与远程主机的连通性和延时,通常是使用 ping 命令

9.一致性哈希

什么是负载均衡?

有多个服务器,同时来了很多客户端的任务请求,把这些任务平均分配到各个服务器上。

负载均衡相关的算法?

静态

1.轮询算法

        每个节点(服务器)的硬件配置有所区别,我们可以引入权重值,将硬件配置更好的节点的权重值设高,然后根据各个节点的权重值,按照一定比重分配在不同的节点上,让硬件配置更好的节点承担更多的请求,这种算法叫做加权轮询。

        只能适用与每个节点的数据都是相同的场景,访问任意节点都能请求到数据,不适用于分布式系统。

2.哈希

        根据客户端要获取指定 key 的数据,或者客户端的IP地址,计算哈希值,用哈希值对服务器的数量取模到对应的服务器。但是有一个很致命的问题,如果节点数量发生了变化,也就是在对系统做扩容或者缩容时,必须迁移改变了映射关系的数据,否则会出现查询不到数据的问题。

3.一致性哈希算法

            一致性哈希算法就很好地解决了分布式系统在扩容或者缩容时,发生过多的数据迁移的问题。

             一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值

            一致性哈希要进行两步哈希:

    • 第一步:对存储节点进行哈希计算,也就是对存储节点做哈希映射,比如根据节点的 IP 地址进行哈希;
    • 第二步:当对数据进行存储或访问时,对数据进行哈希映射;

            一致性哈希是指将「存储节点」和「数据」都映射到一个首尾相连的哈希环上。映射的结果值往顺时针的方向的找到第一个节点,就是存储该数据的节点。

            在一致哈希算法中,如果增加或者移除一个节点,仅影响该节点在哈希环上顺时针相邻的后继节点,其它数据也不会受到影响。

            缺点:但是一致性哈希算法并不保证节点能够在哈希环上分布均匀,这样就会带来一个问题,会有大量的请求集中在一个节点上。

            解决办法:

            不再将真实节点映射到哈希环上,而是将虚拟节点映射到哈希环上,给一个真实节点增加三个虚拟节点,映射到哈希环上,并将虚拟节点映射到实际节点,所以这里有「两层」映射关系。

            虚拟节点除了会提高节点的均衡度,还会提高系统的稳定性。当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高

    动态负载均衡

    10.Nginx

    nginx是一款高性能的开源Web服务器,同时也是一个反向代理服务器负载均衡器HTTP缓存工具

            反向代理:反向代理(Reverse Proxy)是一种服务器端的代理技术,其核心作用是作为客户端与后端服务器之间的“中间人”,隐藏真实服务器信息,并优化网络请求处理。

    11.Page cache作用

    PageCache 的优点主要是两个:

    • 缓存最近被访问的数据;
    • 预读功能;

    作用1:        

            文件传输过程,其中第一步都是先需要先把磁盘文件数据拷贝「内核缓冲区」里,这个「内核缓冲区」实际上是磁盘高速缓存(PageCache)。程序运行的时候,具有「局部性」,所以通常,刚被访问的数据在短时间内再次被访问的概率很高,于是我们可以用 PageCache 来缓存最近被访问的数据,当空间不足时淘汰最久未被访问的缓存。

            读磁盘数据的时候,优先在 PageCache 找,如果数据存在则可以直接返回;如果没有,则从磁盘中读取,然后缓存 PageCache 中。

    作用2:

            读取磁盘数据的时候,需要找到数据所在的位置,但是对于磁盘来说,查找磁盘上的数据非常耗时,PageCache 使用了「预读功能」

            假设 read 方法每次只会读 32 KB 的字节,虽然 read 刚开始只会读 0 ~ 32 KB 的字节,但内核会把其后面的 32~64 KB 也读取到 PageCache,这样后面读取 32~64 KB 的成本就很低,如果在 32~64 KB 淘汰出 PageCache 前,进程读取到它了,收益就非常大。

    缺点:读取大文件怎么办?

            在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用,那就白白浪费 DMA 多做的一次数据拷贝,造成性能的降低,即使使用了 PageCache 的零拷贝也会损失性能

    • PageCache 由于长时间被大文件占据,其他「热点」的小文件可能就无法充分使用到 PageCache,于是这样磁盘读写的性能就会下降了;
    • PageCache 中的大文件数据,由于没有享受到缓存带来的好处,但却耗费 DMA 多拷贝到 PageCache 一次;

    12.大文件传输用什么方式

    太难了握草

    1.nginx异步IO与直接IO
    2.HTTP Range请求或工具分块传输实现断点续传

    13.文件描述符设置成阻塞与非阻塞的区别

    阻塞模式:若缓冲区无数据,线程会挂起,直到数据到达或连接关闭。

    非阻塞模式:若缓冲区无数据,read() 立即返回 -1,并设置 errno 为 EAGAIN

    // 设置非阻塞
    fcntl(fd, F_SETFL, O_NONBLOCK);  
    
    ssize_t n = read(fd, buf, sizeof(buf));
    if (n == -1 && errno == EAGAIN) {
        // 数据未就绪,稍后重试或注册到事件循环
    }
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值