洪志道 洪志道的专栏
浏览 762
文章 5
订阅 29
打开NGINX Unit世界 洪志道 发表于 : 2020-08-31 22:08

引子我是NGINX Unit的贡献者,Unit是我非常喜欢的一个开源软件。 我将写一系列Unit的文章分享Unit的世界,相信这个优秀的软件会有非常好的前景。  NGINX Unit是什么?NGINX Unit是一个全新的,由NGINX作者亲自设计,带领NGINX核心团队开发的纯c软件。官方的定义:Unit是一个动态的web和应用服务器。因此它的三大核心为:动态,web和应用。    Unit总体架构  后续会有专门文章分析Unit构架设计,敬请关注。动态动态指两部分,动态配置和应用进程的动态管理。这里只介绍动态配置,这是它最大的亮点之一。 动态配置一直是NGINX软件的缺陷,重新设计的Unit没有这个问题。 简单说,Unit已经没有配置文件。Unit提供了http API接口,所有配置的更新都通过RESTful方式操作。    应用Unit是个多语言应用软件,它支持同时多个语言,甚至同个语言的不同版本,比如python2和python3,php5和php7。NGINX还有个问题,它不支持应用开发。是的,lua模块已经能做非常多的应用了。但是官方想支持更多主流的语言,于是有了这个设计。   webUnit已经支持了static和proxy两个功能,还比较粗糙。相信这些核心功能未来能跟nginx一样完善。其它Unit已经支持TLS,HTTP/2也在计划当中。此外不得不提的是Unit支持了类似容器的名称空间(namespace)和文件系统隔离(file system isolation)。  Unit搭建文件服务器1. 安装> git clone git@github.com:nginx/unit.git && cd unit > ./configure && make  2. 启动> ./build/unitd  3. 配置> cat config.json {         "listeners": {                 "127.0.0.1:80": {                         "pass": "routes"                 }         },         "routes": [                 {                         "action": {                                 "share": "/var/www/"                         }                 }         ] } EOF  > curl -X PUT --data-binary @config.json --unix-socket control.unit.sock http://localhost/config {     "success": "Reconfiguration done." } 4. 访问> curl http://127.0.0.1:80  更多请看官方文档 下篇介绍:Unit架构设计  

点赞 4
1 条评论
nginx源码分析之变量设计 洪志道 发表于 : 2020-08-31 14:36

