深入剖析Nginx一点小笔记

     前几天在图书馆看书,恰好看到这本《深入剖析nginx》,花了快一周的时间看完了这本书,写点笔记心得便于以后复习。

      以前对nginx的认识就只是停留在一个反向代理服务器上。百度了一下nginx也很火,仅次于apache和微软的iis。nginx的主要特点就是占用系统资源少,并发能力强,稳定性好。

    第1,2章主要讲了下基本的代码分析的准备工作,介绍了一些便于调试代码的工具,以及在linux环境下运用gdb对其代码进行调试,这里不多描述。

    第3章主要介绍了Nginx的进程模型。一般情况下,在启动Nginx后,将会出现多个Nginx进程,各个进程各司其职共同完成对客户端处理响应的任务。从整体架构上来看,有监控进程(也叫做主进程),也有工作进程以及缓存进程。监控进程大部分时间都处于挂起等待状态,直到监控进程收到信号为止,通俗点来说也就是有客户端发送请求。对于nginx的工作进程,此时它充当了客户端与后端服务器之间的代理服务器,它的重心主要就是客户端与后端服务器之间的I数据读写的I/O交互事件,而不是进程信号。关于缓存进程,它不处理客户端的请求,就是它不管数据的读写操作,它只管超时操作。如果开启缓存功能,则会同时开启两个缓存进程,分别是缓存管理进程和缓存加载进程。缓存管理进程的主要任务就是清理超时缓存文件,限制缓存文件的总大小,此后这个过程来回往复,一直到整个进程退出为止。缓存加载进程一般是在nginx正常启动后(一般为60秒)将磁盘中上次缓存的对象加载到内存中,这个过程一般是一次性的,当缓存加载进程完成它的任务以后它就自动退出了。

     进程间通信,这里介绍了第一种channel方式的通信,其实channel就是一个元素个数为2的一个数组,里面存放着一对socket描述符,对于继承了父进程的子进程,他们都拥有了这一对socket描述符,而Nginx将channel[0]给父进程使用,channel[1]给子进程使用,利用数组下标的不同来错开的使用不同的描述符,从而来实现父子进程之间的通信。但是子进程并没有向父进程发送任何消息,子进程之间也没有相互进行通信的逻辑,即channel通信目前只作为父进程来给子进程发送消息使用。

     进程间通信的第二种方式,也是在linux下最有效的进程通信的方式之一,共享内存。在nginx中,共享内存使用一个结构体来表示,其中封装的有共享内存的名字(主要用作共享内存的唯一标识,便于让nginx知道我们想使用的哪一块共享内存),大小,标签(主要用于解决不同模块使用相同名字的共享内存而引发的冲突,使用标签后,比如模块A和模块B以相同的名称sa去获得模块A所创建的共享内存sa,模块A将获得它之间创建的共享内存sa的引用,而模块B则将获得一个共享内存sa已有他用的错误提示)以及分配内存的起始地址。在Nginx配置解析完成后,所有共享内存结构体将连在一起构成一个全局链表。Nginx此时通过遍历链表来实现实际的分配内存,管理机制初始化(例如锁,slab)等。一个完整的共享内存的初始化主要基于slab高效的访问机制,而关于共享内存的使用,由于是多进程共同使用共享内存,则要考虑到进程间的互斥问题,记得以前在上java课的时候老师讲多线程的时候也讲过锁的机制,运用在这里大概就是强制同一时刻只能有一个进程去访问共享内存。 关于Nginx的slab机制主要是两点:缓存和对齐,缓存意味着提前申请好内存并对内存进行划分形成内存池,当我们需要一块内存空间时,Nginx直接从内存池中取出一块大小合适的内存空间即可,而内存释放也是把内存又重新返还给内存池,而不是操作系统。对齐意味着内存的申请总是遵循2的幂次方,8,16,32,64,如果只申请33个字节的内存,也将获得64字节的内存,虽然存在内存的浪费,但有利于提升性能。Nginx的共享内存与slab机制共同使用,对于共享内存(一个以结构体为节点的全局链表),在初始化完成之后,就由slab机制来对它进行内部的划分以及管理。信号处理部分,通过对signal信号的处理,使得Nginx支持与用户进行信息交互(比如在不终止Nginx服务的情况下更新配置)。

       第四章主要讲的是Nginx里用到的稍微复杂一点的数据结构(内存池,哈希)。内存池的实现用到了指针以及链表,链表节点也就是相当于是内存池节点。这里要区别一下大小内存的分配问题,对于小内存的分配,新链表节点的加入都是在链表尾部进行的,通过遍历链表的方式来搜索可以进行分配的内存,对与不可分配的内存池节点,下次再次进行内存分配时可以直接跳过。对于分配大块内存,Nginx里可以调用系统API接口向系统申请内存,一般对于连续的大块内存的分配,会挂载在链表节点的头部,这一点区别于小块内存的分配。Nginx仅提供对大块内存的释放,而没有对小块内存的释放。即从内存池中分配出去的内存不会再回收到内存池中来,而只有在销毁整个内存池时,所有这些内存才会回收到系统中来。Nginx的这种特性取决于它作为webserver的特殊性,即阶段和时效,对于其处理的业务逻辑有明确的阶段,而对于每一个阶段又有明确的时效性,因此Nginx可以针对阶段来分配内存,针对时效来销毁内存池。比如当一个阶段(一个request请求处理)开始(或其过程中)就创建内存池,在一段时间后,必定会因为正常处理,异常错误或超时等而结束,即不会出现Nginx长时间占据大量无用内存池的情况,所以在其阶段过程中回收不用的小块内存自然是不必要的,等到时间一起回收就好了。但是内存池的使用也会带来诸如使valgrind无法正确捕获内存中异常的问题(Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具),当遇到一些奇怪的内存问题无法处理时,可以禁用Nginx的内存池,直接采用mallc/free接口来直接使用系统内存,避免内存池的干扰。Hash,以前数据结构课上了解到主要用于元素的查找,而影响查找效率的因素就是冲突。Nginx解决冲突主要有两种,第一种是采用好的映射函数,这里采用的是两重计算,先计算hash键值key(这是一个字符串)的哈希码hashcode,然后对hashcode做一个对一个按实际hash内存空间大小取模运算就得到其在这个内存空间的具体位置。第二种是选一种合适的空间大小。hash结构创建之后就不可再修改,只供高效查找。

     第五章主要讲的是Nginx的配置解析,使用近似于key-value对的形式,这种形式只针对对配置文件静态格式上的描述。Nginx的配置文件可以认为是一种上下文相关,高度可扩展的,有作用域以及可自定义变量等诸多高级语言特性的脚本语言。Nginx的配置文件是由多个配置项组成的,每一个配置项都有一个项目名和对应的项目值,项目名又称为指令,而项目值可能是简单的字符串(以分号结尾),也可能是由简单字符串和多个配置项组合而成配置块的复合结构(以大括号}结尾),我们可以将配置项归纳为两种,简单配置项以及复杂配置项。

 1 error log /var/log/nginx.error_log info; 

