自学内容网 自学内容网

每日十题八股-2025年1月18日

1.服务器处理并发请求有哪几种方式?
2.讲一下io多路复用
3.select、poll、epoll 的区别是什么?
4.epoll 的 边缘触发和水平触发有什么区别?
5.redis,nginx,netty 是依赖什么做的这么高性能?
6.零拷贝是什么?
7.了解哪些数据结构?
8.数组和链表区别是什么?
9.为什么数组查询的复杂度为O(1)?
10.说一下队列和栈的区别

1.服务器处理并发请求有哪几种方式?

1. 多线程(Thread-based)
方式:为每个请求分配一个线程进行处理。
优点:实现简单,适用于少量并发。
缺点:线程数量受限,线程切换开销大,容易出现线程资源耗尽问题。
2. 多进程(Process-based)
方式:为每个请求创建一个进程。
优点:进程隔离性强,适用于高安全性需求的任务。
缺点:进程的创建和销毁开销更大,占用更多系统资源。
3. 线程池/进程池
方式:预先创建一定数量的线程或进程来处理请求,避免频繁创建和销毁。
优点:资源管理效率更高,减少了系统开销。
缺点:需要合理配置池的大小,否则可能出现资源利用不足或过载问题。
4. 异步非阻塞 I/O
工作流程:
服务器使用一个主线程处理多个连接。
请求到达后,不等待 I/O 操作完成,而是注册事件回调函数。
当 I/O 操作完成时,事件循环通知回调函数,处理结果。
示例场景:
Node.js 处理 HTTP 请求。
Nginx 处理静态文件请求。
5. 事件驱动模型(Event-driven Architecture)
工作流程:
服务器维护一个事件循环和事件队列。
每个请求被封装成事件,放入队列中。
事件循环检测队列中的事件并触发相应的回调函数。
回调函数执行完毕后继续监听下一个事件。
示例场景:
JavaScript 的 async/await,Python 的 asyncio。
6. 协程(Coroutines)
工作流程:
请求处理通过协程调度器管理。
协程使用 yield 或 await 让出控制权,允许其他协程运行。
I/O 操作完成时,控制权返回给协程继续执行。
示例场景:
Python 的 asyncio 库,Go 语言的 goroutines。
7. 反应器模式(Reactor Pattern)
工作流程:
Reactor 线程等待 I/O 事件(例如,新的连接、读写就绪)。
事件发生时,调用相应的处理器(Handler)。
处理器完成任务后返回到 Reactor,继续等待其他事件。
示例场景:
Java 的 Netty 框架,Python 的 Twisted。

在这里插入图片描述

2.讲一下io多路复用

I/O 多路复用(I/O Multiplexing) 是一种高效的并发 I/O 处理机制,通过使用一个线程管理多个 I/O 操作,而不是为每个 I/O 连接创建独立的线程或进程,从而在网络服务器或其他 I/O 密集型场景中处理大量连接。
I/O 事件检测:监听多个文件描述符,当一个或多个描述符的状态发生变化(如有数据可读或可写),通知应用程序处理对应的 I/O。
I/O 多路复用的工作流程(以 epoll 为例)
创建 epoll 实例:使用 epoll_create 创建事件表。
注册事件:使用 epoll_ctl 添加或修改需要监听的文件描述符及其事件类型(如可读、可写)。
等待事件触发:调用 epoll_wait 进入阻塞,等待任何一个事件发生。
处理事件:当事件触发时,epoll 返回已就绪的文件描述符列表,服务器处理相应的 I/O 操作。

3.select、poll、epoll 的区别是什么?

更细节的部分还是要看小林coding的操作系统倒数的这一题。有细节讲select poll 和epoll是怎么操作的。

C10K 是计算机网络和服务器架构中一个著名的性能挑战概念,指的是如何在一台服务器上同时处理 10,000 个客户端连接的问题。它揭示了传统网络编程模型的性能瓶颈,并推动了现代高效网络服务器和框架的发展。

常见的 I/O 多路复用机制
select
支持平台:几乎所有操作系统。
工作原理:使用一个数组存储文件描述符轮询检测 每个文件描述符的状态。
限制:
监听的文件描述符数量有限(通常为 1024)。
每次调用都需要重新传递所有文件描述符,效率较低。

poll
工作原理:类似 select,使用一个链表存储 文件描述符,因此支持更多连接。
改进点:文件描述符数量限制更高,但每次调用仍需遍历所有描述符。

epoll(Linux 专用)
工作原理:将文件描述符添加到一个内核事件表,只在事件发生时通知用户程序。
改进点:
采用事件通知机制,而非轮询。
处理大规模并发时性能更好,适用于高并发服务器。

4.epoll 的 边缘触发和水平触发有什么区别?