nginx的配置文件使用简单灵活,某些部分还具备脚本语言的特点,变量就是其中一个特色。本文将分析变量是如何设计实现的。 0. 什么是变量脚本语言都有变量这个东西,其作用就是让内容可变,用名称代替可变的内容,所以变量具有赋值和取值的特点。nginx的变量跟php一样,以$开头。两种用法:赋值:set $some_var nginx;取值:$some_var; 1.整体设计  * 创建所有变量只能在配置文件解析,也就是工作进程启动之前创建,有些是内置的变量,有些是自定义的变量。没什么区别,比如 $http_host是内置变量,set $some_var some_val。通过set指定创建的是自定义变量,当然也可以其它方式,如果你自己写模块的话。这时用到 cmcf->variables_keys, cmcf->variables 两个数组,数组元素类型为ngx_http_variable_t。  * 使用 变量使用(即拿它的值)要先获取索引(发生在配置阶段),这是为了加快访问速度,然后根据索引拿它的值(发生在运行阶段)。这时用到r->variables数组,数组元素为ngx_http_variable_value_t(ngx_variable_value_t的别名)。配置时:n = ngx_http_get_variable_index(cf, name); 运行时:v = ngx_http_get_indexed_variable(r, n);  cmcf->variables_keys 创建的变量都存在这个数组cmcf->varialles 使用的变量(为了拿索引)都存储在这里。nginx会在init conf时检查cmcf->variables的所有变量必须在cmcf->variables_keys里。r->variables 将cmcf->variables导入到这里,为了更方便处理,这时只需要拿值 2、创建变量nginx有内置的变量,分布在好多个模块里,这些变量在配置解析之前构建完成,接下来解析配置,可能碰到变量(内置或自定义),最后处理所有的变量。preconfigure : 添加内置变量,比如 $request, $server_name, $args 等。parse             : 添加自定义变量。init                 : 处理所有变量,比如自定义变量没有对应相同的内置变量,当作错误处理。如果最终处理成功,会有个hash存储这些变量的数据。我们看下内部如何实现的:  * preconfigure阶段:cmcf->variables_keys:所有内置变量都会添加到这个数组里。每个成员的结构体是ngx_http_variable_ttypedef struct {    ngx_str_t                     name;   /* must be first to build the hash */    ngx_http_set_variable_pt      set_handler;    ngx_http_get_variable_pt      get_handler;    uintptr_t                     data;    ngx_uint_t                    flags; # 这个比较重要,看下面解释    ngx_uint_t                    index;} ngx_http_variable_t;   name是名称,set_handler和get_handler分别用于赋值和取值,需要配合data,这几个比较简单。index是索引的意思,可以通过根据这个值拿到对应的变量,具体后面会再讲到。  flags是变量标记,不同的标记使其用法和用途不同,有NGX_HTTP_VAR_CHANGEABLE,NGX_HTTP_VAR_NOCACHEABLE标记。有NGX_HTTP_VAR_CHANGEABLE标记意味着变量是可变的。比如 $server_name是不可变的,你不能这样操作 set $server_name "err"; $args是可变的,就可以这样操作 set $args "ok"; 内置变量在源码里都有指定它的标记,自定义变量都是可变的。  3.使用变量要获取nginx的变量的值有两种方式:索引和变量名  *索引 如前面介绍,先在配置阶段拿索引,然后在运行阶段根据索引拿值 *变量名 ngx_http_variable_value_t * ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key); 在整个配置文件解析处理后,nginx会构造一个hash:cmcf->variables_hash,存储所有的变量。可想而知,根据变量名就可以快速拿到对应的变量。当然用索引的方式更快,至于选哪种视情况而定了。 4. 总结要理解变量,要先理解nginx的两个阶段,解析阶段和运行阶段。解析阶段尽量做事前工作,如创建变量、拿索引等。到了运行阶段就可以快速的拿变量的值。还可以看出nginx的一个重要设计,解析时函数的参数基本有 ngx_conf_t *cf,到了运行阶段就是 ngx_http_request_t *r了。使用到的东西也更简化了,比如变量在解析阶段,需要有get和set(可选),但到了运行阶段,只需要拿值。这种细微的设计变化,可以好好思考,并转化成自己的理解。我经常推荐看nginx源码的同学可以从ngx_log_t *log这个东西入手,因为它反映了整个nginx的生命周期。

点赞 3
0 条评论
nginx源码分析之缓存设计 洪志道 发表于 : 2020-08-31 14:35

nginx一向以高性能著称,缓存是其中一大利器,本文将分享nginx中各个级别缓存的原理和内部是如何实现的。 0.什么是缓存缓存存在计算机中的各个领域,其目的都是为了提升处理速度,可以说是以空间换取了时间。但事实并不是这么简单,因为引入了缓存,就要处理一致性、过期处理、缓存策略和命中率等。例如CPU有几级缓存,memcached作为内存缓存服务器,redis也是,还有文件缓存服务器,CDN等。总而言之,访问目标后‘备份’到某中间层,下次再访问时,直接从中间层获取,加快了访问速度。所以目标越往后,处理速度越慢,程序要做的就是尽量命中在前面,不要让访问穿透到后面目标。 1.nginx缓存nginx属于web应用服务器,是典型的request/response模式。整体而言,nginx的缓存可以总结为3个:304响应、静态级别缓存、动态级别缓存。下面一一介绍:  *304响应这个其实不算真正意义上的缓存,之所以提出来,是因为它的重要性,也因为涉及客户端的缓存。当客户端访问某URL时,如果服务器响应304状态码,其意义是告诉客户端您访问的东西没有变化过,内容从自己本地获取。浏览器就是典型的应用。当你第一次访问某静态资源时,响应200,当按F5时,会响应304。实现原理是nginx根据头部信息if_modified_since与静态资源的时间戳比较,如果一样,则返回304。做web开发应该清楚这个状态码。  *静态级别缓存location /test {    open_file_cache  max=100;}这个级别的缓存是缓存静态资源文件的句柄。nginx处理一个请求时,会打开一个文件句柄,然后在请求处理结束后关闭它。加入open_file_cache后,就会将文件句柄保存在工作进程中(不是共享内存),只要工作进程存在,当后面的请求访问同样的文件时,就不用重新打开文件。这样的好处是节省了文件打开关闭的开销。  *动态级别缓存http {    ...    fastcgi_cache_path  /tmp/abc keys_zone=c1:10M;    server {        location ~ \.php$ {            ...            fastcgi_cache  c1;            fastcgi_cache_valid  200 30s;        }    }} 这个级别的缓存是缓存动态请求的内容。当客户端访问某动态文件时(http://example.com/test.php),nginx将请求重新封装成fastcgi协议,然后转发给php-fpm处理,当php-fpm正常响应后,nginx将响应内容保存到某目录的文件里。下次同样的请求过来,就直接从缓存目录的文件中读取,比第一次快且省事很多。  聪明的你可能已经想到动态级别缓存可以再加上静态级别缓存,没错的。这3种响应是独立的,可以灵活配合使用。现在您已经明白缓存的原理,接下来看下如何实现的。  2.304响应ngx_http_not_modified_filter_module处理了这个功能,属于http request header filter模块。源码非常简单。static ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r){    ...    if (r->headers_in.if_modified_since) {         if (r->headers_in.if_modified_since            && ngx_http_test_if_modified(r))        {            return ngx_http_next_header_filter(r);        }         /* not modified */        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;        return ngx_http_next_header_filter(r);    }     return ngx_http_next_header_filter(r);} static ngx_uint_t ngx_http_test_if_modified(ngx_http_request_t *r){    ...     if (r->headers_out.last_modified_time == (time_t) -1) {        return 1;    }     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);     if (clcf->if_modified_since == NGX_HTTP_IMS_OFF) {    ims = ngx_parse_http_time(r->headers_in.if_modified_since->value.data,                              r->headers_in.if_modified_since->value.len);     if (ims == r->headers_out.last_modified_time) {        return 0;    }    if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT        || ims < r->headers_out.last_modified_time)    {        return 1;    }    return 0;} 3.静态级别缓存typedef struct {    ngx_rbtree_t             rbtree;    ngx_rbtree_node_t        sentinel;    ngx_queue_t              expire_queue;    ngx_uint_t               current;    ngx_uint_t               max;    time_t                   inactive; } ngx_open_file_cache_t; 记得这个只在工作进程中处理,不涉及共享内存。看的出来ngx_open_file_cache_t是一个红黑树,也是一个队列。每个文件的文件(根据完整文件名)对应一个节点。这样就完整的维护了所有的可能要缓存的文件。又因为缓存需要处理过期,所以最近访问的文件会保持在expire_queue队列的前面,每次访问将从队列中删除,然后移到最前面。整个逻辑封装在一个函数里:ngx_int_t ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name, ngx_open_file_info_t *of, ngx_pool_t *pool) { ... } 4.动态级别缓存ngx_http_file_cache_t {    ngx_http_file_cache_sh_t {        tree        queue        ...    }}ngx_http_cache_t {    keys    key    ngx_http_file_cache_node_t}如果简化点处理过程就是:根据url算出md5,然后将动态请求响应内容存储到文件名为某md5的文件里。http://example.com/a.php -> d41d8cd98f00b204e9800998ecf8427e -> /tmp/abc/d41d8cd98f00b204e9800998ecf8427ehttp://example.com/b.php -> d41d8cd98f00b204e9800998ecf8432d -> /tmp/abc/d41d8cd98f00b204e9800998ecf8432dhttp://example.com/c.php -> d41d8cd98f00b204e9800998ecf84a32 -> /tmp/abc/d41d8cd98f00b204e9800998ecf84a32因为文件存储在硬盘里,所以这里用到共享内存,而不是工作进程的内存中,来保存这种对应关系。我们可以将每个请求(r)当作各个缓存节点,对应ngx_http_cache_t。ngx_http_file_cache_t就是缓存的集合。明白这个关系后,我们解释下整个流程是如何从开始到结束的。  a)根据fastcgi_cache_path的keys_zone,nginx创建了多个 ngx_http_file_cache_t。根据fastcgi_cache指定了某个location将用到哪个ngx_http_file_cache_t。b)为每个请求创建一个ngx_http_cache_t(在ngx_http_file_cache_new函数里),然后继续产生一个key,作为本节点的索引。你可以将请求、请求响应内容、内容存储文件都当作同一个东西,最终都是节点,对应ngx_http_cache_t。所以需要有       key这个东西。c)处理缓存目录和文件名d)nginx处理动态请求是在ngx_event_pipe里的,当需要缓存里,pipe会有个temp_path将响应内容存储到这个文件里e)最后将pipe的temp_path重命名成前面产生的文件名,结束了。 5.小结动态请求的缓存处理代码还是有点复杂的,有兴趣的同学研究下其如何实现很有好处,可以体会下nginx的代码精致程度。如一惯风格,写作主要以白话方式分享原理,细节点到即止,细节上有疑问欢迎交流。

点赞 4
0 条评论
nginx源码分析之配置图解 洪志道 发表于 : 2020-08-31 14:31

nginx配置结构清晰,层次分明,这得益于整个架构的模块化设计,文本将揭示配置文件如何被处理和应用。整个配置文件解析后的结果如图这样存储。  一、解析的核心机制nginx源码里,ngx_conf_t是解析的关键结构体ngx_conf_handler函数里:/* set up the directive‘s configuration context */conf = NULL;/* direct指令,一般是core类型模块指令,比如 daemon, work_processes */if (cmd->type & NGX_DIRECT_CONF) {    conf = ((void **) cf->ctx)[ngx_modules[i]->index]; /* 直接存储,比如 ngx_core_conf_t *//* main指令,比如 events, http,此时指向它的地址,这样才能分配数组指针,存储属于它的结构体们。 */} else if (cmd->type & NGX_MAIN_CONF) {    conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); /* 参考图片 */} else if (cf->ctx) {    confp = *(void **) ((char *) cf->ctx + cmd->conf);    /* 有移位的,因此http有三个部分,main, srv, conf,这个就为此而设计的,继续下面的sendfile指令 */     if (confp) {        conf = confp[ngx_modules[i]->ctx_index];    }}rv = cmd->set(cf, cmd, conf);比如sendfile指令:{ ngx_string("sendfile"),  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF                    |NGX_CONF_FLAG,  ngx_conf_set_flag_slot,  NGX_HTTP_LOC_CONF_OFFSET,  /* 这个对应上面理解,正因有这个offset,它才找到loc部分的配置 */  offsetof(ngx_http_core_loc_conf_t, sendfile),  NULL } 二、配置的应用1、最简单形式,direct方式/* 定义获取配置文件的宏 */#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {    ngx_process = NGX_PROCESS_MASTER;} 2、稍微复杂点形式,main方式/* 定义获取配置文件的宏,注意指针 */#define ngx_event_get_conf(conf_ctx, module)                                  \             (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];ngx_epoll_conf_t  *epcf;epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);nevents = epcf->events; 3、不简单的http配置 /* 宏定义,r是什么,稍后解释 */#define ngx_http_get_module_loc_conf(r, module)  (r)->loc_conf[module.ctx_index]ngx_http_log_loc_conf_t  *lcf;lcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);.../* r是请求,它是这么来的,在ngx_http_init_request函数里(ngx_http_request.c文件)*/r = ...;cscf = addr_conf->default_server;r->main_conf = cscf->ctx->main_conf;r->srv_conf = cscf->ctx->srv_conf;r->loc_conf = cscf->ctx->loc_conf; 还有个要提的,http配置分main, src, loc,下级的配置可以覆盖上级,很明显,上级只是默认设置值而已。 三、重提模块化设计学着用模块化的角度去看nginx的整体设计,一切以模块为核心,配置依赖于模块,即模块本身就携带着它的配置。正因为这样的设计的,配置文件的解析,使用非常简单。避免过多使用全局变量好像成为一种共识,但是在nginx世界里,全局变量可不少,每个模块都是个全局变量,为什么这样设计呢?因为模块之间是有依赖性的,所以需要互相访问。 配置文件解析这块的代码极具借鉴,本文只管窥般分析了配置文件,不能剥夺读者阅读源码的享受,点到即止。

点赞 4
0 条评论
如何高效的学习NGINX 洪志道 发表于 : 2020-06-06 00:41

本文适合对象:源码开发人员和软件使用人员先说说nginx能吸收的营养知识* http协议和服务器是如何实现的* 网络编程的知识* 简直就是c教科书* 代码阅读能力* 良好的开发习惯* 锻炼逻辑能力* 完全掌握nginx提升自信心深入NGINX源码的学习路径建议尽量只看源码,不看任何其它的资料,但是可以少量参考。如果你是新手,选最初发布版本 nginx0.1.0。(2004年发布)从代码量比较 nginx0.1.0:  4w+  vs  nginx1.19.0: 19w+幸运的是架构几乎没变化。因为模块化是nginx架构的核心,这个在0.1就支持了。不幸运的是这也成了一个问题,举个例子,为什么nginx改配置需要reload呢?有了模块化之后,涌现了很多的第三方模块,有些质量非常不错,推动了社区的发展。有些质量比较低。但不管怎么样,这些大量的第三方模块,让nginx的API不能轻易改动,更不用说架构了。20年前互联网没这么复杂,reload完全不是问题。但是放在现在,长连接业务已经变的很常见,这就成为一个明显的问题了。这里不得不感叹很多厉害人物,在20岁出头就已经有神作了。选好版本后,带着问题从main开始阅读。我按顺便列了些问题:0. 动手编译hello world模块1. 解释`void ****conf_ctx`2. 怎么响应3023. 为什么能输出html4. 为什么reload配置能刷新5, 为什么upgrade能平滑升级6. 解释超时怎么处理的7. 解释proxy怎么工作的为方便大家搭建环境,弄了个仓库,有问题可以建立issue,在时间允许上会回复。https://github.com/hongzhidao/nginx-code-reading记得star,让我知道对多少人有帮助。提示:碰到不会再学习* 如果不熟悉gdb,没关系。配置文件里配置 daemon off; 会printf即可。* 如果不熟悉c语言,没关系。先搞懂指针,剩下看源码时碰到不会再学习。* 如果不熟悉网络编程,也没关系。碰到看不懂再学习。* 其它类似epoll等一样适用。* hello world模块用1.19编译。* nginx 0.1.0有些bug,作了修复。如果你觉得跨过新手级别了,看最新版的源码,同样带着问题,但是要写模块。1. 看官方开发指南https://github.com/baishancloud/nginx-development-guide这是跟另一朋友一起翻译的,openssl社区的成员,杨洋。2. 多写模块,做到写出来的风格跟源码里的看起来一样。模块自己想,能在工作中解决问题最好了。3. 提高代码能力。a. 抄源码是个方式。 b. 找独立的函数,自己实现,然后跟源码里对比。 (问题待整理)如果哪天你觉得对360无死角了,像社区的一样,看nginx应该看的都是不足,试试解决这些不足。如果你是一名使用者,包括运维同学。也想深入nginx怎么办?首先,你已经具备nginx的环境,因为你碰到了问题,想深入nginx能在工作中带来帮助。这已经成功一半了。 进入源码吧,比你想象的简单,这跟学习英语一样,用久就习惯了。举个例子,你想知道map的详细用法。1. 查看官方文档:http://nginx.org/en/docs/ 找到 map 模块,详细阅读。2. 对比源码,看它的逻辑: src/http/modules/ngx_http_map_module.c为什么看源码有效呢?首先,完全可以将c代码当作普通的英语描述(俗称伪代码)其次,你会惊讶于nginx的代码质量,你需要的功能都有专门的一个地方体现。最后,我一直说的,nginx源码非常锻炼人的逻辑能力。这几年业余时间跟NGINX社区的一起写代码,对nginx/njs/unit都算熟悉,仅供参考。下篇待续,整体上欣赏NGINX源码的设计。源码文章只会写一篇,只是为了更快进入源码大门。上面提到的看源码是最好的方式,不想剥夺读者思考的乐趣。

点赞 5
1 条评论
TA的热门
本文适合对象:源码开发人员和软件使用人员先说说nginx能吸收的营养知识* http协议和服务器是如何实现的* 网络编程的知识* 简直就是c教科书* 代码阅读能力* 良好的开发习惯* 锻炼逻辑能力* 完全掌握nginx提升自信心深入NGINX源码的学习路径建议尽量只看源码,不看任何其它的资料,但是可以少量参考。如果你是新手,选最初发布版本 nginx0.1.0。(2004年发布)从代码量比较 nginx0.1.0:  4w+  vs  nginx1.19.0: 19w+幸运的是架构几乎没变化。因为模块化是nginx架构的核心,这个在0.1就支持了。不幸运的是这也成了一个问题,举个例子,为什么nginx改配置需要reload呢?有了模块化之后,涌现了很多的第三方模块,有些质量非常不错,推动了社区的发展。有些质量比较低。但不管怎么样,这些大量的第三方模块,让nginx的API不能轻易改动,更不用说架构了。20年前互联网没这么复杂,reload完全不是问题。但是放在现在,长连接业务已经变的很常见,这就成为一个明显的问题了。这里不得不感叹很多厉害人物,在20岁出头就已经有神作了。选好版本后,带着问题从main开始阅读。我按顺便列了些问题:0. 动手编译hello world模块1. 解释`void ****conf_ctx`2. 怎么响应3023. 为什么能输出html4. 为什么reload配置能刷新5, 为什么upgrade能平滑升级6. 解释超时怎么处理的7. 解释proxy怎么工作的为方便大家搭建环境,弄了个仓库,有问题可以建立issue,在时间允许上会回复。https://github.com/hongzhidao/nginx-code-reading记得star,让我知道对多少人有帮助。提示:碰到不会再学习* 如果不熟悉gdb,没关系。配置文件里配置 daemon off; 会printf即可。* 如果不熟悉c语言,没关系。先搞懂指针,剩下看源码时碰到不会再学习。* 如果不熟悉网络编程,也没关系。碰到看不懂再学习。* 其它类似epoll等一样适用。* hello world模块用1.19编译。* nginx 0.1.0有些bug,作了修复。如果你觉得跨过新手级别了,看最新版的源码,同样带着问题,但是要写模块。1. 看官方开发指南https://github.com/baishancloud/nginx-development-guide这是跟另一朋友一起翻译的,openssl社区的成员,杨洋。2. 多写模块,做到写出来的风格跟源码里的看起来一样。模块自己想,能在工作中解决问题最好了。3. 提高代码能力。a. 抄源码是个方式。 b. 找独立的函数,自己实现,然后跟源码里对比。 (问题待整理)如果哪天你觉得对360无死角了,像社区的一样,看nginx应该看的都是不足,试试解决这些不足。如果你是一名使用者,包括运维同学。也想深入nginx怎么办?首先,你已经具备nginx的环境,因为你碰到了问题,想深入nginx能在工作中带来帮助。这已经成功一半了。 进入源码吧,比你想象的简单,这跟学习英语一样,用久就习惯了。举个例子,你想知道map的详细用法。1. 查看官方文档:http://nginx.org/en/docs/ 找到 map 模块,详细阅读。2. 对比源码,看它的逻辑: src/http/modules/ngx_http_map_module.c为什么看源码有效呢?首先,完全可以将c代码当作普通的英语描述(俗称伪代码)其次,你会惊讶于nginx的代码质量,你需要的功能都有专门的一个地方体现。最后,我一直说的,nginx源码非常锻炼人的逻辑能力。这几年业余时间跟NGINX社区的一起写代码,对nginx/njs/unit都算熟悉,仅供参考。下篇待续,整体上欣赏NGINX源码的设计。源码文章只会写一篇,只是为了更快进入源码大门。上面提到的看源码是最好的方式,不想剥夺读者思考的乐趣。
引子我是NGINX Unit的贡献者,Unit是我非常喜欢的一个开源软件。 我将写一系列Unit的文章分享Unit的世界,相信这个优秀的软件会有非常好的前景。  NGINX Unit是什么?NGINX Unit是一个全新的,由NGINX作者亲自设计,带领NGINX核心团队开发的纯c软件。官方的定义:Unit是一个动态的web和应用服务器。因此它的三大核心为:动态,web和应用。    Unit总体架构  后续会有专门文章分析Unit构架设计,敬请关注。动态动态指两部分,动态配置和应用进程的动态管理。这里只介绍动态配置,这是它最大的亮点之一。 动态配置一直是NGINX软件的缺陷,重新设计的Unit没有这个问题。 简单说,Unit已经没有配置文件。Unit提供了http API接口,所有配置的更新都通过RESTful方式操作。    应用Unit是个多语言应用软件,它支持同时多个语言,甚至同个语言的不同版本,比如python2和python3,php5和php7。NGINX还有个问题,它不支持应用开发。是的,lua模块已经能做非常多的应用了。但是官方想支持更多主流的语言,于是有了这个设计。   webUnit已经支持了static和proxy两个功能,还比较粗糙。相信这些核心功能未来能跟nginx一样完善。其它Unit已经支持TLS,HTTP/2也在计划当中。此外不得不提的是Unit支持了类似容器的名称空间(namespace)和文件系统隔离(file system isolation)。  Unit搭建文件服务器1. 安装> git clone git@github.com:nginx/unit.git && cd unit > ./configure && make  2. 启动> ./build/unitd  3. 配置> cat config.json {         "listeners": {                 "127.0.0.1:80": {                         "pass": "routes"                 }         },         "routes": [                 {                         "action": {                                 "share": "/var/www/"                         }                 }         ] } EOF  > curl -X PUT --data-binary @config.json --unix-socket control.unit.sock http://localhost/config {     "success": "Reconfiguration done." } 4. 访问> curl http://127.0.0.1:80  更多请看官方文档 下篇介绍:Unit架构设计  
nginx配置结构清晰,层次分明,这得益于整个架构的模块化设计,文本将揭示配置文件如何被处理和应用。整个配置文件解析后的结果如图这样存储。  一、解析的核心机制nginx源码里,ngx_conf_t是解析的关键结构体ngx_conf_handler函数里:/* set up the directive‘s configuration context */conf = NULL;/* direct指令,一般是core类型模块指令,比如 daemon, work_processes */if (cmd->type & NGX_DIRECT_CONF) {    conf = ((void **) cf->ctx)[ngx_modules[i]->index]; /* 直接存储,比如 ngx_core_conf_t *//* main指令,比如 events, http,此时指向它的地址,这样才能分配数组指针,存储属于它的结构体们。 */} else if (cmd->type & NGX_MAIN_CONF) {    conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); /* 参考图片 */} else if (cf->ctx) {    confp = *(void **) ((char *) cf->ctx + cmd->conf);    /* 有移位的,因此http有三个部分,main, srv, conf,这个就为此而设计的,继续下面的sendfile指令 */     if (confp) {        conf = confp[ngx_modules[i]->ctx_index];    }}rv = cmd->set(cf, cmd, conf);比如sendfile指令:{ ngx_string("sendfile"),  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF                    |NGX_CONF_FLAG,  ngx_conf_set_flag_slot,  NGX_HTTP_LOC_CONF_OFFSET,  /* 这个对应上面理解,正因有这个offset,它才找到loc部分的配置 */  offsetof(ngx_http_core_loc_conf_t, sendfile),  NULL } 二、配置的应用1、最简单形式,direct方式/* 定义获取配置文件的宏 */#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {    ngx_process = NGX_PROCESS_MASTER;} 2、稍微复杂点形式,main方式/* 定义获取配置文件的宏,注意指针 */#define ngx_event_get_conf(conf_ctx, module)                                  \             (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];ngx_epoll_conf_t  *epcf;epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);nevents = epcf->events; 3、不简单的http配置 /* 宏定义,r是什么,稍后解释 */#define ngx_http_get_module_loc_conf(r, module)  (r)->loc_conf[module.ctx_index]ngx_http_log_loc_conf_t  *lcf;lcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);.../* r是请求,它是这么来的,在ngx_http_init_request函数里(ngx_http_request.c文件)*/r = ...;cscf = addr_conf->default_server;r->main_conf = cscf->ctx->main_conf;r->srv_conf = cscf->ctx->srv_conf;r->loc_conf = cscf->ctx->loc_conf; 还有个要提的,http配置分main, src, loc,下级的配置可以覆盖上级,很明显,上级只是默认设置值而已。 三、重提模块化设计学着用模块化的角度去看nginx的整体设计,一切以模块为核心,配置依赖于模块,即模块本身就携带着它的配置。正因为这样的设计的,配置文件的解析,使用非常简单。避免过多使用全局变量好像成为一种共识,但是在nginx世界里,全局变量可不少,每个模块都是个全局变量,为什么这样设计呢?因为模块之间是有依赖性的,所以需要互相访问。 配置文件解析这块的代码极具借鉴,本文只管窥般分析了配置文件,不能剥夺读者阅读源码的享受,点到即止。
nginx一向以高性能著称,缓存是其中一大利器,本文将分享nginx中各个级别缓存的原理和内部是如何实现的。 0.什么是缓存缓存存在计算机中的各个领域,其目的都是为了提升处理速度,可以说是以空间换取了时间。但事实并不是这么简单,因为引入了缓存,就要处理一致性、过期处理、缓存策略和命中率等。例如CPU有几级缓存,memcached作为内存缓存服务器,redis也是,还有文件缓存服务器,CDN等。总而言之,访问目标后‘备份’到某中间层,下次再访问时,直接从中间层获取,加快了访问速度。所以目标越往后,处理速度越慢,程序要做的就是尽量命中在前面,不要让访问穿透到后面目标。 1.nginx缓存nginx属于web应用服务器,是典型的request/response模式。整体而言,nginx的缓存可以总结为3个:304响应、静态级别缓存、动态级别缓存。下面一一介绍:  *304响应这个其实不算真正意义上的缓存,之所以提出来,是因为它的重要性,也因为涉及客户端的缓存。当客户端访问某URL时,如果服务器响应304状态码,其意义是告诉客户端您访问的东西没有变化过,内容从自己本地获取。浏览器就是典型的应用。当你第一次访问某静态资源时,响应200,当按F5时,会响应304。实现原理是nginx根据头部信息if_modified_since与静态资源的时间戳比较,如果一样,则返回304。做web开发应该清楚这个状态码。  *静态级别缓存location /test {    open_file_cache  max=100;}这个级别的缓存是缓存静态资源文件的句柄。nginx处理一个请求时,会打开一个文件句柄,然后在请求处理结束后关闭它。加入open_file_cache后,就会将文件句柄保存在工作进程中(不是共享内存),只要工作进程存在,当后面的请求访问同样的文件时,就不用重新打开文件。这样的好处是节省了文件打开关闭的开销。  *动态级别缓存http {    ...    fastcgi_cache_path  /tmp/abc keys_zone=c1:10M;    server {        location ~ \.php$ {            ...            fastcgi_cache  c1;            fastcgi_cache_valid  200 30s;        }    }} 这个级别的缓存是缓存动态请求的内容。当客户端访问某动态文件时(http://example.com/test.php),nginx将请求重新封装成fastcgi协议,然后转发给php-fpm处理,当php-fpm正常响应后,nginx将响应内容保存到某目录的文件里。下次同样的请求过来,就直接从缓存目录的文件中读取,比第一次快且省事很多。  聪明的你可能已经想到动态级别缓存可以再加上静态级别缓存,没错的。这3种响应是独立的,可以灵活配合使用。现在您已经明白缓存的原理,接下来看下如何实现的。  2.304响应ngx_http_not_modified_filter_module处理了这个功能,属于http request header filter模块。源码非常简单。static ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r){    ...    if (r->headers_in.if_modified_since) {         if (r->headers_in.if_modified_since            && ngx_http_test_if_modified(r))        {            return ngx_http_next_header_filter(r);        }         /* not modified */        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;        return ngx_http_next_header_filter(r);    }     return ngx_http_next_header_filter(r);} static ngx_uint_t ngx_http_test_if_modified(ngx_http_request_t *r){    ...     if (r->headers_out.last_modified_time == (time_t) -1) {        return 1;    }     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);     if (clcf->if_modified_since == NGX_HTTP_IMS_OFF) {    ims = ngx_parse_http_time(r->headers_in.if_modified_since->value.data,                              r->headers_in.if_modified_since->value.len);     if (ims == r->headers_out.last_modified_time) {        return 0;    }    if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT        || ims < r->headers_out.last_modified_time)    {        return 1;    }    return 0;} 3.静态级别缓存typedef struct {    ngx_rbtree_t             rbtree;    ngx_rbtree_node_t        sentinel;    ngx_queue_t              expire_queue;    ngx_uint_t               current;    ngx_uint_t               max;    time_t                   inactive; } ngx_open_file_cache_t; 记得这个只在工作进程中处理,不涉及共享内存。看的出来ngx_open_file_cache_t是一个红黑树,也是一个队列。每个文件的文件(根据完整文件名)对应一个节点。这样就完整的维护了所有的可能要缓存的文件。又因为缓存需要处理过期,所以最近访问的文件会保持在expire_queue队列的前面,每次访问将从队列中删除,然后移到最前面。整个逻辑封装在一个函数里:ngx_int_t ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name, ngx_open_file_info_t *of, ngx_pool_t *pool) { ... } 4.动态级别缓存ngx_http_file_cache_t {    ngx_http_file_cache_sh_t {        tree        queue        ...    }}ngx_http_cache_t {    keys    key    ngx_http_file_cache_node_t}如果简化点处理过程就是:根据url算出md5,然后将动态请求响应内容存储到文件名为某md5的文件里。http://example.com/a.php -> d41d8cd98f00b204e9800998ecf8427e -> /tmp/abc/d41d8cd98f00b204e9800998ecf8427ehttp://example.com/b.php -> d41d8cd98f00b204e9800998ecf8432d -> /tmp/abc/d41d8cd98f00b204e9800998ecf8432dhttp://example.com/c.php -> d41d8cd98f00b204e9800998ecf84a32 -> /tmp/abc/d41d8cd98f00b204e9800998ecf84a32因为文件存储在硬盘里,所以这里用到共享内存,而不是工作进程的内存中,来保存这种对应关系。我们可以将每个请求(r)当作各个缓存节点,对应ngx_http_cache_t。ngx_http_file_cache_t就是缓存的集合。明白这个关系后,我们解释下整个流程是如何从开始到结束的。  a)根据fastcgi_cache_path的keys_zone,nginx创建了多个 ngx_http_file_cache_t。根据fastcgi_cache指定了某个location将用到哪个ngx_http_file_cache_t。b)为每个请求创建一个ngx_http_cache_t(在ngx_http_file_cache_new函数里),然后继续产生一个key,作为本节点的索引。你可以将请求、请求响应内容、内容存储文件都当作同一个东西,最终都是节点,对应ngx_http_cache_t。所以需要有       key这个东西。c)处理缓存目录和文件名d)nginx处理动态请求是在ngx_event_pipe里的,当需要缓存里,pipe会有个temp_path将响应内容存储到这个文件里e)最后将pipe的temp_path重命名成前面产生的文件名,结束了。 5.小结动态请求的缓存处理代码还是有点复杂的,有兴趣的同学研究下其如何实现很有好处,可以体会下nginx的代码精致程度。如一惯风格,写作主要以白话方式分享原理,细节点到即止,细节上有疑问欢迎交流。
nginx的配置文件使用简单灵活,某些部分还具备脚本语言的特点,变量就是其中一个特色。本文将分析变量是如何设计实现的。 0. 什么是变量脚本语言都有变量这个东西,其作用就是让内容可变,用名称代替可变的内容,所以变量具有赋值和取值的特点。nginx的变量跟php一样,以$开头。两种用法:赋值:set $some_var nginx;取值:$some_var; 1.整体设计  * 创建所有变量只能在配置文件解析,也就是工作进程启动之前创建,有些是内置的变量,有些是自定义的变量。没什么区别,比如 $http_host是内置变量,set $some_var some_val。通过set指定创建的是自定义变量,当然也可以其它方式,如果你自己写模块的话。这时用到 cmcf->variables_keys, cmcf->variables 两个数组,数组元素类型为ngx_http_variable_t。  * 使用 变量使用(即拿它的值)要先获取索引(发生在配置阶段),这是为了加快访问速度,然后根据索引拿它的值(发生在运行阶段)。这时用到r->variables数组,数组元素为ngx_http_variable_value_t(ngx_variable_value_t的别名)。配置时:n = ngx_http_get_variable_index(cf, name); 运行时:v = ngx_http_get_indexed_variable(r, n);  cmcf->variables_keys 创建的变量都存在这个数组cmcf->varialles 使用的变量(为了拿索引)都存储在这里。nginx会在init conf时检查cmcf->variables的所有变量必须在cmcf->variables_keys里。r->variables 将cmcf->variables导入到这里,为了更方便处理,这时只需要拿值 2、创建变量nginx有内置的变量,分布在好多个模块里,这些变量在配置解析之前构建完成,接下来解析配置,可能碰到变量(内置或自定义),最后处理所有的变量。preconfigure : 添加内置变量,比如 $request, $server_name, $args 等。parse             : 添加自定义变量。init                 : 处理所有变量,比如自定义变量没有对应相同的内置变量,当作错误处理。如果最终处理成功,会有个hash存储这些变量的数据。我们看下内部如何实现的:  * preconfigure阶段:cmcf->variables_keys:所有内置变量都会添加到这个数组里。每个成员的结构体是ngx_http_variable_ttypedef struct {    ngx_str_t                     name;   /* must be first to build the hash */    ngx_http_set_variable_pt      set_handler;    ngx_http_get_variable_pt      get_handler;    uintptr_t                     data;    ngx_uint_t                    flags; # 这个比较重要,看下面解释    ngx_uint_t                    index;} ngx_http_variable_t;   name是名称,set_handler和get_handler分别用于赋值和取值,需要配合data,这几个比较简单。index是索引的意思,可以通过根据这个值拿到对应的变量,具体后面会再讲到。  flags是变量标记,不同的标记使其用法和用途不同,有NGX_HTTP_VAR_CHANGEABLE,NGX_HTTP_VAR_NOCACHEABLE标记。有NGX_HTTP_VAR_CHANGEABLE标记意味着变量是可变的。比如 $server_name是不可变的,你不能这样操作 set $server_name "err"; $args是可变的,就可以这样操作 set $args "ok"; 内置变量在源码里都有指定它的标记,自定义变量都是可变的。  3.使用变量要获取nginx的变量的值有两种方式:索引和变量名  *索引 如前面介绍,先在配置阶段拿索引,然后在运行阶段根据索引拿值 *变量名 ngx_http_variable_value_t * ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key); 在整个配置文件解析处理后,nginx会构造一个hash:cmcf->variables_hash,存储所有的变量。可想而知,根据变量名就可以快速拿到对应的变量。当然用索引的方式更快,至于选哪种视情况而定了。 4. 总结要理解变量,要先理解nginx的两个阶段,解析阶段和运行阶段。解析阶段尽量做事前工作,如创建变量、拿索引等。到了运行阶段就可以快速的拿变量的值。还可以看出nginx的一个重要设计,解析时函数的参数基本有 ngx_conf_t *cf,到了运行阶段就是 ngx_http_request_t *r了。使用到的东西也更简化了,比如变量在解析阶段,需要有get和set(可选),但到了运行阶段,只需要拿值。这种细微的设计变化,可以好好思考,并转化成自己的理解。我经常推荐看nginx源码的同学可以从ngx_log_t *log这个东西入手,因为它反映了整个nginx的生命周期。