有了解过 Nginx 的底层实现吗?

这类问题定位其实很明确,性价比确实没那么高,不算是高频八股。但是如果自己项目有涉及,加上恰好被问到的话,那就可以成为加分项。(但是确实难以去作为一个主动介绍的程度,只能说是一个防备式的准备)

以及 Nginx 实现原理有涉及一些常见八股,所以在各种深层原理中性价比就偏高。虽然被问的概率还是总体相对低。

反正还是来速成一下。上面和下面的说法都是个人见解,欢迎指出错误。

---

 内存池:
常见的优化,几乎高性能框架都会设计到内存池

池这种思想我个人概括为:不是要用就分配用完就释放,而是用完不马上销毁,后边就能像是预分配一样反复利用

nginx 里的要点是: 在os内核层的页式管理上的应用层继续使用了「内存块」的管理方案来管理申请到的大片内存,减少应用层分配碎片和频繁的系统调用

effective-go里面就有一个挺有意思的简单例子,这里也分享一
https://github.com/bingohuang/effective-go-zh-en/blob/master/14_Concurrency.md 翻到最后
概括来说就是 rpc 标准包里边在网络io接受字节流数据的时候,用户态缓冲区我们预分配好放在一个channel中来维护,在用完之后可以回来被复用
我觉得这种简单漏桶模型的思想其实是跟池类似的,不过没有池那么灵活了

 基于Reactor的多进程并发模型处理大量反向代理等连接事件:(评论区学习的)
反向代理,大家应该都知道,就是你的请求过来了,我们帮请求放拿到响应再返回给你。

Reactor 模型是一种设计模式,在很多高并发高性能IO系统中也都有用到,比如 Redis、RabbitMQ。
Reactor 模型的主要特点可以概括为:事件循环、事件分发、非阻塞io。
除了这些高性能系统,浏览器运行js和epoll本身也算是利用了这种设计思想。

而 Nginx,概括来说就是:**多进程、非阻塞异步IO** 的并发模型。
我们从典型的运行过程来认识这个模型:
1. 主进程负责管理配置和生成 worker 进程
2. 每个worker开自己的单线程进行事件循环,基于特定的多路复用模型进行单线程处理连接建立和响应事件(其实这里我个人有一定疑问,除非说epoll_wait本身就算是一种异步了)

为什么不用多进程?(评论区大佬贡献的问题)这里我个人理解原因主要是:
1. 为了 worker 隔离从而确保稳定性
2. worker 的设计具有一定独立性,本身就有进程的体量,也是为了实现横向扩展的能力

nginx实际实现,就是在每个工作进程(worker process)中都开一个事件循环来等待事件,得到之后进行分发,分发之后的处理也使用非阻塞io,完成之后回调相关函数做后续操作(事件驱动)。

然后其中我们获取事件的方式,就交给了os内核的io多路复用机制:
这里我们知道,epoll解决的问题是进行网络io的时候如何高效建立连接和监听请求的。
传统建立tcp连接的方式,分阻塞和非阻塞两种。
阻塞就是监听连接建立和io过程都阻塞不能做其他事;非阻塞就是通过忙轮询的方式来看是否有连接,可以暂时做别的事情,不过io过程也是阻塞的。
这里结合网络编程伪代码理解会更好

而epoll可以实现多路复用,在单线程内,我们可以同时处理多个连接。
具体来说,epoll用红黑树存储事件和他们对应的socket,当有网络连接事件过来的时候,红黑树会扫一遍看看哪些事件要被触发,然后相关socket会进入就绪队列。
一般最简单的一种用法,我们通过阻塞的方式获取到当前所有就绪队列的socket,如果是 我们用来监听的 server socket,那么就accept然后建立新的事件和 client socket加入红黑树,如果是client socket我们就正常处理io事件。
总结就是有高效查找、事件触发等优势,以此可以高效处理大量连接(和io过程关系不大,主要还是连接建立和连接事件触发两大块)
始终使用「边缘触发」的就绪队列维护方式(用户必须一次处理完阻塞获取的所有事件,否则会清理掉)来应付高并发。

不过确实nginx只在linux上使用epoll,而且这个其实反向代理关联不会特别大,多一个建立连接的目标而已,上游的连接建立依然会使用 epoll。复杂还是复杂在网络编程的实现,不过能说出来这些感觉已经够了。

 零拷贝处理静态文件:
就是除了kafka,nginx也用了零拷贝
nginx 利用 location 来进行动静分离。动态资源我们反向代理直接丢给服务器处理。
nginx 可以利用他自己的优化来单独去处理对静态文件的请求,除了缓存就是零拷贝了。
详细原理就不多说了,看小林会更好点。
要说详细了我也不会,实际实现还挺复杂的,除了sendfile还有其他调用,只能跟面试官嘤嘤嘤了
我感觉说简单了到知道dma是用来承接cpu的一部分操作来处理一次磁盘io过程,然后零拷贝是用来继续减少从用户缓冲区发送到内核socket的两次拷贝和一次上下文切换的。

---

个人笔记有不对请指出
牛佬们其他有关于nginx的常见考点也可以分享下 非常感谢
全部评论
漏桶模型?那里好像说的有些问题,这里只是用户态缓冲区放在 channel 中维护,用完可复用
1 回复 分享
发布于 08-21 00:53 北京
就被问到过一次关于nginx的题目,问题是为什么使用多进程而不是多线程?
1 回复 分享
发布于 08-24 17:49 广东
mark了
点赞 回复 分享
发布于 08-10 21:16 浙江

相关推荐

Git的分支是指在代码仓库中独立存在的一个代码版本。 分支可以用来同时进行不同的开发工作,每个分支都有自己的提交历史和修改记录。在Git中,创建和合并分支的步骤如下:https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mg58Em&uuid=f818c6d22c98401682f8662612b9e57f创建分支:使用git branch命令可以创建一个新的分支。例如,要创建一个名为"feature"的分支,可以运行git branch feature命令。切换分支:使用git checkout命令可以切换到指定的分支。例如,要切换到"feature"分支,可以运行git checkout feature命令。开发和提交:在切换到新分支后,可以在该分支上进行开发工作。添加、修改和删除文件,并使用git add和git commit命令将修改提交到该分支。合并分支:当在新分支上的开发工作完成后,可以将该分支的修改合并到其他分支上。首先,切换到目标分支(例如主分支):git checkout main。然后,使用git merge命令将新分支的修改合并到目标分支上:git merge feature。解决冲突:在合并分支时,如果目标分支和新分支对同一文件进行了不同的修改,可能会发生冲突。需要手动解决冲突,选择保留哪些修改或进行修改的合并。删除分支:在分支合并完成后,可以使用git branch -d命令删除不再需要的分支。例如,要删除"feature"分支,可以运行git branch -d feature命令。通过创建和合并分支,开发者可以在不影响主分支的情况下进行并行开发和测试,提高团队的工作效率。
2024-10-14
在牛客打卡258天,今天也很努力鸭!
点赞 评论 收藏
分享
5 32 评论
分享
牛客网
牛客企业服务