日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

《深入剖析NGINX》学习记录

發(fā)布時間:2024/1/23 编程问答 61 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《深入剖析NGINX》学习记录 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1.HTTP服務(wù)基本特性

  • ? 處理靜態(tài)頁面請求;
  • ? 處理index首頁請求
  • ? 對請求目錄進(jìn)行列表顯示;
  • ? 支持多進(jìn)程間的負(fù)載均衡;
  • ? 對打開文件描述符進(jìn)行緩存(提高性能);
  • ? 對反向代理進(jìn)行緩存(加速);
  • ? 支持gzip、ranges、chunked、XSLT、SSI以及圖像縮放;
  • ? 支持SSL、TLS SNI

2.HTTP服務(wù)高級特性

  • ? ? 基于名稱的虛擬主機(jī);
  • ? ? 基于IP的虛擬主機(jī);
  • ? ? 支持Keep-alive和pipelined連接;
  • ? ? 靈活和方便的配置;
  • ? ? 在更新配置和升級執(zhí)行程序時提供不間斷服務(wù);
  • ? ? 可自定義客戶端訪問的日志格式;
  • ? ? 帶緩存的日志寫操作(提高性能);
  • ? ? 支持快速的日志文件切換;
  • ? ? 支持對3xx-5xx錯誤代碼進(jìn)行重定向;
  • ? ? URI重寫支持正則表達(dá)式;
  • ? ? 根據(jù)客戶端地址執(zhí)行不同的功能;
  • ? ? 支持基于客戶端IP地址的訪問控制;
  • ? ? 支持基于HTTP基本認(rèn)證機(jī)制的訪問控制;
  • ? ? 支持HTTP referer驗證;
  • ? ? 支持HTTP協(xié)議的PUT、DELETE、MKCOL、COPY以及MOVE方法;
  • ? ? 支持FLV流和MP4流;
  • ? ? 支持限速機(jī)制;
  • ? ? 支持單客戶端的并發(fā)控制;
  • ? ? 支持Perl腳本嵌入;

3.郵件代理服務(wù)特性

  • ? ? 使用外部HTTP認(rèn)證服務(wù)器將用戶重定向到IMAP/POP3服務(wù)器;
  • ? ? 使用外部HTTP認(rèn)證服務(wù)器將用戶重定向到內(nèi)部SMTP服務(wù)器;
  • ? ? 支持的認(rèn)證方式;? ? ?

? ? ? ? ? ? POP3: USER/PASS、APOP、AUTHLOGIN/PLAIN/CRAM-MD5.

? ? ? ? ? ? IMAP:sLOGIN、AUTHLOGIN/PLAIN/CRAM-MD5.

? ? ? ? ? ? SMTP: AUTHLOGIN/PLAIN/CRAM-MD5.

  • ? ? 支持SSL;
  • ? ? 支持STARTTLS和STLS.

4.架構(gòu)和擴(kuò)展性

  • ? ? 一個主進(jìn)程和多個工作進(jìn)程配合服務(wù)的工作模型;
  • ? ? 工作進(jìn)程以非特權(quán)用戶運行(安全性考慮);
  • ? ? 支持的事件機(jī)制有:kqueue(FreeBSD 4.1+)、epoll(Line 2.6+)、rt signals(Linux 2.2.19+)、/dev/poll(Solaris 7 11/99+)、event ports(Solaris 10)、select和poll;
  • ? ? 支持kqueue的眾多特性,包括EV_CLEAR、EV_DISABLE(臨時禁止事件)、NOTE_LOWAT、EV_EOF等;
  • ? ? 支持異步文件IO(FreeBSD4.3+、Linux2.6.22+);
  • ? ? 支持DIRECTIO(FreeBSD 4.4+、Linux 2.4+、Solaris 2.6+、Mac OS X);
  • ? ? 支持Accept-filters(FreeBSD 4.1+、NetBSD 5.0+)和TCP_DEFER_ACCEPT(Linux 2.4+);
  • ? ? 10000個非活躍HTTP keep-alive連接僅占用約2.5MB內(nèi)存;

5.已測試過的操作系統(tǒng)和平臺

  • ? ? FreeBSD 3~10/i386、FreeBSD 5~10/amd64;
  • ? ? Linux 2.2~3/i386、Linux 2.6~3/amd64;
  • ? ? Solaris 9/i386、sun4u、Solaris 10/i386、amd64、sun4v;
  • ? ? AIX 7.1/powerpc;
  • ? ? HP-UX 11.31/ia64;
  • ? ? Max OS X/ppc、i386;
  • ? ? Windows XP、Windows Server 2003.

6.由于strace能夠提供Nginx執(zhí)行過程中的這些內(nèi)部信息,所以在出現(xiàn)一些奇怪現(xiàn)象時,比如Nginx啟動失敗、響應(yīng)的文件數(shù)據(jù)和預(yù)期不一致、莫名其妙的Segment action Fault段錯誤、存在性能瓶頸(利用-T選項跟蹤各個函數(shù)的消耗時間),利用strace也許能夠提供一些相關(guān)幫助,最后,要退出strace跟蹤,按Ctrl+C即可。

? ? ?pstack的使用非常簡單,后面跟進(jìn)程ID即可。比如在無客戶端請求的情況下,Nginx阻塞在epoll_wait系統(tǒng)調(diào)用處,此時利用pstack查看到的Nginx函數(shù)調(diào)用堆棧關(guān)系。

?7.利用addr2line工具可以將這些函數(shù)地址轉(zhuǎn)換回可讀的函數(shù)名。

8.整體架構(gòu)

? ? 正常執(zhí)行起來后的Nginx會有多個進(jìn)程,最基本的有master_process(即監(jiān)控進(jìn)程,也叫主進(jìn)程)和worker_process(即工作進(jìn)程),也可能會有Cache相關(guān)進(jìn)程。這些進(jìn)程之間會相互通信,以傳遞一些信息(主要是監(jiān)控進(jìn)程往工作進(jìn)程傳遞)。除了自身進(jìn)程之間的相互通信,Nginx還憑借強(qiáng)悍的功能模塊與外界四通八達(dá),比如通過upstream與后端Web服務(wù)器通信、依靠fastcgi與后端應(yīng)用服務(wù)器通信等。一個較為完整的整體框架結(jié)構(gòu)體如圖所示:

? ??

?9.分析Nginx多進(jìn)程模型的入口為主進(jìn)程的ngx_master_process_cycle()函數(shù),在該函數(shù)做完信號處理設(shè)置等之后就會調(diào)用一個名為ngx_start_worker_processes()的函數(shù)用于fork()產(chǎn)生出子進(jìn)程(子進(jìn)程數(shù)目通過函數(shù)調(diào)用的第二個實參指定),子進(jìn)程作為一個新的實體開始充當(dāng)工作進(jìn)程的角色執(zhí)行ngx_worker_process_cycle()函數(shù),該函數(shù)主體為一個無限for(;;)循環(huán),持續(xù)不斷地處理客戶端的服務(wù)請求,而主進(jìn)程繼續(xù)執(zhí)行ngx_master_process_cycle()函數(shù),也就是作為監(jiān)控進(jìn)程執(zhí)行主體for(;;)循環(huán),這自然也是一個無限循環(huán),直到進(jìn)程終止才退出。服務(wù)進(jìn)程基本都是這種寫法,所以不用詳述。

? ?

下圖表現(xiàn)的很清晰,監(jiān)控進(jìn)程和每個工作進(jìn)程各有一個無限for(;;)循環(huán),以便進(jìn)程持續(xù)的等待和處理自己負(fù)責(zé)的事務(wù),直到進(jìn)程退出。

? ??

10.監(jiān)控進(jìn)程

? ? 監(jiān)控進(jìn)程的無限for(;;)循環(huán)內(nèi)有一個關(guān)鍵的sigsuspend()函數(shù)調(diào)用,該函數(shù)的調(diào)用使得監(jiān)控進(jìn)程的大部分時間都處于掛起等待狀態(tài),直到監(jiān)控進(jìn)程接收到信號為止。當(dāng)監(jiān)控進(jìn)程接收到信號時,信號處理函數(shù)ngx_signal_handler()就會被執(zhí)行。我們知道信號處理函數(shù)一般都要求足夠簡單,所以在該函數(shù)內(nèi)執(zhí)行的動作主要也就是根據(jù)當(dāng)前信號值對相應(yīng)的旗標(biāo)變量做設(shè)置,而實際的處理邏輯必須放在主體代碼里來進(jìn)行,所以該for(;;)循環(huán)接下來的代碼就是判斷有哪些旗標(biāo)變量被設(shè)置而需要處理的,比如ngx_reap(有子進(jìn)程退出?)、ngx_quit或ngx_terminate(進(jìn)行要退出或終止?值得注意的是,雖然兩個旗標(biāo)都是表示結(jié)束Nginx,不過ngx_quit的結(jié)束更優(yōu)雅,它會讓Nginx監(jiān)控進(jìn)程做一些清理工作且等待子進(jìn)程也完全清理并退出之后才終止,而ngx_terminate更為粗暴,不過它通過使用SIGKILL信號能保證在一段時間后必定被結(jié)束掉)、ngx_reconfigure(重新加載配置)等。當(dāng)所有信號都處理完時又掛起在函數(shù)sigsuspend()調(diào)用處繼續(xù)等待新的信號,如此反復(fù),構(gòu)成監(jiān)控進(jìn)程的主要執(zhí)行體。

11.工作進(jìn)程

? ? 工作進(jìn)程的主要關(guān)注點就是與客戶端或后端真實服務(wù)器(此時Nginx作為中即代理)之間的數(shù)據(jù)可讀/可寫等I/O交互事件,而不是進(jìn)程信號,所以工作進(jìn)程的阻塞點是在像select()、epoll_wait()等這樣的I/O多路復(fù)用函數(shù)調(diào)用處,以等待發(fā)生數(shù)據(jù)可讀/可寫事件,當(dāng)然,也可能被新收到的進(jìn)程信號中斷。

12.cache manager process(Cache管理進(jìn)程)與cache loader process(Cache加載進(jìn)程)則是與Cache緩存機(jī)制相關(guān)的進(jìn)程。它們也是由主進(jìn)程創(chuàng)建,對應(yīng)的模型框圖如下所示:

? ??

? ? Cache進(jìn)程不處理客戶端請求,也就沒有監(jiān)控的I/O事件,而其處理的是超時事件,在ngx_process_events_and_timers()函數(shù)內(nèi)執(zhí)行的事件處理函數(shù)只有ngx_event_expire_timers()函數(shù)。

13.Cache管理進(jìn)程的任務(wù)就是清理超時緩存文件,限制緩存文件總大小,這個過程反反復(fù)復(fù),直到Nginx整個進(jìn)程退出為止。

14.采用socketpair()函數(shù)創(chuàng)造一對未命名的UNIX域套接字來進(jìn)行Linux下具有親緣關(guān)系的進(jìn)程之間的雙向通信是一個非常不錯的解決方案。Nginx就是這么做的,先看fork()生成新工作進(jìn)程的ngx_spawn_process()函數(shù)以及相關(guān)代碼。

代碼片段3.4-1,文件名: ngx_process.h typedef struct {ngx_pid_t pid;int status;ngx_socket_t channel[2];... } ngx_process_t; ... #define NGX_MAX_PROCESSES 1024 代碼片段3.4-2, 文件名: ngx_process.c ngx_process_t ngx_processes[NGX_MAX_PROCESSES];ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) {...if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)...pid = fork();... }

? 15.共享內(nèi)存是Linux下進(jìn)程之間進(jìn)行數(shù)據(jù)通信的最有效方式之一,而Nginx就為我們提供了統(tǒng)一的操作接口來使用共享內(nèi)存。

? ? ? ?在Nginx里,一塊完整的共享內(nèi)存以結(jié)構(gòu)體Ngx_shm_zone_t來封裝表示,其中包括的字段有共享內(nèi)存的名稱(shm_zone[i].shm_name)、大小(shm_zone[i].shm.size)、標(biāo)簽(shm_zone[i].tag)、分配內(nèi)存的起始地址(shm_zone[i].shm.addr)以及初始回調(diào)函數(shù)(shm_zone[i].init)等。

代碼片段3.5-1,文件名: ngx_cycle.h typedef struct ngx_shm_zone_s ngx_shm_zone_t; ... struct ngx_shm_zone_s {void *data;ngx_shm_t shm;ngx_shm_zone_init_pt init;void *tag; };

16.共享內(nèi)存的真正創(chuàng)建是在配置文件全部解析完后,所有代表共享內(nèi)存的結(jié)構(gòu)體ngx_shm_zone_t變量以鏈表的形式掛載在全局變量cf->cycle->shared_memory下,Nginx此時遍歷該鏈表并逐個進(jìn)行實際創(chuàng)建,即分配內(nèi)存、管理機(jī)制(比如鎖、slab)初始化等。

17.Nginx互斥鎖接口函數(shù)

函數(shù)含義
ngx_shmtx_create()創(chuàng)建
ngx_shmtx_destory()銷毀
ngx_shmtx_trylock()嘗試加鎖(加鎖失敗則直接返回,不等待)
ngx_shmtx_lock()加鎖(持續(xù)等待,知道加鎖成功)
ngx_shmtx_unlock()解鎖
ngx_shmtx_force_unlock()強(qiáng)制解鎖(可對其它進(jìn)程進(jìn)行解鎖)
ngx_shmtx_wakeup()喚醒等待加鎖進(jìn)程(系統(tǒng)支持信號量的情況下才可使用)

