文档章节

nginx源码分析之设计之美

那一剑的风情
 那一剑的风情
发布于 2012/09/25 17:51
字数 1978
阅读 10640
收藏 132
点赞 6
评论 19
在这里向nginx的作者Igor Sysoev致敬,他开发了一个如此伟大的作品。 毫不夸张的说,nginx已经展现了一个成功的项目代码是应该如何架构的了。 本文将试图与读者分享这里面的设计之美。

大千世界,任何东西都有共通之处。当我们讨论一个东西时,首先要给它定义个边界, 在这边界里有两个东西:内核(Kernel)和用户(User)。nginx作为http服务器(其实远不止),我们给它定义边界:实现http服务器提供服务功能。项目名称为nginx,代号(或简称)为ngx,前缀为ngx_。

一、一切从命名谈起
如果有人认为不具可读性的代码是可接受的,那他就是个'天才'。
刚提到任何东西在我们讨论的边界里,都有两个东西:内核(Kernel)和用户(User)。 Kernel作为基础设施存在,天生存在,User则是码农自定义并创造出来的。 比如函数 printf,这个是Kernel的一部分。ngx_write_console是nginx里自定义的一个函数。 Kernel和User的东西一定要区分,这非常有利于提高代码的可读性。

如何区分呢? 就是给User的东西加上项目的前缀ngx_。为什么这样设计呢?比如log_error,这函数能确认是C提供的,还是自己自定义的呢?但是,换成 ngx_log_error,一目了然,肯定是nginx源码里的一个自定义的函数。
所有的接口(全局和静态函数,全局和静态变量,自定义类型)应该遵守这一原则。在nginx里,自定义结构体看起来非常舒服,比如 ngx_command_t,t是typedef的代号。struct: command vs struct: ngx_command_t,您觉得呢?好的命名应该是在头脑里不假思索的就直取其意,而不用再经过一次智商运算,头脑风暴。

二、模块化思想
nginx的整个代码像流水线一样工作着,这流水线上布满着各种模块,他们协同工作,共同完成提供服务。比如 ngx_core_module, ngx_epoll_module, ngx_http_core_module, ngx_http_static_module等,ngx_string.h(c), ngx_times.h(c),在C世界里,文件即模块,你可以当它是基础设施,或工具,只是有的文件有变量,相当于文件访问入口,比如 ngx_http_core_module.h(c)里的ngx_http_core_module全局变量。
没有任何独立存在,不跟任何人打交道的模块,因为那样,它就没有存在的意义,所以模块是有依赖关系的。 比如ngx_event.h(c)依赖于 ngx_string.h,谁维护着这些关系呢,Makefile。所以能掌握一个项目的人,肯定能手写Makefile文件。每个模块有出场顺序的,直到main函数return。nginx作者给模块设计了一个类型成员,可以是core, event, http的一种,很明显的会是这是 core(核心模块) -> event(事件机制) -> http(http业务处理) 这么一个流程。后面的依赖前面的,非常明了。

三、OO面向对象
面向对象和面向过程之争从来没停过。我一向认为有争议的设计不应该融入语言里,语言应该假设程序员能做最正确的事,而不应该去约束程序员如何犯错误。比如goto是否应该存在,全局变量应该怎么样。C以最简洁的语法提供了程序员能秀的平台。废话点到即止。
nginx里有非常多的结构体,不知某大师曾说程序就是算法+数据结构,这里的数据结构不仅是是数组,列表,队列这些经典的,还包括用户自定义的,或封装的,我们称它为抽象是不是更好呢。结构体让某业务概念更具血肉,比如 ngx_listening_t, ngx_connection_t, ngx_event_t,非常高明的封装和命名。读过DDD书的,如果结合nginx源码去看,会发现OO最强烈的表达就是抽象(封装?多态?继承?)。
一个结构体或类应该表达某个主题,比如ngx_connection_t抽象了连接这个业务,里面的成员应该表达两种属性:显性和隐性。很多人忽略了隐性的属性,以 ngx_url_t 为例,url里有 addr, port 这种显而易见的成员,大家都会。但是应该还包括 err,这个表示,一个url解析后的结果,是不是有点像冗余字段,是的,这就是隐性的属性,会让整个结构体更具表达力。读nginx源码,从结构体出现的顺序去理清是个好的方向。作者在设计时极具功力和细腻,比如 ngx_http_rewrite_module里对rewrite的处理,ngx_http_upstream里对upstream的处理。
结构体代表了业务概念的一个方向,nginx在行为表现方向也设计的很精致,可以细看下日志是如何处理的,其中ngx_log_t的handler和ctx两个成员的设计。还有很明显的责任链模式,让人眼前一亮,参考ngx_http_core_run_phases

