文档章节

nginx源码分析之设计之美

那一剑的风情
 那一剑的风情
发布于 2012/09/25 17:51
字数 1978
阅读 10747
收藏 132
在这里向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

© 著作权归作者所有

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

那一剑的风情

粉丝 120
博文 20
码字总数 21879
作品 0
厦门
程序员
私信 提问
加载中

评论(19)

jnuc093
jnuc093

引用来自“defu”的评论

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

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

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

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

引用来自“defu”的评论

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

"错别字"的那两楼评论, 我认为不是挑刺, 是为帮助作者.
至少我是这么认为的.
论韭菜的100种吃法
论韭菜的100种吃法

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

引用来自“0day”的评论

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

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

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

欢迎点击「算法与编程之美」↑关注我们! 本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列博客。 基本概念 定积分的定义如下: 不定积分定义如下: 如果想了解更多,...

算法与编程之美
07/10
0
0
TypeScript、图形与数学日记1(2018-06-14)

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

随风而行之青衫磊落险峰行
06/14
0
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
1K
0
go语言文件汇总

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

d_watson
2016/04/15
127
2
理解异步之美:Promise 与 async await(二)

承上启下 基础预热:你好,JavaScript异步编程---- 理解JavaScript异步的美妙 理解异步之美:Promise与async await(一) 经历了上一篇基础的Promise讲解后我觉得大家对于promise的基本用法和...

酸楚与甘甜
08/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

sed, awk 练习

1. sed打印某行到某行之间的内容 2. sed 转换大小写 将单词首字母转化大写 将所有小写转化大写 3. sed 在某一行最后面添加一个数字 4. 删除某行到最后一行 解析: {:a;N;$!ba;d} :a : 是...

Fc丶
59分钟前
2
0
babel6升级到7,jest-babel报错:Requires Babel "^7.0.0-0", but was loaded with "6.26.3".

自从将前端环境更新到babel7,jest-babel之前是基于babel6的,执行时候就会报:Requires Babel "^7.0.0-0", but was loaded with "6.26.3". 很烦,因为连续帮好几台电脑修复这个问题,所以记...

曾建凯
今天
1
0
探索802.11ax

802.11ax承诺在真实条件下改善峰值性能和最差情况。 如何改善今天的Wi-Fi? 在决定如何改进当前版本以外的Wi-Fi时,802.11ac,IEEE和Wi-Fi联盟调查了Wi-Fi部署和行为,以确定更广泛使用的障碍...

linuxprobe16
今天
2
0
使用linux将64G的SDCARD格式化为FAT32

一、命令如下: sudo fdisk -lsudo mkfs.vfat /dev/sda -Isudo fdisk /dev/sda Welcome to fdisk (util-linux 2.29.2). Changes will remain in memory only, until you decide to wri......

mbzhong
今天
4
0
深入理解Plasma(四):Plasma Cash

这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍在 Plasma 框架下的项目 Plasma Cash。 深入理解Plasma(1):...

HiBlock
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部