18.Nginx的slab機(jī)制與Linux的slab機(jī)制在基本原理上并沒有什么特別大的不同(當(dāng)然,相比而言,Linux的slab機(jī)制要復(fù)雜得多),簡單來說也就是基于兩點:緩存與對齊。緩存意味著預(yù)分配,即提前申請好內(nèi)存并對內(nèi)存做好劃分形成內(nèi)存池,當(dāng)我們需要使用一塊內(nèi)存空間時,Nginx就直接從已經(jīng)申請并劃分好的內(nèi)存池里取出一塊合適大小的內(nèi)存即可,而內(nèi)存的釋放也是把內(nèi)存返還給Nginx的內(nèi)存池,而不是操作系統(tǒng);對齊則意味著內(nèi)存的申請與分配總是按2的冪次方進(jìn)行,即內(nèi)存大小總是為8、16、32、64等,比如,雖然只申請33個字節(jié)的內(nèi)存,但也將獲得實際64字節(jié)可用大小的內(nèi)存,這的確存在一些內(nèi)存浪費,但對于內(nèi)存性能的提升是顯著的,更重要的是把內(nèi)部碎片也掌握在可控的范圍內(nèi)。

? ? Nginx的slab機(jī)制主要是和共享內(nèi)存一起使用,前面提到對于共享內(nèi)存,Nginx在解析完配置文件,把即將使用的共享內(nèi)存全部以list鏈表的形式組織在全局變量cf->cycle->shared_memory下之后,就會統(tǒng)一進(jìn)行實際的內(nèi)存分配,而Nginx的slab機(jī)制要做的就是對這些共享內(nèi)存進(jìn)行進(jìn)一步的內(nèi)部劃分與管理。

19.函數(shù)ngx_init_zone_pool()是在共享內(nèi)存分配號后進(jìn)行的初始化調(diào)用,而該函數(shù)內(nèi)又調(diào)用了本節(jié)結(jié)束騷的重點對象slab的初始化對象ngx_slab_init();此時的情況如圖:

? ?

20.常變量的值與描述

變量名描述
ngx_pagesize4096

系統(tǒng)內(nèi)存頁大小,Linux下一般情況就是4KB

ngx_pagesize_shift12對應(yīng)ngx_pagesize(4096),即是4096=1<<12;
ngx_slab_max_size2048slots分配和pages分配的分割點,大于等于該值則需從pages里分配
ngx_slab_exact_size128

正好能用一個uintptr_t類型的位圖變量表示的頁劃分;比如在4KB內(nèi)存頁、32位系統(tǒng)環(huán)境下,一個uintptr_t類型的位圖變量最多可以對應(yīng)表示32個劃分塊的裝填,所以要恰好完整地表示一個4KB內(nèi)存頁的每一個劃分塊狀態(tài),必須把這個4KB內(nèi)存頁劃分為32塊,即每一塊大小為:

ngx_slab_exact_size = 4096/32=128

ngx_slab_exact_shift7對應(yīng)ngx_slab_exact_size(128),即是128=1<<7;
pool->min_shift3固定值為3
pool->min_size8固定值為8,最小劃分塊大小,即是1<<pool->min_shift;

? 再來看slab機(jī)制對page頁的管理,初始結(jié)構(gòu)示意圖如下所示:

??

? 21.Nginx對所有發(fā)往其自身的信號進(jìn)行了統(tǒng)一管理,其封裝了一個對應(yīng)的ngx_signal_t結(jié)構(gòu)體來描述一個信號。

代碼片段3.7.1-1,文件名:ngx_process.c typedef struct {int signo;char *signame;char *name;void (*handler)(int signo); } ngx_signal_t;

? ? 其中字段signo也就是對應(yīng)的信號值,比如SIGHUP、SIGINT等。

? ? 字段signame為信號名,信號值所對應(yīng)宏的字符串,比如“SIGHUP”。字段name和信號名不一樣,名稱表明該信號的自定義作用,即Nginx根據(jù)自身對該信號的使用功能而設(shè)定的一個字符串,比如SIGHUP用于實現(xiàn)"在不終止Nginx服務(wù)的情況下更新配置"的功能,所以對應(yīng)的該字段為"reload"。字段handler,處理信號的回調(diào)函數(shù)指針,未直接忽略的信號,其處理函數(shù)全部為函數(shù)ngx_signal_handler()。

22.字符串宏操作

宏定義說明舉例
#define Conn(x,y) x##y子串x和y連接起來形成新的串

int n = Conn(123,456);

結(jié)果為: n=123456;

char *str = Conn("abc", "def");

結(jié)果為:str = "abcdef";

#define ToChar(x) #@x給x加上單引號,因此返回是一個const字符,另外,x長度不可超過4

char a = ToChar(a);

結(jié)果為:a = 'a';

char a = ToChar(abcd);

結(jié)果為:a = 'd';

char a = ToChar(abcde);

結(jié)果為:error C2015: too many characters in constant

#define ToString(x) #x給x加上雙引號,因此返回是一個字符串

char *str = ToString(abcde);

結(jié)果為:str="abcde";

23.對信號進(jìn)行設(shè)置并生效是在fork()函數(shù)調(diào)用之前進(jìn)行的,所以工作進(jìn)程等都能受此作用。當(dāng)然,一般情況下,我們不會向工作進(jìn)程等子進(jìn)程發(fā)送控制信息,而主要是向監(jiān)控進(jìn)程父進(jìn)程發(fā)送,父進(jìn)程收到信號做相應(yīng)處理后,再根據(jù)情況看是否要把信號再通知到其他所有子進(jìn)程。

24.ngx_pool_t結(jié)構(gòu)圖

? ?

25.

void *ngx_palloc(ngx_pool_t *pool, size_t size) void *ngx_pnalloc(ngx_pool_t *pool, size_t size) void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment) void *ngx_pcalloc(ngx_pool_t *pool, size_t size) static void *ngx_palloc_block(ngx_pool_t *pool, size_t size) static void *ngx_palloc_large(ngx_pool_t *pool, size_t size)

26.申請大塊內(nèi)存

27.釋放大塊內(nèi)存

? ?

28.資源釋放

??

? 來看內(nèi)存池的釋放問題,從代碼中不難看出Nginx僅提供對大塊內(nèi)存的釋放(通過接口ngx_pfree()),而沒有提供對小塊內(nèi)存的釋放,這意味著從內(nèi)存池里分配出去的內(nèi)存不會再回收到內(nèi)存池里來,而只有在銷毀整個內(nèi)存池時,所有這些內(nèi)存才會回收到系統(tǒng)內(nèi)存里,這里Nginx內(nèi)存池一個很重要的特點,前面介紹的很多內(nèi)存池設(shè)置于處理也都是基于這個特點。

? ?Nginx內(nèi)存池這樣設(shè)計的原因在于Web Server應(yīng)用的特殊性,即階段與時效,對于其處理的業(yè)務(wù)邏輯分憂明確的階段,而對每一個階段又有明確的時效,因此Nginx可針對階段來分配內(nèi)存池,針對時效來銷毀內(nèi)存池。比如,當(dāng)一個階段(比如request處理)開始(或其過程中)就創(chuàng)建對應(yīng)所需的內(nèi)存池,而當(dāng)這個階段結(jié)束時就銷毀其對應(yīng)的內(nèi)存池,由于這個階段有嚴(yán)格的時效性,即在一段時間后,其必定會因正常處理、異常錯誤或超時等而結(jié)束,所以不會出現(xiàn)Nginx長時間占據(jù)大量無用內(nèi)存池的情況。

29.Nginx Hash數(shù)據(jù)結(jié)構(gòu)的創(chuàng)建過程有點復(fù)雜,這從其初始函數(shù)ngx_hash_init()就占去200多行可知一二,但這種復(fù)雜是源于Nginx對高效率的極致追求。

? ??

? ?

? 30.基樹(Radix tree),是一種基于二進(jìn)制表示鍵值的二叉查找樹,正是由于其鍵值的這個特點,所以只有在特定的情況下才會使用,典型的應(yīng)用場景有文件系統(tǒng)、路由表等。

? ?

31.配置文件格式結(jié)構(gòu)圖

? ?

32.Nginx利用ngx_command_s數(shù)據(jù)類型對所有的Nginx配置項進(jìn)行了統(tǒng)一的描述。

代碼片段5.2-2,文件名:ngx_conf_file.h struct ngx_command_s {ngx_str_t name;ngx_uint_t type;char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);ngx_uint_t conf;ngx_uint_t offset;void *post; };

? ? 其中字段name指定與其對應(yīng)的配置項目的名稱,字段set指向配置指令處理回調(diào)函數(shù),而字段offset指定轉(zhuǎn)換后控制值的存放位置。

33.ngx_conf_parse()函數(shù)體代碼量不算太多,但是它照樣也將配置內(nèi)容的解析過程分得很清楚,總體來看分成以下三個步驟:

? ? a.判斷當(dāng)前解析狀態(tài)。

? ? b.讀取配置標(biāo)記token。

? ? c.當(dāng)讀取了合適數(shù)量的標(biāo)記token后對其進(jìn)行實際的處理,也就是將配置值轉(zhuǎn)換為Nginx內(nèi)對應(yīng)控制變量的值。

34.在判斷好當(dāng)前解析狀態(tài)之后就開始讀取配置文件內(nèi)容,前面已經(jīng)提到配置文件都是由一個個token組成的,因此接下來就是循環(huán)從配置文件里讀取token,而ngx_conf_read_token()函數(shù)就是用來做這個事情的。

rc = ngx_conf_read_token(cf);

? 函數(shù)ngx_conf_read_token()對配置文件進(jìn)行逐個字符掃描并解析出單個的token。當(dāng)然,該函數(shù)并不會頻繁的去讀取配置文件,它每次將從文件內(nèi)讀取足夠多的內(nèi)容以填滿一個大小為NGX_CONF_BUFFER(4096)的緩存區(qū)(除了最后一次,即配置文件剩余內(nèi)容本來就不夠了),這個緩存區(qū)在函數(shù)ngx_conf_parse()內(nèi)申請并保存引用到變量cf->conf_file->buffer內(nèi),函數(shù)ngx_conf_read_token()反復(fù)使用該緩存區(qū),該緩存區(qū)可能有如下一些狀態(tài)。

? ?初始狀態(tài),即函數(shù)ngx_conf_parse()內(nèi)申請緩存區(qū)后的初始狀態(tài),如下圖所示:

? ??

? ?處理過程中的中間狀態(tài),有一部分配置內(nèi)容已經(jīng)被解析為一個個token并保存起來,而有一部分內(nèi)容正要被組合成token,還有一部分內(nèi)容等待處理,如下圖所示:

? ??

? ? 已解析字符和已掃描字符都屬于已處理字符,但它們又是不同的:已解析字符表示這些字符已經(jīng)被作為token額外保存起來了,所以這些字符已經(jīng)完全沒用了;而已掃描字符表示這些字符還未組成一個完整的token,所以它們還不能被丟棄。

? ? 當(dāng)緩存區(qū)里的字符都處理完時,需要繼續(xù)從打開的配置文件中讀取新的內(nèi)容到緩沖區(qū),此時的臨界狀態(tài)為,如下圖所示:

? ??

? ? ? 前面圖示說過,已解析字符已經(jīng)沒用了,因此我們可以將已掃描但還未組成token的字符移動到緩存區(qū)的前面,然后從配置文件內(nèi)讀取內(nèi)容填滿緩存區(qū)剩余的空間,情況如下圖所示:

? ? ?

? ? 如果最后一次讀取配置文件內(nèi)容不夠,那么情況如下圖所示:

? ??

? 35.下表列出了ngx_conf_parse()函數(shù)在解析nginx.conf配置文件時每次調(diào)用ngx_conf_read_token()函數(shù)后的cf->args里存儲的內(nèi)容是什么(這通過gdb調(diào)試Nginx時在ngx_conf_file.c:185處加斷點就很容易看到這些信息),這會大大幫助對后續(xù)內(nèi)容的理解。

? ? cf->args里存儲內(nèi)容實例

次數(shù)返回值rccf->args存儲內(nèi)容
第1次NGX_OK

(gdb)p(*cf->args)->nelts

$43 = 2

(gdb)p*((ngx_str_t*)((*cf->args)->elts))

$44 = {len = 16, data = 0x80ec0c8 "worker_processes"}

(gdb)p*(ngx_str_t*)((*cf->args)->elts+sizeof(ngx_str_t))

$45 = {len = 1, data = 0x80ec0da "2"}

第2次NGX_OK

(gdb)p(*cf->args)->nelts

$46 = 3

(gdb)p*((ngx_str_t*)((*cf->args)->elts))

$47 = {len = 9, data = 0x80ec0dd "error_log"}

(gdb)p * (ngx_str_t*)((*cf->args)->elts+sizeof(ngx_str_t))

$48 = {len = 14, data = 0x80ec0e8 "logs/error.log"}

(gdb)p*(ngx_str_t*)((*cf->args)->elts + 2 *sizeof(ngx_str_t))

$49 = {len = 5, data = 0x80ec0f8 "debug"}

第3次NGX_CONF_BLOCK_START

(gdb)p(*cf->args)->nelts

$52 = 1

(gdb)p*((ngx_str_t *)((*cf->args)->elts))

$53 = {len = 6, data = 0x80ec11f"events"}

第...次......
第6次NGX_CONF_BLOCK_DONE

(gdb)p(*cf->args)->nelts

$58 = 0

第...次......
第n次NGX_CONF_BLOCK_START

(gdb)p(*cf->args)->nelts

$74 = 2

(gdb)p*((ngx_str_t*)((*cf->args)->elts))

$75 = {len = 8, data = 0x80f7392 "location"}

(gdb)p*(ngx_str_t*)((*cf->args)->elts+sizeof(ngx_str_t))

$76 = {len = 1, data = 0x80f739c "/"}

第...次......
第末次NGX_CONF_FILE_DONE

(gdb)p(*cf->args)->nelts

$65 = 0

36.Nginx的每一個配置指令都對應(yīng)一個ngx_command_s數(shù)據(jù)類型變量,記錄著該配置指令的解析回調(diào)函數(shù)、裝換值存儲位置等,而每一個模塊又都把自身所相關(guān)的所有指令以數(shù)組的形式組織起來,所以函數(shù)ngx_conf_handler()首先做的就是查找當(dāng)前指令所對應(yīng)的ngx_command_s變量,這通過循環(huán)遍歷各個模塊的指令數(shù)組即可。由于Nginx所有模塊也是以數(shù)組的形式組織起來的,所以在ngx_conf_handler()函數(shù)體內(nèi)我們可以看到有兩個for循環(huán)的遍歷查找。