四、生命周期里秘密
有两个东西是一直在整个项目代码里游荡的,日志和内存池。简单的讲,有3个重要概念:
cycle             : 代表了整个生命周期,只要进程还在,它就一直存活。
connection     : 代表连接的生命周期,一个客户连接过来,它就开始诞生,连接结束,它就跟着终结。
request         : 代表请求,请求一发过来,它开始诞生,请求结束,它也就消亡。
可能你认为connection和request很像,connection比request生命周期更长,request挂了,connection不一定会挂。keepalive就是最好的证明,有了keepalive,客户端刷新时,connection的fd还一直保持用着,服务端的socket是不会close的。
cycle有自己的pool,connection有自己的pool, request有自己的pool,除了cycle外,其余两个在消亡前,要释放内存。
log的表现也很活跃,从最开始的ngx_cycle有自己的log,然后设置成配置文件里指定的error_log,然后从listen开始分支,每个listen自己复制一份log,然后listen connection用了listen对应的fd, 继续再给 connection的两个event: read, write。

五、配置文件为何存在

是先有项目代码,还是先有配置文件呢?我觉得,配置文件是因项目代码存在而存在的,这样讲似乎有点空白。
项目之初,代码是可以硬编码的,比如实现守护进程。但是呢?这样缺少灵活性,所以用配置文件里的配置选项控制这个行为,也正因为如此,配置选项一定要依附,挂钩于某个模块,然后它的值应该解析到这个模块携带的配置结构体。
现在还是单一的行为,由核心行为引起的。但是到了用户决定行为的时候,配置文件应该出现分支。你想到server, location了吗,不同的server有不同的配置,这也是虚拟主机的实现机制。
这个分支非常的重要,原来的ngx_cycle有个void  ****conf_ctx;很酷吧,4层指针。获取配置是这样的:
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
其中,ngx_get_conf这样预定义:
#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]
但是到了分支这里,从cycle->conf_ctx变成了r
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
ngx_http_get_module_main_conf这样定义:
#define ngx_http_get_module_main_conf(r, module)                             \
    (r)->main_conf[module.ctx_index]
当分析完用户的请求行为后,又会将分析完的配置定下来,比如虚拟主机如何实现的:
ngx_http_find_virtual_server(r, r->headers_in.server.data, r->headers_in.server.len) {
    cscf = ngx_hash_find_combined(&r->virtual_names->names,
                                  ngx_hash_key(host, len), host, len);

    if (cscf) {
        r->srv_conf = cscf->ctx->srv_conf;  // 以后直接找r要src_conf获取。
        r->loc_conf = cscf->ctx->loc_conf;
    }
}

天马行空的一口气写完,本文结束。 阅读原文:http://nglua.com/reads/3.html

© 著作权归作者所有

共有 人打赏支持
那一剑的风情

那一剑的风情

粉丝 119
博文 20
码字总数 21879
作品 0
厦门
程序员
加载中

评论(19)

jnuc093
jnuc093

引用来自“defu”的评论

有时候真看不下去某些人评论(一到四楼),作者花精力,花时间,写出一篇博客来,有些人硬要吹毛求疵,鸡蛋里挑骨头地找出一些缺点来,真不知道是什么心理,非得通过讽刺,挖苦,打击,报复来满足内心的那点平衡吗?有你们这样的评论,作者还有心情写下一篇吗?
非常赞同
算法与编程之美
算法与编程之美
是不是可以把nginx的架构图或是流程图详细的介绍说明下?便于我们先从整体上了解,然后再深入下去
蓄丰
蓄丰
紧凑的文章,一口气拜读完成。
好文,学习了!
宏哥
宏哥
naming convention 和 pipeline
是一切软件设计之精华所在.
宏哥
宏哥
naming convention 和 pipeline
是一切软件设计之精华所在.
big-hero
big-hero