epoll 支持两种事件触发模式,分别是边缘触发(edge-triggered,ET)和水平触发(level-triggered,LT)。
在这里插入图片描述
在这里插入图片描述

  1. 水平触发(Level-Triggered, LT)
    触发机制
    当文件描述符(fd)就绪时,只要条件满足(如有数据可读或可写),每次调用 epoll_wait() 都会通知事件,直到操作完成。
    特点
    默认模式,兼容性好,容易实现。
    如果未处理完数据,下次调用 epoll_wait() 仍会返回该事件。
    示例
    读事件触发后,若未将所有数据读取完毕,epoll_wait() 会反复返回读事件,直到数据被完全读取。
    使用场景
    适用于大多数普通网络程序,编写逻辑简单,适合不频繁切换状态的连接。
  2. 边缘触发(Edge-Triggered, ET)
    触发机制
    当文件描述符从未就绪变为就绪时,epoll_wait() 只通知一次。
    之后,如果没有新数据到达或新状态变化,即使还有未处理的数据,epoll_wait() 也不会再次通知。
    特点
    更高效,减少了多次触发,降低系统调用次数。
    需要程序保证将所有数据一次性读完或写完,否则可能丢失事件通知。
    示例
    读事件触发后,必须使用循环读直到返回 EAGAIN 或 EWOULDBLOCK,否则可能错过后续数据。
    使用场景
    适合高性能服务器,配合非阻塞 I/O 使用。
    适用于处理大量连接和数据的场景,如 Nginx、Redis。

5.redis,nginx,netty 是依赖什么做的这么高性能?

关键词就是Reactor模式用得好。
回答:Redis、Nginx 和 Netty 的高性能依赖于Reactor 模式,这种模式在I/O 多路复用接口的支持下实现了异步非阻塞处理 ,大幅减少了线程/进程切换和锁竞争带来的开销,使它们在处理大规模并发请求时表现出色。

主要是依赖Reactor 模式实现了高性能网络模式,这个是在i/o多路复用接口基础上实现的了网络模型。
Redis 6.0 之前使用的 Reactor 模型就是单 Reactor 单进程模式。
Netty 是采用了多 Reactor 多线程方案。
nginx 是多 Reactor 多进程方案。
在这里插入图片描述Redis 6.0 之前使用的 Reactor 模型就是单 Reactor 单进程模式。
在这里插入图片描述
Netty 是采用了多 Reactor 多线程方案。
在这里插入图片描述

nginx 是多 Reactor 多进程方案。
在这里插入图片描述

6.零拷贝是什么?

零拷贝实现了从两次CPU拷贝,两次DMA拷贝,到只要两次DMA拷贝就能够实现从硬盘读取数据,然后再通过网卡向外发送。零拷贝技术可以把文件传输的性能提高至少一倍以上。
零拷贝技术跳过了多个中间步骤,减少了内存数据复制。具体实现方式如下:
sendfile 系统调用(Linux):
直接从文件描述符读取数据并发送到网络套接字,跳过用户空间。
mmap 文件映射:
将文件内容映射到用户空间地址,通过共享内存方式避免拷贝。
splice 和 tee(Linux 特有):
在不同文件描述符之间传输数据,不经过用户空间。

DMA(Direct Memory Access)是一种允许硬件设备直接访问内存的技术,无需经过 CPU 参与数据的传输,从而减少 CPU 的负载和延迟。

先明确非零拷贝是怎么样的:
在这里插入图片描述
零拷贝:
在这里插入图片描述

7.了解哪些数据结构?

数组,链表,队列,栈,二叉树,跳表,红黑树,B+数。(这三个谨慎说出口,因为了解不深)
在这里插入图片描述

8.数组和链表区别是什么?

最主要的区别就是数组要求在一块连续的内存中,而链表不要求。
基于此拓展出更多的不同,如增删改查的时间复杂度的不同,使用的场景也因为他们的时间复杂度的不同而各有不同。

数组可以通过索引直接访问任何位置的元素,访问效率高,时间复杂度为O(1),而链表需要从头节点开始遍历到目标位置,访问效率较低,时间复杂度为O(n)。
数组插入和删除操作可能需要移动其他元素,时间复杂度为O(n),而链表只需要修改指针指向,时间复杂度为O(1)。

9.为什么数组查询的复杂度为O(1)?

数组必须要内存中一块连续的空间,并且数组中必须存放相同的数据类型。
因此只要知道索引就能够根据(基地址+数据类型大小*索引)直接找到对应的位置。

例如,在初始地址为1000的int数组(4字节),int[5] 就是 1000+5*4 = 1020。

10.说一下队列和栈的区别

队列是先进先出,栈是先进后出。
队列适用于按顺序执行的场景,而栈用于像是递归,函数调用的场景。


原文地址:https://blog.csdn.net/anncyuyan/article/details/145233771

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!