这一条指令不带大括号,所以是一个简单配置项。

 1 location ~ \.php${  fastcgi_pass 127.0.0.1:1025;  }   

这一条指令带大括号,所以是一个复杂配置项。

Nginx配置文件里面的注释信息用#作为开头标记。

    第六章模块综述主要介绍了四大模块,Nginx的模块不像apache或者lighttpd在编译时生成so动态库,然后在程序执行时动态加载,而是在生成Nginx时就直接被编译到二进制可执行文件中,即如果要选用不同的功能模块,则需要重新对nginx进行配置和编译。①handlers:协同完成客户端请求的处理、产生响应数据,比如ngx_http_rewrite_moudle模块,用于处理客户端请求的地址重写,ngx_http_static_moudle模块,负责处理客户端的静态页面请求,ngx_http_log_moudle模块,负责记录请求访问日志。在客户端的请求被Nginx接收后,首先做server的查找与定位。 如果直接访问的是一个目录,Nginx先是查看当前目录是否存在index..html/index.htm/index.php等这样的默认页面<ngx_http_index_handler()的工作>,如果不存在默认页面,就返回一个文件列表页面<ngx_http_autoindex_handler()的工作>,而ngx_http_static_handler()是根据客户端静态页面请求查找对应的页面文件并组成待相应内容。②filters:对handlers产生的响应数据做各种过滤处理(即增/删/改),比如模块ngx_http_not_modified_filter_moudle,对等待响应数据进行过滤检测,如果通过时间戳判断出前后两次请求的响应数据没有发生任何实质改变,那么可以直接响应“304 Not Modified”状态标识,让客户端使用本地缓存即可,而原本待发送的响应数据将被清除掉。所有的header过滤功能函数和body过滤功能函数会分别组成各自的过滤链(链上含有针对各种元素的过滤函数)。根据HTTP协议具备的响应头影响或决定响应体内容的特点,一般先是对响应头进行过滤,根据对响应头过滤的返回值再对响应体进行过滤,如果对响应头的过滤中出错或某些特定情况下,则响应体过滤可不再进行。③upstream:如果存在后端真实服务器,Nginx可利用upstream模块充当反向代理(Reverse Proxy)的角色,对客户端发起的请求只负责进行转发(当然也包括对后端真实服务器响应数据的回转),比如ngx_http_proxy_moudle就为标准的upstream模块。upstream模块与具体的协议无关,其除了支持HTTP协议外,还支持包括FASTCGI,SCGI,(这两种都是对标准cgi的优化替代)UWSGI(一种web服务器,它实现了WSGI协议、uwsgi、http等协议),MEMCACHED(Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。)等在内的多种协议。upstream模块的典型应用是反向代理,例如ngx_http_proxy_moudle模块,客户端对服务器80端口的请求都被Nginx Proxy Server 转发到另外两个真实的Nginx web server 实例上进行处理(web server 和proxy server都只是两个进程,并且运行在同一服务器)。如果禁用了反向代理的缓存功能,则每次客户端的请求都被转发到后端真实服务器进行处理,更便于对每次Nginx的执行流程进行分析④load-banlance:主要为upstream模块服务,在Nginx充当中间代理角色时,由于后端真实服务器往往多于一个,对于某一次客户端的请求,有ngx_http_upstream_ip_hash_moudle这样的load banlance模块来解决如何选择对应的后端真实服务器的问题,这里的ip_hash也是一种负载均衡算法。

     第10章是请求定位,对于任何一个客户端的请求,在Nginx内都必须有与之对应的server以及location来匹配,以提供处理该请求的上下文环境,否则Nginx将无法进行正常处理而返回错误。在一般的应用中,Nginx内的server和location会有多个,这一章主要解决如何将客户端的请求正确定位到对应的server和location。即便是同一个server里,Nginx的location一般也会有多个,以便于灵活地处理客户端的各种请求。location可以简单理解成就是我们在客户端向服务端所请求的一个地址,而服务端用location来进行相应的匹配,Nginx一般采用最佳匹配。对于在server内部的不同location,如果没有上下层的嵌套,则在配置文件解析后,以队列形式存在。如果有多个location的层次嵌套,则形成一棵树,树的节点为队列。形成这样的树的好处在于在整个http配置解析完以后,所有的location都成功收集并已根据各自所属server形成多棵不同的树(不同的server的location不会相互干扰,因为对于请求的处理,先定位到具体server,再在这个server之内去查找对应的location)。为一台服务器配置不同的网站也就是开了不同的虚拟主机,Nginx可以通过不同的域名来决定把用户引到哪一台虚拟主机来进行操作。

posted @ 2016-12-26 00:08  tr1ple  阅读(2452)  评论(0编辑  收藏  举报