memcache是一款高性能的分布式cache系統,得到了非常廣泛的應用。memcache定義了一套私有通信協議,使得不能通過HTTP請求來訪問memcache。但協議本身簡單高效,而且memcache使用廣泛,所以大部分現代開發語言和平臺都提供了memcache支持,方便開發者使用memcache。
nginx提供了ngx_http_memcached模塊,提供從memcache讀取數據的功能,而不提供向memcache寫數據的功能。作為web服務器,這種設計是可以接受的。
下面,我們開始分析ngx_http_memcached模塊,一窺upstream的奧秘。
#### Handler模塊?[](http://tengine.taobao.org/book/chapter_05.html#handler "永久鏈接至標題")
初看memcached模塊,大家可能覺得并無特別之處。如果稍微細看,甚至覺得有點像handler模塊,當大家看到這段代碼以后,必定疑惑為什么會跟handler模塊一模一樣。
[](http:// "點擊提交Issue,反饋你的意見...")
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_memcached_handler;
因為upstream模塊使用的就是handler模塊的接入方式。同時,upstream模塊的指令系統的設計也是遵循handler模塊的基本規則:配置該模塊才會執行該模塊。
[](http:// "點擊提交Issue,反饋你的意見...")
{ ngx_string("memcached_pass"),
NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
ngx_http_memcached_pass,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL }
所以大家覺得眼熟是好事,說明大家對Handler的寫法已經很熟悉了。
[](http:// "點擊提交Issue,反饋你的意見...")
#### Upstream模塊
那么,upstream模塊的特別之處究竟在哪里呢?答案是就在模塊處理函數的實現中。upstream模塊的處理函數進行的操作都包含一個固定的流程。在memcached的例子中,可以觀察ngx_http_memcached_handler的代碼,可以發現,這個固定的操作流程是:
1. 創建upstream數據結構。
[](http:// "點擊提交Issue,反饋你的意見...")
if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
2. 設置模塊的tag和schema。schema現在只會用于日志,tag會用于buf_chain管理。
[](http:// "點擊提交Issue,反饋你的意見...")
u = r->upstream;
ngx_str_set(&u->schema, "memcached://");
u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
3. 設置upstream的后端服務器列表數據結構。
[](http:// "點擊提交Issue,反饋你的意見...")
mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
u->conf = &mlcf->upstream;
4. 設置upstream回調函數。在這里列出的代碼稍稍調整了代碼順序。
[](http:// "點擊提交Issue,反饋你的意見...")
u->create_request = ngx_http_memcached_create_request;
u->reinit_request = ngx_http_memcached_reinit_request;
u->process_header = ngx_http_memcached_process_header;
u->abort_request = ngx_http_memcached_abort_request;
u->finalize_request = ngx_http_memcached_finalize_request;
u->input_filter_init = ngx_http_memcached_filter_init;
u->input_filter = ngx_http_memcached_filter;
5. 創建并設置upstream環境數據結構。
[](http:// "點擊提交Issue,反饋你的意見...")
ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ctx->rest = NGX_HTTP_MEMCACHED_END;
ctx->request = r;
ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);
u->input_filter_ctx = ctx;
6. 完成upstream初始化并進行收尾工作。
[](http:// "點擊提交Issue,反饋你的意見...")
r->main->count++;
ngx_http_upstream_init(r);
return NGX_DONE;
任何upstream模塊,簡單如memcached,復雜如proxy、fastcgi都是如此。不同的upstream模塊在這6步中的最大差別會出現在第2、3、4、5上。其中第2、4兩步很容易理解,不同的模塊設置的標志和使用的回調函數肯定不同。第5步也不難理解,只有第3步是最為晦澀的,不同的模塊在取得后端服務器列表時,策略的差異非常大,有如memcached這樣簡單明了的,也有如proxy那樣邏輯復雜的。這個問題先記下來,等把memcached剖析清楚了,再單獨討論。
第6步是一個常態。將count加1,然后返回NGX_DONE。nginx遇到這種情況,雖然會認為當前請求的處理已經結束,但是不會釋放請求使用的內存資源,也不會關閉與客戶端的連接。之所以需要這樣,是因為nginx建立了upstream請求和客戶端請求之間一對一的關系,在后續使用ngx_event_pipe將upstream響應發送回客戶端時,還要使用到這些保存著客戶端信息的數據結構。這部分會在后面的原理篇做具體介紹,這里不再展開。
將upstream請求和客戶端請求進行一對一綁定,這個設計有優勢也有缺陷。優勢就是簡化模塊開發,可以將精力集中在模塊邏輯上,而缺陷同樣明顯,一對一的設計很多時候都不能滿足復雜邏輯的需要。對于這一點,將會在后面的原理篇來闡述。
[](http:// "點擊提交Issue,反饋你的意見...")
#### 回調函數[](http://tengine.taobao.org/book/chapter_05.html#id3 "永久鏈接至標題")
前面剖析了memcached模塊的骨架,現在開始逐個解決每個回調函數。
1. ngx_http_memcached_create_request:很簡單的按照設置的內容生成一個key,接著生成一個“get $key”的請求,放在r->upstream->request_bufs里面。
2. ngx_http_memcached_reinit_request:無需初始化。
3. ngx_http_memcached_abort_request:無需額外操作。
4. ngx_http_memcached_finalize_request:無需額外操作。
5. ngx_http_memcached_process_header:模塊的業務重點函數。memcache協議的頭部信息被定義為第一行文本,可以找到這段代碼證明:
[](http:// "點擊提交Issue,反饋你的意見...")
for (p = u->buffer.pos; p < u->buffer.last; p++) {
if ( * p == LF) {
goto found;
}
如果在已讀入緩沖的數據中沒有發現LF(‘n’)字符,函數返回NGX_AGAIN,表示頭部未完全讀入,需要繼續讀取數據。nginx在收到新的數據以后會再次調用該函數。
nginx處理后端服務器的響應頭時只會使用一塊緩存,所有數據都在這塊緩存中,所以解析頭部信息時不需要考慮頭部信息跨越多塊緩存的情況。而如果頭部過大,不能保存在這塊緩存中,nginx會返回錯誤信息給客戶端,并記錄error log,提示緩存不夠大。
process_header的重要職責是將后端服務器返回的狀態翻譯成返回給客戶端的狀態。例如,在ngx_http_memcached_process_header中,有這樣幾段代碼:
[](http:// "點擊提交Issue,反饋你的意見...")
r->headers_out.content_length_n = ngx_atoof(len, p - len - 1);
u->headers_in.status_n = 200;
u->state->status = 200;
u->headers_in.status_n = 404;
u->state->status = 404;
u->state用于計算upstream相關的變量。比如u->state->status將被用于計算變量“upstream_status”的值。u->headers_in將被作為返回給客戶端的響應返回狀態碼。而第一行則是設置返回給客戶端的響應的長度。
在這個函數中不能忘記的一件事情是處理完頭部信息以后需要將讀指針pos后移,否則這段數據也將被復制到返回給客戶端的響應的正文中,進而導致正文內容不正確。
[](http:// "點擊提交Issue,反饋你的意見...")
u->buffer.pos = p + 1;
process_header函數完成響應頭的正確處理,應該返回NGX_OK。如果返回NGX_AGAIN,表示未讀取完整數據,需要從后端服務器繼續讀取數據。返回NGX_DECLINED無意義,其他任何返回值都被認為是出錯狀態,nginx將結束upstream請求并返回錯誤信息。
6. ngx_http_memcached_filter_init:修正從后端服務器收到的內容長度。因為在處理header時沒有加上這部分長度。
7. ngx_http_memcached_filter:memcached模塊是少有的帶有處理正文的回調函數的模塊。因為memcached模塊需要過濾正文末尾CRLF “END” CRLF,所以實現了自己的filter回調函數。處理正文的實際意義是將從后端服務器收到的正文有效內容封裝成ngx_chain_t,并加在u->out_bufs末尾。nginx并不進行數據拷貝,而是建立ngx_buf_t數據結構指向這些數據內存區,然后由ngx_chain_t組織這些buf。這種實現避免了內存大量搬遷,也是nginx高效的奧秘之一。
- 上篇:nginx模塊開發篇
- nginx平臺初探
- 初探nginx架構
- nginx基礎概念
- connection
- request
- keepalive
- pipe
- lingering_close
- 基本數據結構
- ngx_str_t
- ngx_pool_t
- ngx_array_t
- ngx_hash_t
- ngx_hash_wildcard_t
- ngx_hash_combined_t
- ngx_hash_keys_arrays_t
- ngx_chain_t
- ngx_buf_t
- ngx_list_t
- ngx_queue_t
- nginx的配置系統
- 指令參數
- 指令上下文
- nginx的模塊化體系結構
- 模塊的分類
- nginx的請求處理
- handler模塊
- handler模塊簡介
- 模塊的基本結構
- 模塊配置結構
- 模塊配置指令
- 模塊上下文結構
- 模塊的定義
- handler模塊的基本結構
- handler模塊的掛載
- handler的編寫步驟
- 示例: hello handler 模塊
- handler模塊的編譯和使用
- 更多handler模塊示例分析
- http access module
- http static module
- http log module
- 過濾模塊
- 過濾模塊簡介
- 過濾模塊的分析
- upstream模塊
- upstream模塊
- upstream模塊接口
- memcached模塊分析
- 本節回顧
- 負載均衡模塊
- 配置
- 指令
- 鉤子
- 初始化配置
- 初始化請求
- peer.get和peer.free回調函數
- 本節回顧
- 其他模塊
- core模塊
- event模塊
- 模塊開發高級篇
- 變量
- 下篇:nginx原理解析篇
- nginx架構詳解
- nginx的源碼目錄結構
- nginx的configure原理
- 模塊編譯順序
- nginx基礎設施
- 內存池
- nginx的啟動階段
- 概述
- 共有流程
- 配置解析
- nginx的請求處理階段
- 接收請求流程
- http請求格式簡介
- 請求頭讀取
- 解析請求行
- 解析請求頭
- 請求體讀取
- 讀取請求體
- 丟棄請求體
- 多階段處理請求
- 多階段執行鏈
- POST_READ階段
- SERVER_REWRITE階段
- FIND_CONFIG階段
- REWRITE階段
- POST_REWRITE階段
- PREACCESS階段
- ACCESS階段
- POST_ACCESS階段
- TRY_FILES階段
- CONTENT階段
- LOG階段
- Nginx filter
- header filter分析
- body filter分析
- ngx_http_copy_filter_module分析
- ngx_http_write_filter_module分析
- subrequest原理解析
- https請求處理解析
- 附錄A 編碼風格
- 附錄B 常用API
- 附錄C 模塊編譯,調試與測試