引用来自“刘海洋2”的评论

nginx和redis的代码都不错。另外很多开源的代码质量不敢恭维。感觉不到所谓的大师。。。。

能做出贡献的就是大师,代码只是一方面
G.
G.

引用来自“defu”的评论

有时候真看不下去某些人评论(一到四楼),作者花精力,花时间,写出一篇博客来,有些人硬要吹毛求疵,鸡蛋里挑骨头地找出一些缺点来,真不知道是什么心理,非得通过讽刺,挖苦,打击,报复来满足内心的那点平衡吗?有你们这样的评论,作者还有心情写下一篇吗?

"错别字"的那两楼评论, 我认为不是挑刺, 是为帮助作者.
至少我是这么认为的.
big-hero
big-hero

引用来自“那一剑的风情”的评论

引用来自“0day”的评论

多几个前缀字母就成最差了之一了。。。这帽子扣的

加前缀是为了区分Kernel和User,有个相对,就是边界的概念。
比如php语言,linux,这种你可视为内核,就没必要加前缀了。
像平时大家开发的项目,这种都是基于某个Kernel的(你理解成语言也行)加个前缀不是很好吗

自己用着习惯就好
明月惊鹊
明月惊鹊
openssl的源码也不错吧,也写写吧,O(∩_∩)O哈哈~
刘海洋2
nginx和redis的代码都不错。另外很多开源的代码质量不敢恭维。感觉不到所谓的大师。。。。
Nginx源码分析之设计之美

在这里向nginx的作者Igor Sysoev致敬,他开发了一个如此伟大的作品。 毫不夸张的说,nginx已经展现了一个成功的项目代码是应该如何架构的了。 本文将试图与读者分享这里面的设计之美。 大千世...

托勒密 ⋅ 2012/10/16 ⋅ 0

nginx源码分析之配置图解

nginx配置结构清晰,层次分明,这得益于整个架构的模块化设计,文本将揭示配置文件如何被处理和应用。 整个配置文件解析后的结果如图这样存储。 一、解析的核心机制 nginx源码里,ngxconft是...

那一剑的风情 ⋅ 2012/09/28 ⋅ 1

TypeScript、图形与数学日记1(2018-06-14)

决定写本书,虽然文笔不行,但是还是要尝试一下。 主要的demo来自: 数学之美-计算机图形学中的数学方法论(总纲)-2018年1月18日更新版 近两年都在做服务器方面的事情。 现在终于有机会静下心来...

随风而行之青衫磊落险峰行 ⋅ 06/14 ⋅ 0

go语言文件汇总

归并排序及go语言实现 堆排序算法及go语言实现 Go语言基础学习(一)变量 【Leetcode】:Counting Bits问题 in Go语言 基于go语言的心跳响应 【Leetcode】:Single Number III问题 in Go语言 ...

d_watson ⋅ 2016/04/15 ⋅ 2

nginx源码分析—hash结构ngx_hash_t(v1.0.4)

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正! Content 0.序 1.hash结构 1.1ngxhasht结构 1.2ngxhashinit_t结构 1....

晨曦之光 ⋅ 2012/03/09 ⋅ 0

nginx源码分析—启动流程

作者:阿波 本文链接:http://blog.csdn.net/livelylittlefish/article/details/7243718 Content 0. 序 1. main()分析 2. 注意问题 2.1 几个初值 2.2 nginx工作模式 2.3 一些配置 2.4 其他开...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

Java架构师如何冲击年薪40w

有人回答说这只能是大企业或者互联网企业工程师才能拿到。也许是的,小公司或者非互联网企业拿两万的不太可能是码农了,应该已经转管理。还有区域问题,这个不在我的考虑范围内,因为除了北上...

阿阳啊啊 ⋅ 2017/10/16 ⋅ 0