代碼片段5.3-4,文件名:ngx_conf_file.c static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last) {...for (i=0; ngx_modules[i]; i++) {...cmd = ngx_modules[i]->commands;...for (/*void */; cmd->name.len; cmd++) { }

37.看一個Nginx配置文件解析的流程圖:

? ?

38.以http配置項的處理為例,我們知道ngx_http_module雖然是核心模塊, 但是其配置存儲空間還沒有實際申請,所以看第384行給conf進(jìn)行賦值的語句右值是數(shù)組元素的地址,由于ngx_http_module模塊對應(yīng)7號數(shù)組元素,所以conf指針的當(dāng)前指向如下圖所示:

? ?

?

83: 代碼片段5.4-8,文件名: ngx_http.c 84: { ngx_string("http"), 85: NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, 86: ngx_http_block, 87: 0, 88: 0, 89: NULL }, 90: ... 118: static char * 119: ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 120: { 121: ... 125: ngx_http_conf_ctx_t *ctx; 126: ... 132: ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); 133: ... 137: *(ngx_http_conf_ctx_t **) conf = ctx; 128:

? ?代碼第132行申請了內(nèi)存空間,而第137行通過conf參數(shù)間接地把這塊內(nèi)存空間“掛載”在7號數(shù)組元素下。經(jīng)過ngx_http_block()函數(shù)的處理,我們能看到的配置信息最基本的組織結(jié)構(gòu)如下圖所示:

? ?

? 39.配置繼承示例圖

? ??

? 40.Nginx不會像Apache或Lighttpd那樣在編譯時生成so動態(tài)庫,然后在程序執(zhí)行時再進(jìn)行動態(tài)加載,Nginx模塊源文件會在生成Nginx時就直接被編譯到其二進(jìn)制執(zhí)行文件中,所以,如果要選用不同的功能模塊,必須對Nginx做重新配置和編譯。對于功能模塊的選擇,如果要修改默認(rèn)值,需要在進(jìn)行configure時主動指定,比如新增http_flv功能模塊(默認(rèn)是沒有這個功能的,各個選項的默認(rèn)值可以在文件auto/options內(nèi)看到).

[root@localhost nginx-1.2.0]# ./configure --with-http_flv_module

? 執(zhí)行后,生成的objs/ngx_modules.c源文件內(nèi)就會包含對ngx_http_flv_module模塊的引用,要再去掉http_flv功能模塊,則需要重新configure,即不帶--with-http_flv_module配置后再編譯生成新的Nginx二進(jìn)制程序。

41.根據(jù)模塊的主要功能性質(zhì),大體可以將它們分為四個類別:

? ? a.handlers:協(xié)同完成客戶端請求的處理、產(chǎn)生響應(yīng)數(shù)據(jù),比如ngx_http_rewrite_module模塊,用于處理客戶端請求的地址重寫,ngx_http_static_module模塊,負(fù)責(zé)處理客戶端的靜態(tài)頁面請求,ngx_http_log_module模塊,負(fù)責(zé)記錄請求訪問日志。

? ? b.filters: 對handlers產(chǎn)生的響應(yīng)數(shù)據(jù)做各種過濾處理(即增/刪/改),比如模塊ngx_http_not_modified_filter_module,對待響應(yīng)數(shù)據(jù)進(jìn)行過濾檢測,如果通過時間戳判斷出前后兩次請求的響應(yīng)數(shù)據(jù)沒有發(fā)生任何實質(zhì)改變,那么可以直接響應(yīng)"304 Not Modified"狀態(tài)標(biāo)識,讓客戶端使用本地緩存即可,而原本待發(fā)送的響應(yīng)數(shù)據(jù)將被清除掉。

? ? c.upstream:如果存在后端真實服務(wù)器,Nginx可利用upstream模塊充當(dāng)反向代理(Reverse Proxy)的角色,對客戶端發(fā)起的請求只負(fù)責(zé)進(jìn)行轉(zhuǎn)發(fā)(當(dāng)然也包括對后端真實服務(wù)器響應(yīng)數(shù)據(jù)的回轉(zhuǎn)),比如ngx_http_proxy_module就為標(biāo)準(zhǔn)的upstraem模塊。

? ? d.load-balance: 在Nginx充當(dāng)中間代理角色時,由于后端真實服務(wù)器往往多于一個,對于某一次客戶單的請求,如何選擇對應(yīng)的后端真實服務(wù)器來進(jìn)行處理,有類似于ngx_http_upstream_ip_hash_module這樣的load balance模塊來實現(xiàn)不同的負(fù)責(zé)均衡算法

42.封裝Nginx模塊的結(jié)構(gòu)體為ngx_module_s,定義如下:

代碼片段6-1, 文件名: ngx_conf_file.h struct ngx_module_s {ngx_uint_t ctx_index; //當(dāng)前模塊在同類模塊中的序號ngx_uint_t index; //當(dāng)前模塊在所有模塊中的序號...ngx_uint_t version; //當(dāng)前模塊版本號void *ctx; //指向當(dāng)前模塊特有的數(shù)據(jù)ngx_command_t *commands; //指向當(dāng)前模塊配置項解析數(shù)組ngx_uint_t type; //模塊類型//以下為模塊回調(diào)函數(shù),回調(diào)時機(jī)可根據(jù)函數(shù)名看出ngx_int_t (*init_master)(ngx_log_t *log);... }; 代碼片段6-2,文件名:ngx_core.h typedef struct ngx_module_s ngx_module_t;

? ?結(jié)構(gòu)體ngx_module_s值得關(guān)注的幾個字段分別為ctx、commands和type,其中commands字段標(biāo)識當(dāng)前模塊可以解析的配置項目,表示模塊類型的type只有5種可能的值,而同一類型模塊的ctx指向的數(shù)據(jù)類型也相同,參見下表:

? ? type值的不同類型

序號type值ctx指向數(shù)據(jù)類型
1NGX_CORE_MODULEngx_core_module_t
2NGX_EVENT_MODULEngx_event_module_t
3NGX_CONF_MODULENULL
4NGX_HTTP_MODULEngx_http_module_t
5NGX_MAIL_MODULEngx_mail_module_t

43.Handler模塊

? ? http的請求的整個處理過程一共被分為11個階段,每一個階段對應(yīng)的處理功能都比較單一,這樣能盡量讓Nginx模塊代碼更為內(nèi)聚。這11個階段是Nginx處理客戶端請求的核心所在。

? ? 請求處理狀態(tài)機(jī)的11個階段

序號階段宏名階段簡單描述
0NGX_HTTP_POST_READ_PHASE請求頭讀取完成之后的階段
1NGX_HTTP_SERVER_REWRITE_PHASEServer內(nèi)請求地址重寫階段
2NGX_HTTP_FIND_CONFIG_PHASE配置查找階段
3NGX_HTTP_REWRITE_PHASELocation內(nèi)請求地址重寫階段
4NGX_HTTP_POST_REWRITE_PHASE請求地址重寫完成之后的階段
5NGX_HTTP_PREACCESS_PHASE訪問權(quán)限檢查準(zhǔn)備階段
6NGX_HTTP_ACCESS_PHASE訪問權(quán)限檢查階段
7NGX_HTTP_POST_ACCESS_PHASE訪問權(quán)限檢查完成之后的階段
8NGX_HTTP_TRY_FILES_PHASE配置項try_files處理階段
9NGX_HTTP_CONTENT_PHASE內(nèi)容產(chǎn)生階段
10NGX_HTTP_LOG_PHASE日志模塊處理階段

?a.NGX_HTTP_POST_READ_PHASE階段。

當(dāng)Nginx成功接收到一個客戶端請求后(即函數(shù)accept()正確返回對應(yīng)的套接口描述符,連接建立), 針對該請求所做的第一個實際工作就是讀取客戶端發(fā)過來的請求頭內(nèi)容,如果在這個階段掛上對應(yīng)的回調(diào)函數(shù), 那么在Nginx讀取并解析完客戶端請求頭內(nèi)容后(階段名稱里的POST有在...之后的含義),就會執(zhí)行這些回調(diào)函數(shù)。

?b.NGX_HTTP_SERVER_REWRITE_PHASE階段,和第3階段NGX_HTTP_REWRITE_PHASE都屬于地址重寫,也都是針對rewrite模塊而設(shè)定的階段,前者用于server上下文里的地址重寫,而后者用于location上下文里的地址重寫。

? ? NGX_HTTP_SERVER_REWRITE_PHASE階段在NGX_HTTP_POST_READ_PHASE階段之后,所以具體的先后順序如下圖所示:

? ??

? c.NGX_HTTP_FIND_CONFIG_PHASE階段。

此階段上不能掛載任何回調(diào)函數(shù),因為它們永遠(yuǎn)也不會被執(zhí)行,該階段完成的是Nginx的特定任務(wù), 即進(jìn)行Location定位。只有把當(dāng)前請求的對應(yīng)location找到了,才能從該location上下文中取出 更多精確地用戶配置值,做后續(xù)的進(jìn)一步請求處理。

? ?d.經(jīng)過上一階段后,Nginx已經(jīng)正確定位到當(dāng)前請求的對應(yīng)location,于是進(jìn)入到NGX_HTTP_REWRITE_PHASE階段進(jìn)行地址重寫,這和第1階段的地址重寫沒什么特別。唯一的差別在于,定義在location里的地址重寫規(guī)則只對被定位到當(dāng)前l(fā)ocation的請求才生效,用編程語言的說法就是,它們各自的作用域不一樣。

? ?e.NGX_HTTP_POST_REWRITE_PHASE階段。

該階段是指在進(jìn)行地址重寫之后,當(dāng)然,根據(jù)前面的列表來看,具體是在location請求地址重寫階段之后。 這個階段不會執(zhí)行任何回調(diào)函數(shù),它本身也是為了完成Nginx的特定任務(wù),即檢查當(dāng)前請求是否做了過多的 內(nèi)部跳轉(zhuǎn)(比如地址重寫、redirect等),我們不能讓對一個請求的處理在Nginx內(nèi)部跳轉(zhuǎn)很多次甚至是死循環(huán) (包括在server上下文或是在location上下文所進(jìn)行的跳轉(zhuǎn)),畢竟跳轉(zhuǎn)一次,基本所有流程就得重新走一遍,這是非常消耗性能的。

? ? f.NGX_HTTP_PREACCESS_PHASE、NGX_HTTP_ACCESS_PHASE、NGX_HTTP_POST_ACCESS_PHASE階段

做訪問權(quán)限檢查的前期、中期、后期工作,其中后期工作是固定的,判斷前面訪問權(quán)限檢查的結(jié)果 (狀態(tài)碼存放在字段r->access_code內(nèi)), 如果當(dāng)前請求沒有訪問權(quán)限,那么直接返回狀態(tài)403錯誤,所以這個階段也無法去掛載額外的回調(diào)函數(shù)。

? ? g.NGX_HTTP_TRY_FILES_PHASE階段

? ? 針對配置項try_files的特定處理階段

? ? h.NGX_HTTP_LOG_PHASE階段

? ? ? ?專門針對日志模塊所設(shè)定的處理階段。

? ?在一般條件下,我們的自定義模塊回調(diào)函數(shù)都掛載在NGX_HTTP_CONTENT_PHASE階段,畢竟大部分的業(yè)務(wù)需求都是修改http響應(yīng)數(shù)據(jù),Nginx自身的產(chǎn)生響應(yīng)內(nèi)容的模塊,像ngx_http_statis_module、ngx_http_random_index_module、ngx_http_index_module、ngx_http_gzip_static_module、ngx_http_dav_module等也都掛載在這個階段。

44.各個功能模塊將其自身的功能函數(shù)掛載在cmcf->phases之后,內(nèi)部的情況如下圖所示:

? ?

45.在函數(shù)ngx_http_init_phase_handlers()里對所有這些回調(diào)函數(shù)進(jìn)行一次重組,結(jié)果如下圖所示:

? ?

46.對http請求進(jìn)行分階段處理核心函數(shù)ngx_http_core_run_phases

代碼片段6.1-2,文件名:ngx_http_core_module.c void ngx_http_core_run_phases(ngx_http_request_t *r) {ngx_int_t rc;ngx_http_phase_handler_t *ph;ngx_http_core_main_conf_t *cmcf;cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);ph = cmcf->phase_engine.handlers;while(ph[r->phase_handler].checker) {rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);if (rc == NGX_OK) {return;}} }

?47.handler函數(shù)各種返回值的含義

序號返回值含義
1NGX_OK當(dāng)前階段已經(jīng)被成功處理,必須進(jìn)入到下一個階段
2NGX_DECLINED當(dāng)前回調(diào)不處理當(dāng)前情況,進(jìn)入到下一個回調(diào)處理
3NGX_AGAIN當(dāng)前處理所需資源不足,需要等待所依賴事件發(fā)生
4NGX_DONE當(dāng)前處理結(jié)束,仍需等待進(jìn)一步事件發(fā)生后做處理
5NGX_ERROR,NGX_HTTP_...當(dāng)前回調(diào)處理發(fā)生錯誤,需要進(jìn)入到異常處理流程

48.所有的header過濾功能函數(shù)和body過濾功能函數(shù)會分別組成各自的兩條過濾鏈,如下圖所示:

?

49.Upstream模塊與具體的協(xié)議無關(guān),其除了支持HTTP以外,還支持包括FASTCGI、SCGI、UWSGI、MEMCACHED等在內(nèi)的多種協(xié)議。Upstream模塊的典型應(yīng)用是反向代理。

50.對于任何一個Upstream模塊而言,最核心的實現(xiàn)主要是7個回調(diào)函數(shù),Upstream代理模塊自然也不例外:

? Upstream實現(xiàn)并注冊了7個回調(diào)函數(shù)如下表所示:

回調(diào)指針函數(shù)功能Upstream代理模塊
create_request根據(jù)Nginx與后端服務(wù)器通信協(xié)議(比如HTTP、Memcache),將客戶端的HTTP請求信息轉(zhuǎn)換為對應(yīng)的發(fā)送到后端服務(wù)器的真實請求。

ngx_http_proxy_create_request()

由于Nginx與后端服務(wù)器通信協(xié)議也為HTTP,所以直接拷貝客戶端的請求頭、請求體(如果有)到變量r->upstream->request_bufs內(nèi)

process_header根據(jù)Nginx和后端服務(wù)器通信協(xié)議,將后端服務(wù)器返回的頭部信息裝換為對客戶端響應(yīng)的HTTP響應(yīng)頭

ngx_http_proxy_process_status_line()

此時后端服務(wù)器返回的頭部信息已經(jīng)保存在變量r->upstream->buffer內(nèi),將這串字符串解析為HTTP響應(yīng)頭存儲到變量r->upstream->headers_in內(nèi)

input_filter_init根據(jù)前面獲得的后端服務(wù)器返回的頭部信息,為進(jìn)一步處理后端服務(wù)器將返回的響應(yīng)體做初始準(zhǔn)備工作

ngx_http_proxy_input_filter_init()

根據(jù)已解析的后端服務(wù)器返回的頭部信息,設(shè)置需進(jìn)一步處理的后端服務(wù)器將返回的響應(yīng)體的長度,該值保存在變量r->upstream->length內(nèi)

input_filter正式處理后端服務(wù)器返回的響應(yīng)體

ngx_http_proxy_buffered_copy_filter()

本次收到的響應(yīng)體數(shù)據(jù)長度為bytes,數(shù)據(jù)長度存儲在r->upstream->buffer內(nèi),把它加入到r->upstream->out_bufs響應(yīng)數(shù)據(jù)連等待發(fā)送給客戶端

finalize_request正常結(jié)束與后端服務(wù)器的交互,比如剩余待取數(shù)據(jù)長度為0或讀到EOF等,之后就會調(diào)用該函數(shù)。由于Nginx會自動完成與后端服務(wù)器交互的清理工作,所以該函數(shù)一般僅做下日志,標(biāo)識響應(yīng)正常結(jié)束

ngx_http_proxy_finalize_request()

記錄一條日志,標(biāo)識正常結(jié)束語后端服務(wù)器的交互,然后函數(shù)返回

reinit_request對交互重新初始化,比如當(dāng)Nginx發(fā)現(xiàn)一臺后端服務(wù)器出錯無法正常完成處理,需要嘗試請求另一臺后端服務(wù)器時就會調(diào)用該函數(shù)

ngx_http_proxy_reinit_request()

設(shè)置初始值,設(shè)置回調(diào)指針,處理比較簡單

abort_request異常結(jié)束與后端服務(wù)器的交互后就會調(diào)用該函數(shù)。大部分情況下,該函數(shù)僅做下日志,標(biāo)識響應(yīng)異常結(jié)束

ngx_http_proxy_abort_request()

記錄一條日志,標(biāo)識異常結(jié)束與后端服務(wù)器的交互,然后函數(shù)返回

這5個函數(shù)執(zhí)行的先后次序如下圖所示:

??

?要寫一個Upstream模塊,我們只需要實現(xiàn)上面提到的這7個函數(shù)即可。當(dāng)然,可以看到最主要的也就是create_request、process_header和input_filter這三個回調(diào),它們實現(xiàn)從HTTP協(xié)議到Nginx與后端服務(wù)器之間交互協(xié)議的來回轉(zhuǎn)換。

51.Load-balance模塊

? Load-balance模塊可以稱為輔助模塊,與前面介紹的以處理請求/響應(yīng)數(shù)據(jù)為目標(biāo)的三種模塊完全不同,它主要為Upstream模塊服務(wù),目標(biāo)明確且單一,即如何從多臺后端服務(wù)器中選擇出一臺合適的服務(wù)器來處理當(dāng)前請求。

? ?要實現(xiàn)一個具體的Load-balance模塊,需要實現(xiàn)如下4個回調(diào)函數(shù)即可,見下表:

? Load-balance模塊的4個回調(diào)接口

回調(diào)指針函數(shù)功能round_robin模塊IP_hash模塊
uscf->peer.init_upstream解析配置文件過程中被調(diào)用,根據(jù)upstream里各個server配置項做初始準(zhǔn)備工作,另外的核心工作是設(shè)置回調(diào)指針us->peer.init。配置文件解析完后就不再被調(diào)用

ngx_http_upstream_init_

round_robin()

設(shè)置:us->peer.init = ngx_http

_upstream_init_

round_robin_peer;

ngx_http_upstream_init_

ip_hash()

設(shè)置:us->peer.init = ngx_http_upstream_init_

ip_hash_peer;

us->peer.init在每一次Nginx準(zhǔn)備轉(zhuǎn)發(fā)客戶端請求到后盾服務(wù)器前都會調(diào)用該函數(shù),該函數(shù)為本次轉(zhuǎn)發(fā)選擇合適的后端服務(wù)器做初始準(zhǔn)備工作,另外的核心工作是設(shè)置回調(diào)指針r->upstream->peer.get和r->upstream->peer.free等

ngx_http_upstream_init_

round_robin_peer()

設(shè)置:r->upstream->peer.get = ngx_http_upstream_get_

round_robin_peer;

r->upstream->peer.free = ngx_http_upstream_free_

round_robin_peer;

ngx_http_upstream_init_

ip_hash_peer()

設(shè)置:r->upstream->peer.get = ngx_http_upstream_get_

ip_hash_peer;r->upstream->peer.free為空

r->upstream->peer.get在每一次Nginx準(zhǔn)備轉(zhuǎn)發(fā)客戶端請求到后端服務(wù)器前都會調(diào)用該函數(shù),該函數(shù)實現(xiàn)具體的為本次轉(zhuǎn)發(fā)懸則合適后端服務(wù)器的算法邏輯,即完成選擇獲取合適后端服務(wù)器的功能

ngx_http_upstream_get_

round_robin_peer()

加權(quán)選擇當(dāng)前全職最高

(即從各方面綜合比較更

有能力處理當(dāng)前請求)的后端服務(wù)器

ngx_http_upstream_get_

IP_hash_peer()

根據(jù)IP哈希值選擇后端服務(wù)器

r->upstream->peer.free在每一次Nginx完成與后端服務(wù)器之間的交互后都會調(diào)用該函數(shù)。如果選擇算法有前后依賴性,比如加權(quán)選擇,那么需要做一些數(shù)值更新操作;如果選擇算法沒有前后依賴性,比如IP哈希,那么該函數(shù)可為空。

ngx_http_upstream_free_

round_robin_peer()

更新相關(guān)數(shù)值,比如rrp->current等

?

?52.Nginx是以事件驅(qū)動的,也就是說Nginx內(nèi)部流程的向前推進(jìn)基本都是靠各種事件的觸發(fā)來驅(qū)動,否則Nginx將一直阻塞在函數(shù)epoll_wait()或sigsuspend()這樣的系統(tǒng)調(diào)用上。

53.各種I/O事件處理機(jī)制

名稱特點
select標(biāo)準(zhǔn)的I/O復(fù)用模型,幾乎所有的類UNIX系統(tǒng)上都有提供,但性能相對較差。如果在當(dāng)前系統(tǒng)平臺上找不到更優(yōu)的I/O事件處理機(jī)制,那么Nginx默認(rèn)編譯并使用select復(fù)用模型,我們也可以通過使用--with-select_module或--without-select_module配置選項來啟用或禁用select復(fù)用模型模塊的編譯
poll標(biāo)準(zhǔn)的I/O復(fù)用模型,理論上比select復(fù)用模型要優(yōu)。同select復(fù)用模型類似,可以通過使用--with-poll_module或--without-poll_module配置選項來啟用或禁用poll復(fù)用模型模塊的編譯
epoll系統(tǒng)Linux 2.6_上正式提供的性能更優(yōu)秀的I/O復(fù)用模型
kqueue在系統(tǒng)FreeBSD 4.1_, OpenBSD2.9_,NetBSD 2.0和MacOS X上特有的性能更優(yōu)秀的I/O復(fù)用模型
eventport在系統(tǒng)Solaris10上可用的高性能I/O復(fù)用模型
/dev/poll在系統(tǒng)Solaris 7 11/99+,HP/UX 11.22+(eventport),IRIX 6.5.15+和Tru64 UNIX 5.1A+上可用的高性能I/O復(fù)用模型
rtsig實時信號(real time signals)模型,在Linux 2.2.19+系統(tǒng)上可用??梢酝ㄟ^使用--with-rtsig_module配置選項來啟用rtsig模塊的編譯
aio異步I/O(Asynchronous Input and Output)模型,通過異步I/O函數(shù),如aio_read、aio_write、aio_cancel、aio_error、aio_fsync、aio_return等實現(xiàn)

54.在Nginx源碼里,I/O多路復(fù)用模型被封裝在一個名為ngx_event_actions_t的結(jié)構(gòu)體里,該結(jié)構(gòu)體包含的字段主要就是回調(diào)函數(shù),將各個I/O多路復(fù)用模型的功能接口進(jìn)行統(tǒng)一,參見下表:

? ?I/O多路復(fù)用模型統(tǒng)一接口

ngx_event_actions_t接口說明
init

初始化

add將某描述符的某個事件(可讀/可寫)添加到多路復(fù)用監(jiān)控里
del將某描述符的某個事件(可讀/可寫)從多路復(fù)用監(jiān)控里刪除
enable啟用對某個指定事件的監(jiān)控
disable禁用對某個指定事件的監(jiān)控
add_conn將指定連接關(guān)聯(lián)的描述符加入到多路復(fù)用監(jiān)控里
del_conn將指定連接關(guān)聯(lián)的描述符從多路復(fù)用監(jiān)控里刪除
process_changes監(jiān)控的事件發(fā)生變化,只有kqueue會用到這個接口
process_events阻塞等待事件發(fā)生,對發(fā)生的事件進(jìn)行逐個處理
done回收資源

55.Nginx內(nèi)對I/O多路復(fù)用模型的整體封裝

? ?

56.epoll接口作為poll接口的變體在Linux 內(nèi)核2.5中被引入。相比于select實現(xiàn)的多路復(fù)用I/O模型,epoll模型最大的好處在于它不會隨著被監(jiān)控描述符數(shù)目的增長而導(dǎo)致效率急速下降。

? ? epoll提供了三個系統(tǒng)調(diào)用接口,分別如下所示:

#include <sys/epoll.h> int epoll_create(int size);//創(chuàng)建一個epoll的句柄(epoll模型專用的文件描述符), size用來告訴內(nèi)核監(jiān)聽的描述符數(shù)目的最大值,請求內(nèi)核為存儲事件分配空間, 并返回一個描述符(在epoll使用完后,必須調(diào)用close()關(guān)閉這個描述符,否則可能導(dǎo)致系統(tǒng)描述符被耗盡) int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//用來向內(nèi)核注冊、刪除或修改事件 int epoll_wait(int epfd, struct *epoll_event *events, int maxevents, int timeout);//用來等待事件發(fā)生。 int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);//epoll_pwait()和函數(shù)epoll_wait()的差別在于其 可以通過最后一個參數(shù)設(shè)置阻塞過程中的信號屏蔽字

57.Nginx關(guān)注事件以及對應(yīng)的回調(diào)處理函數(shù)變化過程

序號關(guān)注事件類型對應(yīng)的回調(diào)函數(shù)
1ngx_http_init_request()
2ngx_http_empty_handler()
3ngx_http_process_request_line()
4ngx_http_process_request_headers()
5ngx_http_request_handler()
6ngx_http_request_handler()
7ngx_http_empty_handler()
8ngx_http_keepalive_handler()

58.函數(shù)ngx_trylock_accept_mutex()的內(nèi)部流程

? ?

59.Nginx在多核平臺上針對負(fù)載均衡和優(yōu)化所做的工作,就是提供有worker_cpu_affinity配置指令,利用該指令可以將各個工作進(jìn)程固定在指定的CPU核上執(zhí)行。CPU親和性,簡單點說就是讓某一段代碼/數(shù)據(jù)盡量在指定的某一個或幾個CPU核心上長時間運行/計算的機(jī)制。

60.事件超時意味著等待的事件沒有在指定的時間內(nèi)到達(dá),Nginx有必要對這些可能發(fā)生超時的事件進(jìn)行統(tǒng)一管理,并在發(fā)生事件超時時做出相應(yīng)的處理,比如回收資源,返回錯誤等。

? Nginx把事件封裝在一個名為ngx_event_s的結(jié)構(gòu)體內(nèi),而該結(jié)構(gòu)體有幾個字段與Nginx的超時管理聯(lián)系緊密。

代碼片段7.5-1,文件名: ngx_event.h struct ngx_event_s {...unsigned timedout:1; //用于標(biāo)識當(dāng)前事件是否已經(jīng)超過,0為沒有超時;unsigned timer_set:1;//用于標(biāo)識當(dāng)前事件是否已經(jīng)加入到紅黑樹管理,需要對其是否超時做監(jiān)控...ngx_rbtree_node_t timer;//屬于紅黑樹節(jié)點類型變量,紅黑樹就是通過該字段來組織所有的超時事件對象。

61.紅黑樹的初始化函數(shù)ngx_event_timer_init()是在ngx_event_process_init()函數(shù)內(nèi)被調(diào)用,所以每一個工作進(jìn)程都會在自身的初始化時建立這顆紅黑樹,如下圖所示:

??

62.通過紅黑樹,Nginx對那些需要關(guān)注其是否超時的事件對象就有了統(tǒng)一的管理,Nginx可以選擇在合適的時機(jī)對事件計時紅黑樹管理的事件進(jìn)行一次超時檢測,對于超時了的事件對象進(jìn)行相應(yīng)的處理。

? ?

63.父子請求之間的可變變量值

? ?

64.主進(jìn)程通過fork()函數(shù)創(chuàng)建子進(jìn)程,也就是工作進(jìn)程,它們將全部繼承這些已初始化好的監(jiān)聽套接字。在每個工作進(jìn)程的事件初始化函數(shù)ngx_event_process_init()內(nèi),對每一個監(jiān)聽套接字創(chuàng)建對應(yīng)的connection連接對象(為什么不直接用一個event事件對象呢?主要是考慮到可以傳遞更多信息到函數(shù)ngx_event_accept()內(nèi),并且這個連接對象雖然沒有對應(yīng)的客戶端,但可以與accept()創(chuàng)建的連接套接口統(tǒng)一起來,因為連接套接口對應(yīng)的是connection連接對象,所以可以簡化相關(guān)邏輯的代碼實現(xiàn)而無需做復(fù)雜的判斷與區(qū)分),并利用該connection的read事件對象(因為在監(jiān)聽套接口上觸發(fā)的肯定是讀事件)。

? ? 可以看到Nginx主進(jìn)程在創(chuàng)建完工作進(jìn)程之后并沒有關(guān)閉這些監(jiān)聽套接口,但主進(jìn)程卻又沒有進(jìn)行accept()客戶端連接請求,那么是否會導(dǎo)致一些客戶端請求失敗呢?答案當(dāng)然是否定的,雖然主進(jìn)程也擁有那些監(jiān)聽套接口,并且它也的確能收到客戶端的請求,但是主進(jìn)程并沒有監(jiān)控這些監(jiān)聽套接口上的事件,沒有去讀取客戶端的請求數(shù)據(jù)。既然主進(jìn)程沒有去讀監(jiān)聽套接口上的數(shù)據(jù),那么數(shù)據(jù)就阻塞在那里,等待任意一個工作進(jìn)程捕獲到對應(yīng)的可讀事件后,進(jìn)而去處理并響應(yīng)客戶端請求。至于主進(jìn)程為什么保留(不關(guān)閉)那些監(jiān)聽套接口,是因為在后續(xù)再創(chuàng)建新工作進(jìn)程(比如某工作進(jìn)程異常退出,主進(jìn)程收到SIGCHLD信號)時,還要把這些監(jiān)聽套接口傳承過去。

65.創(chuàng)建連接套接口

? ? 當(dāng)有客戶端發(fā)起連接請求,監(jiān)控監(jiān)聽套接口的事件管理機(jī)制就會捕獲到可讀事件,工作進(jìn)程便執(zhí)行對應(yīng)的回調(diào)函數(shù)ngx_event_accept(),從而開始連接套接口的創(chuàng)建工作。

? ? 函數(shù)ngx_event_accept()的整體邏輯都比較簡單,但是有兩個需要注意的地方。首先是每次調(diào)用accept()函數(shù)接受客戶端請求的次數(shù),默認(rèn)情況下調(diào)用accept()函數(shù)一次,即工作進(jìn)程每次捕獲到監(jiān)聽套接口上的可讀事件后,只接受一個客戶端請求,如果同時收到多個客戶端請求,那么除第一個以外的請求需等到再一次觸發(fā)事件才能被accept()接受。但是如果用戶配置有multi_accept on;,那么工作進(jìn)程每次捕獲到監(jiān)聽套接口上的可讀事件后,將反復(fù)調(diào)用accept()函數(shù),即一次接受當(dāng)前所有到達(dá)的客戶端連接請求。

代碼片段9.2-1,文件名:ngx_event_accept.c void ngx_event_accept(ngx_event_t *ev) {...ev->available = ecf->multi_accept;...do {...s = accept(lc->fd, (struct sockaddr *)sa, &socklen);...if (s == -1) {...return;...} while(ev->available); }

66.如下所示,在讀到NGX_AGAIN時,也就是需要的請求數(shù)據(jù)沒有全部到達(dá),將事件對象rev加入到超時管理機(jī)制和事件監(jiān)控機(jī)制,以等待后續(xù)數(shù)據(jù)可讀事件或超時事件。

代碼片段9.2-4,文件名:ngx_http_request.c static ssize_t ngx_http_read_request_header(ngx_http_request_t *r) {...if (n == NGX_AGAIN) {...ngx_add_timer(rev, cscf->client_header_timeout);...if (ngx_handle_read_event(rev, 0) != NGX_OK) {

67.函數(shù)ngx_http_init_request(),正式開始對一個客戶端服務(wù)請求進(jìn)行處理與響應(yīng)工作。該函數(shù)的主要功能仍然只是做處理準(zhǔn)備:建立http連接對象ngx_http_connection_t、http請求對象ngx_http_request_t、找到對應(yīng)的server配置default_server、大量的初始化賦值操作,最后執(zhí)行回調(diào)函數(shù)ngx_http_process_line(),進(jìn)入到http請求頭的處理中。

代碼片段9.3-1,文件名:ngx_http_request.c static void ngx_http_init_request(ngx_event_t *rev) {...hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));...r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t));...addr = port->addrs;addr_conf = &addr[0].conf;.../* the default server configuration for the address:port */cscf = addr_conf->default_server;...rev->handler = ngx_http_process_request_line;...rev->handler(rev); }

68.函數(shù)ngx_http_process_request_line()處理的數(shù)據(jù)就是客戶端發(fā)送過來得http請求頭中的Request-Line。這個過程可分為三步:讀取Request-Line數(shù)據(jù)、解析Request-Line、存儲解析結(jié)果并設(shè)置相關(guān)值。

? ? ?a.第一步,讀取Request-Line數(shù)據(jù)。通過函數(shù)ngx_http_read_request_header()將數(shù)據(jù)讀到緩存區(qū)r->header_in內(nèi)。由于客戶端請求頭部數(shù)據(jù)可能分多次到達(dá),所以緩存區(qū)r->header_in內(nèi)可能還有一些上一次沒解析完的頭部數(shù)據(jù),所以會存在數(shù)據(jù)的移動等操作。

? ? ?b.第二步,解析Request-Line。對讀取到的Request-Line數(shù)據(jù)進(jìn)行解析的工作實現(xiàn)在函數(shù)ngx_http_parse_request_line()內(nèi)。

? ? ?c.第三步,存儲解析結(jié)果并設(shè)置相關(guān)值。在Request-Line的解析過程中會有一些賦值操作,但更多的是在成功解析后,ngx_http_request_t對象r內(nèi)的相關(guān)字段值都將被設(shè)置,比如uri(/)、method_name(GET)、http_protocol(HTTP/1.0)等。

? ? ? Request-Line解析成功,即函數(shù)ngx_http_parse_request_line()返回NGX_OK,意味著這初步算是一個合法的http客戶端請求。

代碼片段9.3-2,文件名: ngx_http_request.c static void ngx_http_process_request_line(ngx_event_t *rev) {...for (;;) {...n = ngx_http_read_request_header(t);...rc = ngx_http_parse_request_line(r, r->header_in);if (rc == NGX_OK) {...if (ngx_list_init(&r->headers_in.headers, r->pool, 20,sizeof(ngx_table_elt_t))...rev->handler = ngx_http_process_request_headers;ngx_http_rpocess_request_headers(rev);

? 函數(shù)ngx_http_process_request_headers()內(nèi)的具體實現(xiàn)如下:

代碼片段9.3-4,文件名:ngx_http_request.c static void ngx_http_process_request_headers(ngx_event_t *rev) {...rc = ngx_http_parse_header_line(r, r->header_in, cscf->underscores_in_headers);if (rc == NGX_OK) {...h = ngx_list_push(&r->headers_in.headers);...hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len);...if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {

69.函數(shù)ngx_http_header_filter()完成響應(yīng)頭字符串?dāng)?shù)據(jù)的組織工作。該函數(shù)申請一個buf緩存塊,然后根據(jù)最初設(shè)置以及經(jīng)過過濾鏈的修改后的相關(guān)響應(yīng)頭字段值,組織響應(yīng)頭數(shù)據(jù)以字符串的形式存儲在該緩存塊內(nèi)。? ??

代碼片段9.4-1,文件名:ngx_http_header_filter_module.c static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r) {...ngx_chain_t out;...out.buf = b;out.next = NULL;return ngx_http_write_filter(r, &out); }

? ? 該緩存塊被接入到發(fā)送鏈變量out(注意這是一個局部變量)內(nèi),之后進(jìn)入到函數(shù)ngx_http_write_filter()進(jìn)行"寫入"操作,打上引號是因為此處只有在滿足某些條件的情況下才會執(zhí)行實際的數(shù)據(jù)寫出。

代碼片段9.4-2,文件名:ngx_http_write_filter_module.c ngx_int_t ngx_http_write_fitler(ngx_http_request_t *r, ngx_chain_t *in) {.../*avoid the output if there are no last buf, no flush point,*there are the incoming bufs and the size of all bufs* is smaller than "postpone_output" directive*/if (!last && !flush && in && size < (off_t) clcf->postponse_output) {return NGX_OK;}

70.只有一塊待發(fā)送緩存塊的r->out鏈結(jié)構(gòu)

? ??

? 函數(shù)依次返回后到函數(shù)ngx_http_static_handler()內(nèi)繼續(xù)執(zhí)行,看一下相關(guān)的完整代碼:

代碼片段9.4-3,文件名:ngx_http_static_module.c static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r) {...b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));...b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));...rc = ngx_http_send_header(r);...b->last_buf = (r == r->main) ? 1: 0;...b->file->fd = of.fd;b->file->name = path;...return ngx_http_output_filter(r, &out); }

? ? 有兩塊待發(fā)送緩存塊的r->out鏈結(jié)構(gòu)

? ??

71.在進(jìn)行實際的數(shù)據(jù)寫出操作時,關(guān)注我們的重點函數(shù):

代碼片段9.4-4,文件名:ngx_http_write_filter_module.c chain = c->send_chain(c, r->out, limit);

? 回調(diào)函數(shù)send_chain根據(jù)系統(tǒng)環(huán)境的不同而指向不同的函數(shù),相關(guān)代碼如下:

代碼片段9.4-5, 文件名:ngx_linux_sendfile_chain.c ngx_chain_t * ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) {...for (;;) {...for (cl = in; cl && send < limit; cl = cl->next) {if (file) {...rc = sendfile(c->fd, file->file->fd, &offset, file_size);...} else {rc = writev(c->fd, header.elts, header.nelts);

? ? 客戶端需要的數(shù)據(jù)都發(fā)送出去了,那么剩下的工作也就是進(jìn)行連接關(guān)閉和一些連接相關(guān)資源的清理。

72.在進(jìn)一步描述http連接關(guān)閉流程之前,有必要先介紹一下Nginx的子請求(sub request)概念,因為它的出現(xiàn)導(dǎo)致了http連接關(guān)閉流程的復(fù)雜化。所謂子請求,并不是由客戶端直接發(fā)起的,它是由于Nginx在處理客戶端的請求時,根據(jù)自身邏輯而內(nèi)建的心情求,如下圖所示:

? ?

? 子請求幾乎具有主請求的所有特征(比如有對應(yīng)完整的ngx_http_request_t結(jié)構(gòu)體對象),并且子請求本身也可以發(fā)起新的子請求,即這是一個可以嵌套的概念。在默認(rèn)情況下,在讀一個客戶端請求(即主請求)的處理過程中,可以發(fā)起的總子請求數(shù)目(即包括子請求、孫子請求等)大約為200個(由宏NGX_HTTP_MAX_SUBREQUESTS限定),這在一般情況下,已經(jīng)足夠了。

?73.根據(jù)發(fā)起子請求的特征,即子請求可以遞歸發(fā)起子請求(樹結(jié)構(gòu))以及同一個子請求可以發(fā)起多個子請求(鏈表結(jié)構(gòu)),按照樹加鏈表的形式對它們進(jìn)行組織是自然而然的事情。而在結(jié)構(gòu)體ngx_http_request_t內(nèi)提供了兩個與此相對應(yīng)的字段。

代碼片段9.5-2,文件名:ngx_http_request.h typedef struct ngx_http_postponed_request_s ngx_http_postponed_request_t;struct ngx_http_postponed_request_s {ngx_http_request_t *request;ngx_chain_t *out;ngx_http_postponed_request_t *next; }; ... struct ngx_http_request_s {...ngx_http_request_t *parent;ngx_http_postponed_request_t *postponed;

74.在開始新的子請求、內(nèi)部跳轉(zhuǎn)、命名location跳轉(zhuǎn)、開始upstream請求等多種情況下,都可能導(dǎo)致主請求request對象的引用計數(shù)count自增1,這意味著在對應(yīng)的操作完成(比如內(nèi)部跳轉(zhuǎn)處理結(jié)束)之前不能釋放主請求對象和連接對象。之所以需要做這樣的設(shè)計,原因仍然在于Nginx是通過事件觸發(fā)來向前推進(jìn)的,資源相互關(guān)聯(lián)的各個請求對象在執(zhí)行過程中誰先誰后不可預(yù)知,雖然在其他大部分地方不需要同步而各自自由前進(jìn),但在結(jié)束點上做資源釋放卻需要同步,否則導(dǎo)致的結(jié)果就可想而知了。

? ? 在HTTP 1.0協(xié)議里,客戶端通過發(fā)送Connection:Keep-Alive的請求頭來實現(xiàn)與服務(wù)器之間的keepalive;而在HTTP 1.1協(xié)議里,由于標(biāo)準(zhǔn)要求連接默認(rèn)被保持,所以此時請求頭Connection:Keep-Alive也不再有意義,但通過請求頭Connection:Close可明確要求不進(jìn)行keepalive連接保持,在Nginx內(nèi)的具體判斷,首先是獲取Connection請求頭并設(shè)置connection_type變量。

代碼片段9.6.1-1,文件名:ngx_http_request.c if (ngx_strcasestrn(h->value.data, "close", 5 - 1)) {r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; } else if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) {r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE; }

75.

代碼片段9.6.1-7,文件名:ngx_http_request.c static void ngx_http_keepalive_handler(ngx_event_t *rev) {...if (rev->timeout || c->close) {ngx_http_close_connection(c);return;}...n = c->recv(c, b->last, size);c->log_error = NGX_ERROR_INFO;if (n == NGX_AGAIN) {if (ngx_handle_read_event(rev, 0) != NGX_OK) {ngx_http_close_connection(c);}return;}...ngx_http_init_request(rev); }

?下面SO_LINGER選項的全部相關(guān)代碼:

代碼片段9.6.2-2,文件名:ngx_http_request.c static void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) {...if (r->connection->timedout) {clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);if (clcf->reset_timedout_connection) {linger.l_onoff = 1;linger.l_linger = 0;if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,(const void *)&linger, sizeof(struct linger)) == -1)

? 76.函數(shù)ngx_http_core_find_location()的處理流程圖

? ??

77.Nginx+Fastcgi+PHP測試環(huán)境

? ?

? 客戶端通過HTTP/HTTPS協(xié)議對Web服務(wù)器端PHP資源的請求被Nginx的Fastcgi模塊接手處理(上圖中圓圈1),該模塊將客戶端請求數(shù)據(jù)的格式轉(zhuǎn)換為FASTCGI協(xié)議格式后(圖11-1中圓圈2),通過Upstream模塊與PHP引擎之間建立的連接,把它們轉(zhuǎn)發(fā)送到Php引擎(圖11-1中圓圈3).PHP引擎根據(jù)轉(zhuǎn)發(fā)請求進(jìn)行處理后,通過同一條連接把數(shù)據(jù)再傳回給Nginx(圖11-1中圓圈4),Nginx通過Fastcgi模塊將收到的響應(yīng)數(shù)據(jù)轉(zhuǎn)換回HTTP/HTTPS協(xié)議格式后(圖11-1中圓圈5),發(fā)回最終的客戶端(圖11-1中圓圈6)。

78.對于客戶端發(fā)送的PHP資源請求,Nginx在前期的處理和對HTML資源請求的處理沒什么兩樣,仍然還是創(chuàng)建request請求對象、解析請求頭、定位location并開始轉(zhuǎn)動請求處理狀態(tài)機(jī)等,真正出現(xiàn)分叉的地方在狀態(tài)機(jī)進(jìn)入NGX_HTTP_CONTENT_PHASE階段后。

? ? Nginx針對HTML頁面請求與PHP頁面請求所做的不同處理

? ??

79.fastcgi模塊提出了5個核心功能函數(shù),分別如下:

代碼片段11.2-2,文件名:ngx_http_fastcgi_module.c static ngx_int_t ngx_http_fastcgi_handler(ngx_http_request_t *r) {...if (ngx_http_upstream_create(r) != NGX_OK) {...u->create_request = ngx_http_fastcgi_create_request;u->reinit_request = ngx_http_fastcgi_reinit_request;u->process_header = ngx_http_fastcgi_process_header;u->abort_request = ngx_http_fastcgi_abort_request;u->finalize_request = ngx_http_fastcgi_finalize_request;...rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

? ? 重要的是回調(diào)函數(shù)create_request()和process_header(),它們分別表示向后端服務(wù)器發(fā)起請求和從后端服務(wù)器接收數(shù)據(jù),這一去一來無非是Nginx與后端服務(wù)器交互的核心。

80.Nginx服務(wù)客戶端的請求體的函數(shù)調(diào)用流程

? ??

? ?從客戶端讀取的請求體存放在r->request_body內(nèi),受一些用戶設(shè)置的影響,請求體數(shù)據(jù)可能放置在一塊或多塊內(nèi)存緩存區(qū),或者是某個臨時文件內(nèi),但只要請求體大于一定的值(可通過指令client_body_buffer_size設(shè)置,默認(rèn)情況下是2頁內(nèi)存,一般也就是8KB),則都會主動寫到臨時文件,這實現(xiàn)在函數(shù)ngx_http_write_request_body()內(nèi),注意它一次可以寫入多個buf塊。

81.Nginx傳遞發(fā)送給PHP引擎的請求數(shù)據(jù)

名稱長度值長度名稱
120QUERY_STRING
143REQUEST_METHODGET
120CONTENT_TYPE
140CONTENT_LENGTH
116SCRIPT_NAME/t.php

Nginx要把請求數(shù)據(jù)發(fā)送到PHP引擎,首先得建立起Nginx到PHP引擎之間的通信連接,如果用戶在配置文件里設(shè)置的PHP引擎監(jiān)聽地址是很明確的,即沒有帶上配置變量,(暫稱之為靜態(tài)變量),那么此時可直接調(diào)用函數(shù)ngx_http_upstream_connect()發(fā)起連接建立請求。

代碼片段11.3.1-1,文件名:ngx_htp_upstream.c if (u->resolved == NULL) {uscf = u->conf->upstream;...if (uscf->peer.init(r, uscf) != NGX_OK) {...}ngx_http_upstream_connect(r, u); }

82.時刻記住Nginx的主要特性:非阻塞、事件驅(qū)動、異步。

83.連接建立準(zhǔn)備工作

? ?

84.連接建立

? ??

85.接收并處理Fastcgi響應(yīng)頭

? ? 函數(shù)ngx_http_upstream_process_header()用于讀取后端服務(wù)器的響應(yīng)數(shù)據(jù),而在我們這里,Nginx讀到的響應(yīng)數(shù)據(jù)是FASTCGI協(xié)議格式的。

代碼片段11.4.1-1,文件名:ngx_http_upstream.c static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) {...if (c->read->timeout) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);return;}if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);return;}

? ? 這處理的是超時情況以及連接已斷開的情況,對于這兩種情況都將調(diào)用函數(shù)ngx_http_upstream_next()重新選擇后端服務(wù)器。

? ? 先來看函數(shù)ngx_http_fastcgi_process_header()里的處理邏輯,它循環(huán)處理每一條FASTCGI記錄。

代碼片段11.4.1-3,文件名:ngx_http_fastcgi_module.c static ngx_int_t ngx_http_fastcgi_process_header(ngx_http_request_t *r) {...for (;;) {if (f->state < ngx_http_fastcgi_st_data) {rc = ngx_http_fastcgi_process_record(r, f);...if (rc == NGX_AGAIN) {return NGX_AGAIN;}...if (f->state == ngx_http_fastcgi_st_padding) {return NGX_AGAIN;}...if (f->type == NGX_HTTP_FASTCGI_STDERR) {...continue;}...f->fastcgi_stdout = 1;start = u->buffer.pos;...for (;;) {...rc = ngx_http_parse_header_line(r, &u->buffer, 1);...if (rc == NGX_AGAIN) {break;}if (rc == NGX_OK) {...break;}if (rc == NGX_HTTP_PARSE_HEADER_DONE) {...break;}...return NGX_HTTP_UPSTREAM_INVALID_HEADER;}...if (rc == NGX_HTTP_PARSE_HEADER_DONE) {return NGX_OK;}if (rc == NGX_OK) {continue;}/* rc == NGX_AGAIN */...return NGX_AGAIN;} }

?86.每一條Fastcgi記錄在NGINX內(nèi)對應(yīng)的結(jié)構(gòu)體為ngx_http_fastcgi_header_t,具體來看其數(shù)據(jù)分別表示:如下表所示:

響應(yīng)數(shù)據(jù)中Fastcgi記錄格式

字段名字段值說明

第一條記錄:總長度為57+7+8=72字節(jié)

Version1取固定值1
Type6NGX_HTTP_FASTCGI_STDOUT
Request Id1占兩個字節(jié),對應(yīng)請求序號1
Content Length57占兩個字節(jié),內(nèi)容長度
Padding Length?7即在該記錄末尾處填充了7個0
Reserved0取值0

第二條記錄:從地址0x930b320開始的8字節(jié),這一條標(biāo)記結(jié)束的記錄。

Version1取固定值·1
Type3NGX_HTTP_FASTCGI_END_REQUEST
Request Id?1占兩個字節(jié),對應(yīng)請求序號1
Content Length8占兩個字節(jié),內(nèi)容長度
Padding Length0無填充
Reserved?0取值0

? Fastcgi響應(yīng)頭的最后一點處理代碼在函數(shù)ngx_http_upstream_process_header()內(nèi)。

代碼片段11.4.1-4,文件名:ngx_http_upstream.c static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) {../* rc == NGX_OK */if (u->headers_in.status_n > NGX_HTTP_SPECIAL_RESPONSE) {...}if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {return;}if (!r->subrequest_in_memory) {ngx_http_upstream_send_response(r, u);return;}

87.在函數(shù)ngx_http_upstream_send_response()內(nèi)做了很多準(zhǔn)備工作,其中最重要的就是給變量u->pipe做初始化處理。先看該變量所對應(yīng)結(jié)構(gòu)體ngx_event_pipe_t的具體定義。

代碼片段11.4.2-2,文件名:ngx_event_pipe.h struct ngx_event_pipe_s {ngx_connection_t *upstream; //表示Nginx與后端服務(wù)器之間的連接對象ngx_connection_t *downstream; //表示Nginx與客戶端之間的連接對象ngx_chain_t *free_raw_bufs;ngx_chain_t *in;ngx_chain_t **last_in;ngx_chain_t *out;ngx_chain_t *free;ngx_chain_t *busy; ...ngx_event_pipe_input_filter_pt input_filter;//Nginx接收到后端服務(wù)器的響應(yīng)數(shù)據(jù)后所需執(zhí)行的過濾回調(diào),在這里也就是函數(shù)ngx_http_fastcgi_input_filter(),用于對Fastcgi記錄做解析void *input_ctx;ngx_event_pipe_output_filter_pt output_filter; //Nginx發(fā)送數(shù)據(jù)到客戶端的過濾函數(shù),這里直接指向ngx_http_output_filter(),即走普通的HTTP響應(yīng)體過濾鏈。void *output_ctx;

89.ngx_event_pipe_write_to_downstream()函數(shù):

代碼片段11.4.2-13,文件名:ngx_event_pipe.c static ngx_int_t ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p) {...for (;;) {...if (p->upstream_eof || p->upstream_error || p->upstream_done) {..if (p->out) {...rc = p->output_filter(p->output_ctx, p->out);...p->out = NULL;}if (p->in) {...rc = p->output_fitler(p->output_Ctx, p->in);...p->in = NULL;}...p->downstream_done = 1;break;}...}return NGX_OK; }

90.Nginx的負(fù)載均衡

? ??

91.加權(quán)輪詢的流程圖

? ??

92.IP哈希的流程圖

? ??

93.兩種策略對比

? ? 顯而易見,加權(quán)輪詢策略的實用性更強(qiáng),它不依賴與客戶端的任何信息,而完全依靠后端服務(wù)器的情況來進(jìn)行選擇,優(yōu)勢就是能把客戶端請求更合理更均勻地分配到各個后端服務(wù)器處理,但其劣勢也很明顯,同一個客戶端的多次請求可能會被分配到不同的后端服務(wù)器進(jìn)行處理,所有無法滿足做會話保持的應(yīng)用的需求。

? ? 與此同時,IP哈希策略能較好地把同一個客戶端的多次請求分配到同一臺后端服務(wù)器處理,所以避免了加權(quán)輪詢策略無法適用會話保持的需求,但是,因為IP哈希策略是根據(jù)客戶端的IP地址來對后端服務(wù)器做選擇,所以如果某個時刻,來自某個IP地址的請求特別多(比如大量用戶通過同一個NAT代理發(fā)起請求),那么將導(dǎo)致某臺后端服務(wù)器的壓力可能非常大,而其他后端服務(wù)器卻還很空閑的不均衡情況。

?

總結(jié)

以上是生活随笔為你收集整理的《深入剖析NGINX》学习记录的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

国产精品va在线 | 深夜福利视频在线观看 | 在线精品一区二区 | 麻花豆传媒mv在线观看 | av一区二区三区在线播放 | 国精产品满18岁在线 | 九九九毛片| 日本动漫做毛片一区二区 | 精品人妖videos欧美人妖 | 日韩一区二区免费在线观看 | 亚洲自拍av在线 | 日本久久久久久科技有限公司 | 黄色一级大片在线观看 | 国产午夜精品久久 | 日本精品久久久久中文字幕 | 在线国产91 | 九九综合在线 | 日韩区欧美久久久无人区 | 西西4444www大胆视频 | 中文字幕网站视频在线 | 亚洲va在线va天堂 | 国产免费作爱视频 | 免费在线国产视频 | 国产精品美女久久久久久网站 | 亚洲狠狠丁香婷婷综合久久久 | 婷婷久久久久 | 香蕉视频免费看 | 亚洲视频综合在线 | www.五月激情.com | 国产精品女同一区二区三区久久夜 | 丝袜美腿一区 | 国产热re99久久6国产精品 | 一本色道久久精品 | 天天色天天射天天操 | www.日日操.com| 免费中文字幕在线观看 | 一区二区三区高清在线观看 | 成人a毛片 | 狠狠色丁香婷婷综合基地 | 亚洲激情视频在线观看 | 一区二区三区久久精品 | 97av影院 | 精品三级av | 激情五月激情综合网 | 天天激情综合 | 日韩精品久久一区二区三区 | 久久综合九色综合欧美就去吻 | 日韩欧美一区二区三区黑寡妇 | 亚洲欧洲xxxx | 中文在线免费观看 | 国产96在线视频 | 国产一区在线免费观看视频 | 亚洲精品av中文字幕在线在线 | 国产精品久久在线观看 | 国产成人精品一区一区一区 | 免费在线观看污网站 | 亚洲五月激情 | 日韩在线观看你懂得 | 国模精品一区二区三区 | 久久99国产综合精品免费 | 九七人人干 | 久久久免费精品 | 在线日韩亚洲 | 麻豆av一区二区三区在线观看 | 在线观看视频国产 | 欧美a级片免费看 | 欧美性色19p| 91中文字幕 | 97在线视频网站 | 免费在线播放 | 狠狠五月婷婷 | 午夜三级福利 | 91porny九色在线播放 | 最新av免费在线观看 | 色噜噜日韩精品一区二区三区视频 | 久久99久久99精品中文字幕 | 亚洲一级久久 | 精品国产美女在线 | 激情导航 | 中文字幕在线视频一区二区三区 | 四虎影视成人永久免费观看视频 | 天天操天天怕 | 亚洲精品国产精品国自产在线 | 免费高清在线观看电视网站 | 黄色在线网站噜噜噜 | 国偷自产视频一区二区久 | 99久久精品国产亚洲 | 青青网视频 | 在线观看成人小视频 | 九九精品在线观看 | 日日爽夜夜操 | 看国产黄色大片 | 在线免费观看成人 | 91亚色视频在线观看 | 亚洲精品视频二区 | 国产午夜小视频 | 日本久久久亚洲精品 | 久久久国产在线视频 | 欧美色综合久久 | 国产五月婷 | 黄网站污 | 国产午夜精品一区二区三区欧美 | 国内精品久久久久影院日本资源 | 九九热免费在线视频 | 亚洲亚洲精品在线观看 | av免费黄色 | 日日碰狠狠添天天爽超碰97久久 | 精品国产一区二区三区不卡 | 国产午夜精品一区二区三区 | 国产99在线免费 | www激情久久 | 99热这里精品 | 人人干天天射 | 人人干人人超 | 97操碰 | 91精品婷婷国产综合久久蝌蚪 | 国产精品视频内 | 亚洲一区二区三区miaa149 | aaa日本高清在线播放免费观看 | 欧美日韩精品网站 | 日韩欧美区 | 麻豆视频在线免费观看 | 2019中文最近的2019中文在线 | 久草在线91 | 国产成人精品一区二区三区免费 | 久草在线资源网 | 日本视频高清 | 日韩美一区二区三区 | 97天堂| 日韩av高潮 | 91电影福利 | 国产一级免费电影 | 97免费公开视频 | 久久一级片 | 91麻豆精品国产91久久久无需广告 | 99精品久久久久 | 免费毛片aaaaaa | 成人av一区二区在线观看 | 欧美精品午夜 | 中文字幕在线人 | 美女黄色网在线播放 | 国产精品自产拍在线观看中文 | 国产高清久久久久 | 亚洲精品久久久久久久不卡四虎 | 在线亚洲小视频 | 久久免费成人网 | 免费在线观看黄网站 | 99在线免费观看 | 国产精品免费视频一区二区 | 黄色一级大片在线观看 | 国产精品久久久久久超碰 | 久久综合色天天久久综合图片 | 国产精品自产拍在线观看 | 182午夜在线观看 | 天天综合狠狠精品 | 国产精品久久久久久99 | 亚洲经典精品 | 亚洲伊人网在线观看 | 亚洲一区精品二人人爽久久 | 亚洲精品福利在线 | 久久久国产99久久国产一 | 欧美另类xxx | 黄色a级片在线观看 | 人人爽人人爽人人片av免 | 激情五月婷婷综合网 | 波多野结衣在线观看视频 | 久久精品一二区 | 精品国产亚洲一区二区麻豆 | 亚洲日本激情 | 欧美激情精品久久久久久 | 欧美日韩伦理在线 | 国产视频一区二区在线观看 | 久久久久久久久久久久国产精品 | www.天天射.com| 国产麻豆精品一区二区 | 超碰人人在线观看 | 久草在线视频免费资源观看 | 在线免费黄网站 | 欧美一级爽 | 五月婷婷在线综合 | 91亚洲夫妻 | 国产午夜精品一区二区三区 | 色国产精品一区在线观看 | 免费看黄色小说的网站 | 嫩模bbw搡bbbb搡bbbb | 男女视频国产 | 99综合影院在线 | 色综合久久五月天 | 极品久久久久久久 | 欧美精品成人在线 | 国产欧美日韩视频 | 玖玖精品视频 | 国产成人a亚洲精品v | 欧美va日韩va| 久久精品国产99 | 国产91在线 | 美洲 | 一级一片免费看 | 中文字幕在线免费看线人 | 免费在线一区二区三区 | 久碰视频在线观看 | 99综合久久 | 国产97视频 | 免费一级片在线 | 久艹在线观看视频 | 精品国产一区二区三区久久影院 | 婷婷色中文网 | 欧美日韩一区二区免费在线观看 | 成人一级片视频 | 美女福利视频一区二区 | 青草视频免费观看 | 日韩欧美在线中文字幕 | 天天曰夜夜操 | 国模精品在线 | 911久久香蕉国产线看观看 | 高清av在线 | 国产一区二区久久久 | 亚洲成a人片综合在线 | 在线综合 亚洲 欧美在线视频 | 97综合网| 日韩欧美一区二区不卡 | 激情开心 | 狠狠狠色丁香婷婷综合久久五月 | 久久精品亚洲一区二区三区观看模式 | 丁香久久激情 | 欧美俄罗斯性视频 | 亚洲在线视频网站 | 国产黄色片免费观看 | 日韩三级免费观看 | 国产黄色在线看 | 久草在线综合 | 成年人免费在线观看网站 | 亚洲国产精品日韩 | www欧美色 | 久久精品一区二区三 | 国产91精品久久久久 | 九九热精品视频在线观看 | 久久精品www人人爽人人 | 欧美黄网站 | 国产亚洲午夜高清国产拍精品 | 国产精品2区 | 久久精品99北条麻妃 | 偷拍区另类综合在线 | 日日摸日日添夜夜爽97 | 在线免费高清一区二区三区 | 国产精品麻豆91 | 天天天干天天射天天天操 | 蜜桃视频在线视频 | 91亚洲精品视频 | 色婷婷综合视频在线观看 | 亚洲电影影音先锋 | 亚洲午夜精品久久久久久久久 | 夜夜骑日日 | 欧美日韩一区二区三区在线免费观看 | 色久av| 国产一区二区电影在线观看 | 国产一级二级三级视频 | 丁香婷婷激情 | 欧美资源在线观看 | 在线看片91 | 在线综合 亚洲 欧美在线视频 | 久久图 | 免费观看日韩 | 蜜臀av夜夜澡人人爽人人 | 人人爽人人爽人人爽人人爽 | 久久精品一区二区三区国产主播 | 9999精品免费视频 | 久久综合免费视频影院 | 天堂视频中文在线 | 国产专区视频 | 中文字幕人成不卡一区 | 色狠狠一区二区 | 夜夜操网 | 欧美视频一区二 | 五月婷婷视频在线 | 热99在线视频 | 久久久久免费 | 国产精品一区二区三区免费看 | 国产涩涩在线观看 | 人人玩人人添人人澡97 | 中文字幕五区 | 久久综合色天天久久综合图片 | 亚州av网站 | 国产成人av免费在线观看 | 波多野结衣亚洲一区二区 | 深夜福利视频在线观看 | 香蕉蜜桃视频 | 免费能看的黄色片 | 久久精品亚洲一区二区三区观看模式 | 99久久er热在这里只有精品15 | 日韩精品欧美专区 | 黄色日视频 | 国产免费叼嘿网站免费 | 免费国产在线视频 | 国产一级不卡毛片 | 日本黄色大片免费 | av不卡在线看 | 一区在线观看 | 久久久五月天 | 国产精品青草综合久久久久99 | 中文字幕在线观看网 | 日韩在线观看中文字幕 | 81精品国产乱码久久久久久 | 国产 日韩 欧美 自拍 | 亚洲精品在线免费观看视频 | 欧美亚洲一级片 | 久久久久久久久久免费视频 | 日本黄色免费大片 | 日韩爱爱网站 | 成人黄色毛片 | 九九av| 欧美国产视频在线 | 蜜臀一区二区三区精品免费视频 | 欧美一级淫片videoshd | 美女网站免费福利视频 | 亚洲成人在线免费 | a视频在线观看免费 | 97在线公开视频 | 亚洲天堂自拍视频 | 国产午夜精品久久久久久久久久 | 国产精品成久久久久三级 | 色婷婷在线观看视频 | 久草在线在线视频 | 国产一区在线播放 | 91丨精品丨蝌蚪丨白丝jk | 午夜天使| 91成人久久 | 亚洲无线视频 | 国产最新精品视频 | 在线观看日韩一区 | 精品福利视频在线观看 | 美女视频黄免费 | 欧美午夜视频在线 | 久久久国产精品麻豆 | 国产精品久久久免费 | 亚洲综合少妇 | 中文字幕在线观看完整 | 久久久久综合网 | 久草网站 | 国产一级黄色片免费看 | 天天看天天干 | 久久综合中文字幕 | 亚洲国产精彩中文乱码av | 亚洲精品中文在线观看 | 成人动态视频 | 精品国产视频在线观看 | 97电影在线 | 精品久久久久久亚洲综合网站 | 日韩午夜剧场 | 天天色天天艹 | 国产成人不卡 | 日日爽天天爽 | 欧美va天堂va视频va在线 | 国产女人18毛片水真多18精品 | 激情黄色av | 日韩精品视频在线观看网址 | 国产精品女同一区二区三区久久夜 | 日韩av黄| 超碰97国产精品人人cao | 国产成人一区三区 | 91欧美精品 | 成人91在线 | 精品国产乱码一区二区三区在线 | 久久综合色影院 | 亚洲激情综合网 | 色午夜影院| 在线观看国产亚洲 | 日韩午夜视频在线观看 | 久久久久久久久久伊人 | 国产精品色婷婷 | av不卡免费看 | 狠狠操天天射 | av电影亚洲 | 97色婷婷人人爽人人 | 九九九九精品 | 伊人久久精品久久亚洲一区 | 免费看一级特黄a大片 | 欧美日韩视频在线一区 | 成人综合婷婷国产精品久久免费 | 66av99精品福利视频在线 | 欧美日韩国产二区三区 | 一区二区视频免费在线观看 | 国产精品美女久久久久久久 | 国产色在线视频 | 激情动态 | 成人久久18免费网站麻豆 | 天天搞天天 | 欧美精品一区二区三区四区在线 | 国产成人区 | 国产美女精品视频 | 久久精品99久久久久久 | 国产一级精品在线观看 | 久久色亚洲| 高清av免费一区中文字幕 | 久久视频国产精品免费视频在线 | 日韩一区二区免费在线观看 | 精品久久1 | aaa免费毛片 | 国产高清无线码2021 | 久草在线这里只有精品 | 欧美资源在线观看 | 91丨九色丨蝌蚪丨对白 | 九九精品久久 | 91日韩精品一区 | 国内精品中文字幕 | 久久综合九色综合97婷婷女人 | 国产综合婷婷 | 99热这里只有精品在线观看 | 国产一区久久 | 国产视频不卡 | 婷婷丁香激情五月 | 国产精品九九热 | 亚洲综合国产精品 | 美女黄视频免费 | 九九九热精品免费视频观看 | 最近的中文字幕大全免费版 | 狠狠色丁香婷婷综合 | 91麻豆视频网站 | 欧美精品xx| 美女黄频网站 | 久久精品视频在线观看免费 | 国产免费又粗又猛又爽 | 久久国产影视 | a国产精品 | 91大神精品视频在线观看 | 九九热视频在线免费观看 | 中文字幕资源在线 | 免费视频网 | 免费看污污视频的网站 | 欧美性生交大片免网 | 久草综合在线观看 | 黄色网址国产 | 日本激情中文字幕 | 色婷婷在线视频 | 狠狠色丁香九九婷婷综合五月 | 久久99精品久久久久久久久久久久 | 丁香激情网 | 天天色天天射天天干 | 国产成人一区二区三区免费看 | 国产免费资源 | www操操| 在线观看日韩国产 | 婷婷深爱五月 | 日韩一区二区三区免费电影 | 丁香激情五月婷婷 | 久草免费在线观看视频 | 国产精品久久久久久久久久妇女 | 91麻豆精品国产91久久久使用方法 | 狠狠色丁香九九婷婷综合五月 | 91x色| av超碰在线 | 亚洲天堂社区 | 亚洲va欧美va人人爽春色影视 | 亚洲国产理论片 | 波多野结衣在线播放视频 | 婷婷在线资源 | 狠狠干美女 | 国产又粗又长的视频 | 五月婷婷丁香色 | 久久99视频精品 | 国产精品手机在线观看 | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | av三级av | 天天操天天射天天添 | 偷拍福利视频一区二区三区 | 偷拍区另类综合在线 | 欧美一区在线观看视频 | 国产一级二级在线播放 | 午夜精品久久久久99热app | 欧美日本不卡 | 日本最大色倩网站www | 国产精品毛片一区二区 | 啪啪小视频网站 | 亚洲精品高清一区二区三区四区 | 99精品视频免费在线观看 | 久久精品国产精品亚洲精品 | 91黄色影视 | 九九热在线观看视频 | 国产1区在线 | 久久福利精品 | 在线观看一二三区 | 不卡电影一区二区三区 | 国产一二三在线视频 | 国产在线视频一区二区 | 天天干天天拍天天操天天拍 | 精品国产乱码久久久久久1区2匹 | 91麻豆网 | 精品国产成人在线影院 | 色午夜| 草 免费视频 | 欧美日韩午夜爽爽 | 丁香六月婷婷 | 麻豆视频免费播放 | 精品视频在线观看 | 福利精品在线 | 久久xx视频| 久久久久亚洲精品男人的天堂 | 久久热首页 | 国产精品对白一区二区三区 | 又黄又网站 | 中文字幕在线观看的网站 | 久久精品中文字幕免费mv | 天天爽综合网 | av高清一区二区三区 | 国产高清绿奴videos | 久久综合网色—综合色88 | 国产精品成人久久 | 亚洲欧美怡红院 | 91你懂的 | 美女黄频在线观看 | 亚洲精品视频在线观看免费视频 | 久久精品男人的天堂 | 欧美日韩国产精品爽爽 | 天天操天天射天天 | 欧美aa一级片| 在线观看91 | 国产精品久久久久av福利动漫 | 免费福利在线观看 | 五月开心激情 | 国产精品av久久久久久无 | 天天射天天色天天干 | 欧美激情视频在线免费观看 | 欧美一区免费观看 | 精品一区二区三区在线播放 | 福利久久| 97成人在线观看视频 | 成人av av在线 | 欧美日韩亚洲在线 | 国产又粗又猛又爽 | 成人免费观看在线视频 | 久久精品久久久久久久 | 色婷婷狠狠五月综合天色拍 | 亚洲色影爱久久精品 | 高清在线观看av | 精品黄色在线观看 | 婷婷五月在线视频 | 亚洲在线精品视频 | 精品国产免费久久 | 91探花视频 | 六月天色婷婷 | 黄色免费视频在线观看 | 成年人看片 | 中文字幕在线免费观看视频 | 国产色综合天天综合网 | 久久精品99国产精品酒店日本 | 国产精品成人国产乱一区 | 探花视频在线观看+在线播放 | 免费av的网站 | 久久99热这里只有精品国产 | 日韩毛片在线播放 | 婷婷日 | 久久久国产一区二区三区 | 黄网站app在线观看免费视频 | 激情视频免费在线 | 中文字幕精| 免费高清无人区完整版 | 久久免费影院 | 久草在线免费资源站 | 91视频成人免费 | 日韩高清成人在线 | 欧洲色综合 | 999久久久久久久久 69av视频在线观看 | 成人亚洲精品久久久久 | www.99在线观看 | 色婷婷综合激情 | 中文字幕视频一区 | 中文字幕在线免费观看视频 | 国产高清视频在线免费观看 | 狠狠色丁香婷婷综合久小说久 | 久久在线视频精品 | 免费视频黄色 | 色综合婷婷久久 | 亚洲狠狠操 | 视频二区在线 | 97碰视频| 国产亚洲精品久久久久久久久久久久 | 欧洲色综合| 久一网站| 毛片网站在线观看 | 国产精品久久久久久久毛片 | 久久免费公开视频 | 精品嫩模福利一区二区蜜臀 | av免费网站在线观看 | 日韩亚洲在线视频 | 日韩美女高潮 | 973理论片235影院9 | 亚洲激情六月 | 色com| 97超碰资源网 | 青草视频在线 | 小草av在线播放 | 91在线观看视频网站 | 久久夜夜夜| 日韩av午夜在线观看 | 日韩色爱 | 四川妇女搡bbbb搡bbbb搡 | 国产97在线看 | 国产精品黄网站在线观看 | 中文字幕第一页在线vr | 国产视频在 | 免费网站观看www在线观看 | 国产精品地址 | 成年人免费看的视频 | 一区二区三区免费在线观看视频 | 国产在线中文 | 国产亚洲精品成人av久久影院 | 国产高清av在线播放 | 久久99热这里只有精品 | 国产精品久久久久久久久久久久午夜片 | 国产免费观看视频 | 国产一区二区在线免费播放 | 中文字幕婷婷 | 综合网五月天 | 成人一级黄色片 | 国产精品1区 | 日韩欧美综合在线视频 | 91精品麻豆 | 免费观看黄 | 99久久婷婷国产一区二区三区 | 国产亚洲激情视频在线 | 日韩av在线小说 | 五月开心婷婷网 | 成人久久综合 | 精品国产乱码久久久久久久 | 国产成人精品不卡 | 日韩精品一区二区三区不卡 | 国产精品久久久久三级 | 久草在| 国产专区第一页 | 精品一区91 | 精品久久久久免费极品大片 | av看片在线观看 | 97av在线 | 99re中文字幕| 国产女人免费看a级丨片 | 黄色aa久久 | 成人在线播放免费观看 | 国产三级视频在线 | 欧美日韩视频精品 | 国产精品久久久久永久免费看 | 久久成人高清 | 久久99热这里只有精品国产 | aa级黄色大片 | 黄色小说在线免费观看 | 色99视频| 美女久久久| 国产最新视频在线 | 国产伦理剧 | av在线播放国产 | 天天干天天操天天入 | 精品在线观看一区二区 | 国产精品国产三级国产aⅴ无密码 | 国产小视频在线 | 国产 欧美 在线 | 婷婷六月色 | 一级一片免费看 | 中文字幕一区二区三区乱码在线 | 国产在线观看你懂得 | 永久精品视频 | 亚洲成av人片在线观看无 | 国产手机视频在线播放 | 98超碰人人| 成人h视频在线 | 人人添人人 | 成年人在线免费视频观看 | 久色小说 | 91精品日韩| 一级黄色片在线免费观看 | 欧美一级免费在线 | 2019中文字幕第一页 | 久久综合网色—综合色88 | 美女网站久久 | 水蜜桃亚洲一二三四在线 | 国产精品综合在线 | 狠狠的操狠狠的干 | 成人亚洲精品国产www | 国产91精品一区二区麻豆亚洲 | 91香蕉视频色版 | 黄色成人91 | 在线视频1卡二卡三卡 | 欧美一级久久久久 | 国产在线一线 | 天天射天天舔天天干 | 亚洲精选视频免费看 | 97人人看| 国产精品成人aaaaa网站 | 久久情网 | 玖玖爱在线观看 | 人人玩人人弄 | 中文在线a∨在线 | 激情小说 五月 | 国产亚洲一区二区三区 | 久久精品国产一区二区电影 | 女人高潮特级毛片 | 精品国产一区二区三区四区在线观看 | 久久久久久久久久久综合 | 亚洲欧洲视频 | 又粗又长又大又爽又黄少妇毛片 | 国产人免费人成免费视频 | 国产精品久一 | 国产午夜精品久久久久久久久久 | 夜又临在线观看 | 天天操夜夜拍 | 亚洲乱码国产乱码精品天美传媒 | 国产精品美女久久久久久久网站 | 激情网综合 | 国产免费又爽又刺激在线观看 | 亚洲高清91 | 国产精品综合久久 | 亚洲国产合集 | 日韩动态视频 | 成人在线观看免费视频 | 久久综合网色—综合色88 | 成人欧美亚洲 | 四虎成人网 | 欧美大片大全 | 亚洲精品在线播放视频 | 国产麻豆精品久久 | 欧美一级免费黄色片 | 精品国产乱码久久久久久浪潮 | 久久av在线播放 | 国内精品久久久久国产 | 久久久精品视频成人 | 高清免费av在线 | 99久久这里有精品 | 在线看日韩 | 91在线视频| 最近乱久中文字幕 | 干天天 | 日韩精品中文字幕在线播放 | 麻豆91精品视频 | 在线黄色免费 | 天天天天天天天天操 | 久久久天堂 | 久草在线播放视频 | 久久视讯 | 国产美腿白丝袜足在线av | 国产一级视频在线 | 成人免费视频观看 | 久久狠狠一本精品综合网 | 亚洲精品视频在线免费 | 一级片免费在线 | 日韩经典一区二区三区 | 久久国产日韩 | 国产成人av免费在线观看 | 91成人精品观看 | 久久国产热视频 | 最新在线你懂的 | 国产一区二区免费 | 亚洲做受高潮欧美裸体 | 亚洲成人精品在线 | 欧美日韩超碰 | 成人午夜网 | 91看片麻豆 | 亚洲欧美在线视频免费 | 天天操天天操天天操天天操天天操天天操 | 伊人开心激情 | 日韩视频免费在线观看 | 超碰人人干人人 | 天天综合网国产 | 成人黄色影片在线 | 国产91勾搭技师精品 | 国产高清免费在线播放 | 免费在线观看视频a | 成年人免费观看国产 | wwwwww色| 欧洲精品视频一区二区 | 欧美性成人 | 久久婷亚洲五月一区天天躁 | 伊人天天色 | 在线精品视频在线观看高清 | 9在线观看免费高清完整版 玖玖爱免费视频 | 久草剧场 | 四虎永久网站 | 一区二区三区国产欧美 | 欧美超碰在线 | 国产精品视频永久免费播放 | 久久久久久久久久久黄色 | 视频一区视频二区在线观看 | 国产美女视频黄a视频免费 久久综合九色欧美综合狠狠 | 日韩欧美精品一区二区 | 免费成视频 | 国产精品一区免费在线观看 | 国产精品视频免费在线观看 | 日韩色中色 | 很污的网站 | 久久免费国产精品 | av电影免费看| 一本一本久久a久久精品综合 | 日本中文在线观看 | 久久九九免费视频 | 久久久久久久免费看 | 日本久久久久久科技有限公司 | 99精品视频在线观看视频 | 视频在线观看99 | 久久久精品国产免费观看一区二区 | 色之综合网 | 成人免费观看视频大全 | 91亚洲在线 | 中文永久字幕 | 在线小视频 | 在线黄色免费av | 国产 字幕 制服 中文 在线 | 日韩久久久久久久久久 | 国产精品久久久久高潮 | 午夜精品一区二区国产 | 天天操天天摸天天射 | 91福利视频在线 | 日韩视频免费在线 | 男女激情网址 | 成人午夜电影在线 | 日韩理论视频 | 99精品国产成人一区二区 | 欧美精品一区二区免费 | 五月激情电影 | 最近中文字幕完整高清 | 激情电影影院 | 中文字幕在线一区二区三区 | av中文在线影视 | 在线免费观看欧美日韩 | 日韩精品 在线视频 | 欧美疯狂性受xxxxx另类 | 国产成人av电影 | 99视频播放 | 久久久亚洲国产精品麻豆综合天堂 | 国产高清视频在线 | 国产精品福利久久久 | 国产视频日韩视频欧美视频 | 天天射天天干天天插 | 亚洲最大激情中文字幕 | 国产色小视频 | 中文字幕日本电影 | 亚洲欧美日韩精品久久久 | 日本成人免费在线观看 | 国产精品丝袜在线 | 久草资源在线观看 | 正在播放国产一区二区 | 99视频在线 | 中文字幕在线视频一区二区 | 中文字幕国产视频 | 国际精品久久 | 中文视频在线播放 | 狠狠色丁香久久婷婷综 | 天天操夜夜看 | 高清精品在线 | 亚洲一区二区精品3399 | 在线免费三级 | 在线观看免费高清视频大全追剧 | 91成人午夜 | 亚洲视频大全 | 久久激情五月丁香伊人 | 久久久免费观看 | 国产精品久久久久久久久久久久午夜 | 国产精品久久久久久久午夜片 | 天天色天天草天天射 | 麻豆网站免费观看 | 麻豆视传媒官网免费观看 | 精品国产一区二区三区男人吃奶 | 久草在线99 | 亚洲丝袜一区二区 | 免费日韩 精品中文字幕视频在线 | 久久久久久久电影 | 国产精品欧美一区二区三区不卡 | 国产一线二线三线在线观看 | 最近日韩中文字幕中文 | 国产精品精品国产婷婷这里av | 久草99| 99精品国产免费久久久久久下载 | 国产看片免费 | free. 性欧美.com | 中文字幕久久久精品 | 日韩大陆欧美高清视频区 | 免费观看一级特黄欧美大片 | 久久极品| 91中文在线 | 亚洲精品玖玖玖av在线看 | 国产中的精品av小宝探花 | 国产视频久久久久 | 国产亚洲精品久久久久久电影 | 国产理论免费 | 国产精品一区二区麻豆 | 99精品国产99久久久久久97 | 国产视频二区三区 | a在线观看免费视频 | 色人久久 | 97精品久久 | 中文字幕有码在线观看 | www看片网站 | 婷婷综合亚洲 | 精品亚洲午夜久久久久91 | 免费高清影视 | 欧美日韩国产精品一区二区 | 日本深夜福利视频 | 最近的中文字幕大全免费版 | 最新av免费在线 | 亚洲精品久久在线 | av片一区二区 | 日韩视频图片 | 精品国产一区二区三区av性色 | 久草在线资源观看 | 依人成人综合网 | 久久免费99精品久久久久久 | 99精品热视频只有精品10 | 亚洲午夜久久久久久久久 | 国产爽妇网 | 日本视频网 | 永久免费的啪啪网站免费观看浪潮 | 麻豆视频免费在线观看 | 黄色成人影院 | av免费在线观看1 | www.天天色| 97精品电影院 | 中国一级片在线播放 | 天天躁天天狠天天透 | 日韩在线三级 | 超碰97免费 | 色国产精品一区在线观看 | 九九九九色 | 中文一区在线 | 亚洲日日夜夜 | 国产精品美女久久久久久久 | 日韩视频中文 | 三级小视频在线观看 | 中文字幕视频网站 | www.午夜视频| 美女视频黄是免费的 | 国产精品久久久久久久免费大片 | 久久精品波多野结衣 | 美女精品国产 | 亚洲最新在线 | 97碰在线视频 | 天天干天天操天天 | 91探花国产综合在线精品 | 日本性久久 | 国产高清黄 | 成人av观看| 亚洲高清资源 | 久久综合久久综合久久 | 欧美色伊人 | 亚洲精品在线观看免费 | 五月天综合网站 | 国产1区2区 | 99久久婷婷国产一区二区三区 | 免费看片网址 | 国内成人精品2018免费看 | 国产精品久久久久四虎 | 亚洲成人网av | 香蕉免费 | 国产精品欧美久久久久久 | 五月婷婷综合网 | www.黄色小说.com | 日本精品视频免费观看 | 亚洲精品一区二区三区新线路 | 日韩和的一区二在线 | 成人av电影免费 | 九九视频精品免费 | 精品一区二区免费视频 | 精品中文字幕在线 | 国产黄色成人 | 日韩专区av | 国产精品福利久久久 | 国内外成人在线 | 久久国产精品偷 | 99免费在线视频 | 国产在线自| 亚洲精品日韩一区二区电影 | 麻豆精品传媒视频 | 免费看国产一级片 | 国产在线观看免费 | 久久精品一二三区 | 久久99日韩 | 91麻豆精品国产自产 | av在线影片 | 精品久久久久久亚洲综合网站 | 亚洲一二三区精品 | 久久精品99久久 | 欧美 日韩精品 | av在线不卡观看 | 久久精品美女视频网站 | 热九九精品 | 91pony九色丨交换 | 91在线播放国产 | 色诱亚洲精品久久久久久 | 69国产成人综合久久精品欧美 | 综合伊人久久 | 超碰免费久久 | 久久九九免费视频 | 高潮久久久久久久久 | 激情综合色综合久久综合 | 国产亚洲精品久久久久久电影 | 国产精品一区二区在线观看 | 丁香激情视频 | 91热爆在线观看 | 草久在线观看视频 | av片一区二区 | 久久精品一区二区三 | 超碰在线人人97 | 免费看av片网站 | 国产中文视频 |