nginx源码分析—数组结构ngx_array_t

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正! Content 0.序 1.数组结构 1.1ngxarrayt结构 1.2ngxarrayt的逻辑结构 ...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

源码圈 365 胖友的书单整理

🙂🙂🙂关注微信公众号:【芋道源码】有福利: RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表 RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址 您对于源码的疑问...

芋道源码掘金Java群217878901 ⋅ 2017/09/21 ⋅ 0

nginx源码分析—链表结构ngx_list_t

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正! Content 1.链表结构 1.2 ngxlistt的逻辑结构 2.1创建链表 3.一个例子...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

20.zip压缩 tar打包 打包并压缩

6月25日任务 6.5 zip压缩工具 6.6 tar打包 6.7 打包并压缩 6.5 zip压缩工具: zip支持压缩目录 zip压缩完之后原来的文件不删除 不同的文件内容其实压缩的效果不一样 文件内有很多重复的用xz压...

王鑫linux ⋅ 3分钟前 ⋅ 0

double类型数据保留四位小数的另一种思路

来源:透析公式处理,有时候数据有很长的小数位,有的时候由在四位以内,如果用一般的处理方法,那么不足四位的小树会补充0到第四位,这样子有点画蛇添足的感觉,不太好看。所以要根据小数的...

young_chen ⋅ 9分钟前 ⋅ 0

Python 优化 回溯下降算法

使用sympy构造表达式,实现回溯下降算法 画出函数图像,先使用暴力搜索,找到最小值约为2.5左右 然后选定初始点,开始进行回溯搜索,下降方向为负梯度方向 下降的误差与步数大致呈现下面的状...

阿豪boy ⋅ 14分钟前 ⋅ 0

Django配置163邮箱出现 authentication failed(535)错误解决方法

最近用Django写某网站,当配置163邮箱设置完成后,出现535错误即:smtplib.SMTPAuthenticationError: (535, b'Error: authentication failed') Django初始配置邮箱设置 EMAIL_HOST = "smtp.1...

陈墨轩_CJX ⋅ 15分钟前 ⋅ 0

用接口模拟可伸缩枚举(34)

1、枚举的可伸缩性最后证明都不是什么好点子 扩展类型的元素是基本类型实例,基本类型的实例却不是扩展类型的元素,很混乱 目前还没有很好的方法来枚举基本类型的所有元素,及其扩展 可伸缩性...

职业搬砖20年 ⋅ 19分钟前 ⋅ 0

Ubuntu18.04 IDEA快捷键无法使用

IDEA默认的回退到上一视图的快捷键是Ctrl + Alt + Left,在ubuntu中这个快捷键被占用了,在16.04中可以在界面中取消这个快捷键,但是18.04就看不到了,可以使用以下命令解决 gsettings set ...

Iceberg_XTY ⋅ 23分钟前 ⋅ 0

如何解决s权限位引发postfix及crontab异常

一、问题现象 业务反馈某台应用服务器,普通用户使用mutt程序发送邮件时,提示“postdrop warning: mail_queue_enter: create file maildrop/713410.6065: Permission denied”,而且普通用法...

问题终结者 ⋅ 35分钟前 ⋅ 0

Unable to load database on disk

由于磁盘空间满了以后,导致zookeeper异常退出,清理磁盘空间后,zk启动报错,信息如下: 2018-06-25 17:18:46,904 INFO org.apache.zookeeper.server.quorum.QuorumPeerConfig: Reading co...

刀锋 ⋅ 55分钟前 ⋅ 0

css3 box-sizing:border-box 实现div一行多列

<!DOCTYPE html><html><head><style> div.container{ background:green; padding:10px 10px;}div.box{box-sizing:border-box;-moz-box-sizing:border-box; /* Fir......

qimh ⋅ 今天 ⋅ 0

Homebrew简介和基本使用

一、Homebrew是什么 Homebrew是一款Mac OS平台下的软件包管理工具,拥有安装、卸载、更新、查看、搜索等很多实用的功能。简单的一条指令,就可以实现包管理,而不用你关心各种依赖和文件路径...

说回答 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部