Nginx中間件入門(mén)指南
Nginx (engine x) 是一個(gè)高性能的HTTP和反向代理web服務(wù)器,同時(shí)也提供了IMAP/POP3/SMTP服務(wù)。Nginx是由伊戈?duì)?middot;賽索耶夫?yàn)槎砹_斯訪問(wèn)量第二的Rambler.ru站點(diǎn)(俄文:Рамблер)開(kāi)發(fā)的,公開(kāi)版本1.19.6發(fā)布于2020年12月15日。
其將源代碼以類(lèi)BSD許可證的形式發(fā)布,因它的穩(wěn)定性、豐富的功能集、簡(jiǎn)單的配置文件和低系統(tǒng)資源的消耗而聞名。2022年01月25日,nginx 1.21.6發(fā)布。
Nginx是一款輕量級(jí)的Web 服務(wù)器/反向代理服務(wù)器及電子郵件(IMAP/POP3)代理服務(wù)器,在BSD-like 協(xié)議下發(fā)行。其特點(diǎn)是占有內(nèi)存少,并發(fā)能力強(qiáng),事實(shí)上nginx的并發(fā)能力在同類(lèi)型的網(wǎng)頁(yè)服務(wù)器中表現(xiàn)較好。
一、初識(shí)Nginx
Nginx是什么?
Nginx也是一個(gè)web服務(wù)器,能夠支持正向代理(代理用戶去訪問(wèn)服務(wù)器)、反向代理(代理服務(wù)器來(lái)讓用戶訪問(wèn))、負(fù)載均衡(將用戶的訪問(wèn)分發(fā)給服務(wù)器集群)。
conf的作用及讀取過(guò)程
Nginx的系統(tǒng)配置conf文件有什么作用?
用于定義 Nginx 的全局和特定站點(diǎn)的行為。
- 有master進(jìn)程監(jiān)聽(tīng)到有用戶連接請(qǐng)求后,worker進(jìn)程通過(guò)原子操作搶占連接,加入epoll監(jiān)聽(tīng),其中worker進(jìn)程的數(shù)量在conf中確定。
- http塊下多個(gè)的server塊,每個(gè)server塊代表一個(gè)虛擬主機(jī),定義虛擬主機(jī)的設(shè)置,如監(jiān)聽(tīng)端口、路由規(guī)則。
- 在server塊中定義分發(fā)的服務(wù)器ip地址,實(shí)現(xiàn)負(fù)載均衡。
worker_processes 4;
events {
worker_connections 1024;
}
http {
upstream backend {
server 192.168.159.130:9002 weight=2;//可以是任意的服務(wù)器地址 不一定是本機(jī)的
server 192.168.159.130:9003 weight=2;
}
server {
listen 9000;
location / {//路由規(guī)則
#root /home/king/share/nginx/html9000/;
proxy_pass http://backend; # ...轉(zhuǎn)發(fā)給 backend 服務(wù)器池處理
}
}
server {
listen 9001;
location / {
root /home/king/share/nginx/html9001/;# ...由本機(jī)文件系統(tǒng)處理
}
}
server {
listen 9002;
location / {
root /home/king/share/nginx/html9002/;
}
}
server {
listen 9003;
location / {
root /home/king/share/nginx/html9003/;
}
}
}Nginx在代碼中對(duì)conf文件的處理是怎么樣的?
Nginx 主進(jìn)程啟動(dòng),readline逐行讀取conf內(nèi)容。
例如:當(dāng)讀取并解析到
worker_processes 1024;
這行配置時(shí),在內(nèi)存中會(huì)發(fā)生以下事情:
- 創(chuàng)建一塊內(nèi)存,并且并將其初始化為一個(gè)
ngx_core_conf_t結(jié)構(gòu)體,用一個(gè)conf指針指向 - 將conf指針傳入
ngx_set_worker_processes函數(shù),這是讀到worker_processes后觸發(fā)的回調(diào)函數(shù) - 在函數(shù)內(nèi)部根據(jù)"1024",修改
ngx_core_conf_t結(jié)構(gòu)體中worker_processes 字段的值
HTTP狀態(tài)機(jī)
Nginx的狀態(tài)機(jī)是什么?
在Nginx源碼里有作者自定義的11個(gè)http狀態(tài),也有一份黑白名單,里面限制了某些ip、限制了某些資源的訪問(wèn)、限制了某些用戶的訪問(wèn),這些限制也是存在于特定狀態(tài)下的。通過(guò)明確的階段劃分,Nginx 實(shí)現(xiàn)了處理流程的標(biāo)準(zhǔn)化和模塊化。

Nginx驚群
Nginx的驚群現(xiàn)象及應(yīng)對(duì)方法是什么?
Nginx 的“驚群現(xiàn)象”是指:
在多進(jìn)程(worker)模式下,多個(gè)進(jìn)程同時(shí)等待某個(gè)事件(如新連接),當(dāng)事件發(fā)生時(shí),所有進(jìn)程都被喚醒,但只有一個(gè)能成功 accept,其他進(jìn)程再次休眠,造成資源浪費(fèi)和性能下降。
應(yīng)對(duì)方法:
在 worker 進(jìn)程間引入 accept_mutex(接受互斥鎖)。只有獲得鎖的進(jìn)程才會(huì)去 accept 新連接,其他進(jìn)程等待,避免所有進(jìn)程一起被喚醒。通過(guò)配置 conf中events模塊的accept_mutex on實(shí)現(xiàn)
二、Nginx組件拆出來(lái)使用
Nginx內(nèi)部實(shí)現(xiàn)了很多組件,在鏈接對(duì)應(yīng)頭文件的基礎(chǔ)上,可以直接編寫(xiě)代碼,調(diào)用其特定的組件比如:內(nèi)存池、線程池、原子操作、日志、數(shù)據(jù)結(jié)構(gòu)(ngx_str_t ;ngx_array_t ;ngx_list_t ;ngx_rbtree_t)
下面是內(nèi)存池、string、日志模塊的拆分使用
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "nginx.h"
#include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"
#include "ngx_array.h"
#include "ngx_hash.h"
#define unused(x) x=x
volatile ngx_cycle_t *ngx_cycle;
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
const char *fmt, ...) { //日志模塊調(diào)用
unused(level);
unused(log);
unused(err);
unused(fmt);
}
void print_pool(ngx_pool_t *pool) {
printf("\nlast: %p, end: %p\n", pool->d.last, pool->d.end);
}
int main() {
#if 0
ngx_str_t name = ngx_string("King"); //string數(shù)據(jù)結(jié)構(gòu)使用
printf("name --> len: %ld, data: %s\n", name.len, name.data);
#elif 0
ngx_pool_t *pool = ngx_create_pool(4096, NULL); //ngx_pool_t 內(nèi)存池使用
print_pool(pool);
int *p1 = ngx_palloc(pool, sizeof(int));
print_pool(pool);
void *p2 = ngx_palloc(pool, 0x10);
print_pool(pool);
void *p3 = ngx_palloc(pool, 0x15);
print_pool(pool);
ngx_destroy_pool(pool);
#else
#endif
}三、Nginx模塊開(kāi)發(fā)
過(guò)濾器模塊
接收到后端的response,返回response給瀏覽器客戶端
任務(wù):開(kāi)發(fā)一個(gè)在 HTTP 響應(yīng)的 HTML 內(nèi)容前插入一段固定的文本?(如作者信息和鏈接)的Nginx HTTP Filter 模塊,ngx_http_prefix_filter_module.c。
流程:在讀取到conf的"add_prefix"后,會(huì)執(zhí)行對(duì)應(yīng)的set函數(shù),將對(duì)應(yīng)的值保存到讀取到location模塊初始化創(chuàng)建的配置結(jié)構(gòu)體里面。當(dāng)conf文件讀取結(jié)束,會(huì)觸發(fā)prefix過(guò)濾器初始化,將我們的prefix_filter加入到Nginx的Filter鏈表里面。當(dāng)后端傳來(lái)了HTTP響應(yīng),就會(huì)觸發(fā)我們的header_top和body_top過(guò)濾器函數(shù),在header函數(shù)中里面檢查和標(biāo)記請(qǐng)求,在body函數(shù)中加入對(duì)應(yīng)的文本,然后傳入下一個(gè)過(guò)濾器。
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_flag_t enable; # 是否啟用模塊(0/1)
} ngx_http_prefix_filter_conf_t;
typedef struct {
ngx_int_t add_prefix; #標(biāo)記是否需要插入前綴(0=不插入,1=需要插入,2=已插入)
} ngx_http_prefix_filter_ctx_t;
三個(gè)核心處理函數(shù)
static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf);
static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in);
static ngx_str_t filter_prefix = ngx_string("<h2>Author : King</h2><p><a href=\"http://www.0voice.com\">0voice</a></p>");
static void *ngx_http_prefix_filter_create_conf(ngx_conf_t *cf) {
#創(chuàng)建一個(gè)獨(dú)立的配置結(jié)構(gòu)體,并初始化默認(rèn)值。
ngx_http_prefix_filter_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_prefix_filter_conf_t));
if (conf == NULL) {
return NULL;
}
conf->enable = NGX_CONF_UNSET;
return conf;
}
static char *ngx_http_prefix_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) {#繼承配置
ngx_http_prefix_filter_conf_t *prev = (ngx_http_prefix_filter_conf_t*)parent;// 父配置(如 server 塊)
ngx_http_prefix_filter_conf_t *conf = (ngx_http_prefix_filter_conf_t*)child;// 子配置(如 location 塊)
// 合并規(guī)則:如果子配置未設(shè)置,則繼承父配置的值
ngx_conf_merge_value(conf->enable, prev->enable, 0);
return NGX_CONF_OK;
}
static ngx_command_t ngx_http_prefix_filter_commands[] = {
#conf中可用的指令
{
ngx_string("add_prefix"), # 指令名
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_FLAG,#指令可能出現(xiàn)的在conf中的部分
ngx_conf_set_flag_slot, #遇到命令的時(shí)候執(zhí)行這個(gè)set函數(shù)
NGX_HTTP_LOC_CONF_OFFSET, #讀取到conf中add_prefix值后,存儲(chǔ)的位置(location 級(jí)別)
offsetof(ngx_http_prefix_filter_conf_t, enable), #具體存儲(chǔ)在結(jié)構(gòu)體的哪個(gè)位置
NULL
},
ngx_null_command //結(jié)尾標(biāo)識(shí)
};
static ngx_http_module_t ngx_http_prefix_filter_module_ctx = {
NULL,
ngx_http_prefix_filter_init, #讀完最后一行conf時(shí)執(zhí)行此回調(diào)函數(shù)
NULL, #解析到主模塊 執(zhí)行此回調(diào)函數(shù)
NULL, // 初始化主配置
NULL, #解析到server塊 執(zhí)行此回調(diào)函數(shù)
NULL, // server配置繼承
ngx_http_prefix_filter_create_conf, #解析到location塊 執(zhí)行此回調(diào)函數(shù) 會(huì)創(chuàng)建一個(gè)獨(dú)立的配置結(jié)構(gòu)體,并初始化默認(rèn)值
ngx_http_prefix_filter_merge_conf //location配置繼承
};
ngx_module_t ngx_http_prefix_filter_module = { #模塊屬性
NGX_MODULE_V1,
&ngx_http_prefix_filter_module_ctx, #模塊上下文
ngx_http_prefix_filter_commands, #模塊的命令
NGX_HTTP_MODULE, #模塊的類(lèi)型
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
Nginx 處理 HTTP 響應(yīng)時(shí),會(huì)依次調(diào)用兩類(lèi) Filter:?Header Filter?:處理響應(yīng)頭;?Body Filter?:處理響應(yīng)體(如修改 HTML 內(nèi)容)。這些 Filter 以鏈表形式組織,下面是頭插法插入我們的Header Filter與Body Filter。
static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf) {
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_prefix_filter_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_prefix_filter_body_filter;
return NGX_OK;
}
static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r) {
#檢查并標(biāo)記請(qǐng)求
ngx_http_prefix_filter_ctx_t *ctx;
ngx_http_prefix_filter_conf_t *conf;
if (r->headers_out.status != NGX_HTTP_OK) {
return ngx_http_next_header_filter(r);
}
// 獲取模塊配置
ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);
if (ctx) {
return ngx_http_next_header_filter(r);
}
// 檢查是否已處理過(guò)(防止重復(fù)處理)
conf = ngx_http_get_module_loc_conf(r, ngx_http_prefix_filter_module);
if (conf == NULL) {
return ngx_http_next_header_filter(r);
}
if (conf->enable == 0) {
return ngx_http_next_header_filter(r);
}
// 為需要加入的前綴 分配空間,隨后把這個(gè)指針和當(dāng)前的HTTP請(qǐng)求綁定
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_prefix_filter_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ctx->add_prefix = 0;
ngx_http_set_ctx(r, ctx, ngx_http_prefix_filter_module);
// 僅對(duì) text/html 響應(yīng)插入前綴
if (r->headers_out.content_type.len >= sizeof("text/html") - 1
&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char*)"text/html", sizeof("text/html")-1) == 0) {
ctx->add_prefix = 1;// 標(biāo)記需要插入前綴
if (r->headers_out.content_length_n > 0) {
r->headers_out.content_length_n += filter_prefix.len;// 修正 Content-Length
}
}
return ngx_http_prefix_filter_header_filter(r);
}
static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {
#插入前綴內(nèi)容
ngx_http_prefix_filter_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);
if (ctx == NULL || ctx->add_prefix != 1) {
return ngx_http_next_body_filter(r, in);
}
#標(biāo)記已處理
ctx->add_prefix = 2;
# 創(chuàng)建包含前綴內(nèi)容的緩沖區(qū),存放預(yù)設(shè)的前綴文本(filter_prefix)
ngx_buf_t *b = ngx_create_temp_buf(r->pool, filter_prefix.len);
b->start = b->pos = filter_prefix.data;
b->last = b->pos + filter_prefix.len;
# 將前綴緩沖區(qū)插入到響應(yīng)體鏈表的頭部
ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);
cl->buf = b;
cl->next = in;
return ngx_http_next_body_filter(r, cl);
}編寫(xiě)ngx_http_prefix_filter_module.c后,還需要在同一目錄下編寫(xiě)config文件,讓Nginx識(shí)別到這個(gè)模塊。
#模塊的名字 ngx_addon_name=ngx_http_prefix_filter_module #Makefile中的關(guān)鍵字 在其后面加上我們的庫(kù) HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_prefix_filter_module" #源文件的路徑 NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_prefix_filter_module.c"
handler模塊
接收瀏覽器客戶端請(qǐng)求,直接返回請(qǐng)求給瀏覽器客戶端
任務(wù):統(tǒng)計(jì)頁(yè)面訪問(wèn)次數(shù),可以直接在Nginx上通過(guò)handler模塊計(jì)數(shù),因?yàn)樗械臄?shù)據(jù)都會(huì)經(jīng)過(guò)Nginx,可以統(tǒng)計(jì)后直接返回給客戶端。
流程:在conf文件中讀取到關(guān)鍵詞"count",執(zhí)行對(duì)應(yīng)的set函數(shù),每執(zhí)行一次,都會(huì)設(shè)置一個(gè)handler函數(shù),還會(huì)創(chuàng)建一個(gè)共享內(nèi)存區(qū),并且指定對(duì)應(yīng)的回調(diào)init函數(shù)?;卣{(diào)init函數(shù)會(huì)在Nginx啟動(dòng)和reload的時(shí)候執(zhí)行。handler函數(shù)的執(zhí)行時(shí)機(jī)是當(dāng)客戶端請(qǐng)求發(fā)送來(lái)的時(shí)候,每來(lái)一個(gè)請(qǐng)求,都會(huì)執(zhí)行handler函數(shù)。在handler函數(shù)里面,就能拿到請(qǐng)求方的ip地址,根據(jù)ip的最后一位1-255,search查找ngx_rbtree是否有相同節(jié)點(diǎn),決定是否插入。通過(guò)encode_page函數(shù)組織html網(wǎng)頁(yè),遍歷ngx_rbtree獲取總訪問(wèn)次數(shù),將內(nèi)容都寫(xiě)入。最后組織一個(gè)header和body,返回給客戶端。
亮點(diǎn):采用ngx_rbtree增強(qiáng)健壯性,增加slab共享內(nèi)存,解決多進(jìn)程間通信的問(wèn)題。所有 worker 進(jìn)程都能訪問(wèn)同一份計(jì)數(shù)數(shù)據(jù),通過(guò)slab自帶的鎖依次通過(guò)lookup函數(shù)遍歷紅黑樹(shù)
代碼
#include <ngx_http.h>
#include <ngx_config.h>
#include <ngx_core.h>
/*
#include <arpa/inet.h>
#include <netinet/in.h>
*/
#define ENABLE_RBTREE 1
#讀到關(guān)鍵字的時(shí)候調(diào)用 用于指定handler函數(shù) 和 創(chuàng)建共享內(nèi)存區(qū)并指定其初始化回調(diào)函數(shù)
static char *ngx_http_pagecount_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
#有請(qǐng)求的時(shí)候調(diào)用,加鎖統(tǒng)計(jì)當(dāng)前客戶端 IP 的訪問(wèn)次數(shù),并生成統(tǒng)計(jì)頁(yè)面返回給用戶
static ngx_int_t ngx_http_pagecount_handler(ngx_http_request_t *r);
#讀到location模塊調(diào)用,為每個(gè) location 創(chuàng)建并初始化配置結(jié)構(gòu)體
static void *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf);
#開(kāi)啟/reload Nginx時(shí)候調(diào)用,初始化共享內(nèi)存區(qū)的回調(diào)函數(shù)
static ngx_int_t ngx_http_pagecount_shm_init (ngx_shm_zone_t *zone, void *data);
#在handler中的lookup函數(shù)中調(diào)用,自定義紅黑樹(shù)的插入規(guī)則
static void ngx_http_pagecount_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
#在handler中調(diào)用,遍歷紅黑樹(shù)統(tǒng)計(jì)數(shù)量
static int ngx_encode_http_page_rb(ngx_http_pagecount_conf_t *conf, char *html);
#在handler中調(diào)用,對(duì)于一個(gè)新key(ip)的處理
static ngx_int_t ngx_http_pagecount_lookup(ngx_http_request_t *r, ngx_http_pagecount_conf_t *conf, ngx_uint_t key);
static ngx_command_t count_commands[] = {
{//告訴Nginx conf中的count是關(guān)鍵字,并且指定其處理函數(shù)set
ngx_string("count"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, //指令可以出現(xiàn)的位置
ngx_http_pagecount_set, //讀到conf中的count指令時(shí)調(diào)用
NGX_HTTP_LOC_CONF_OFFSET,
0, NULL
},
ngx_null_command
};
static ngx_http_module_t count_ctx = {
NULL,
NULL,
//沒(méi)用上了 因?yàn)閔andler和共享內(nèi)存池(包括紅黑樹(shù))都在set函數(shù)里創(chuàng)建并且綁定回調(diào)了,Nginx啟動(dòng)的時(shí)候會(huì)自動(dòng)調(diào)用回調(diào)
NULL,
NULL,
NULL,
NULL,
ngx_http_pagecount_create_location_conf, //讀到location配置時(shí)調(diào)用,確保每個(gè) location 都有獨(dú)立的配置空間
NULL,
};
//ngx_http_count_module 最先編寫(xiě)的內(nèi)容
ngx_module_t ngx_http_pagecount_module = {
NGX_MODULE_V1,
&count_ctx,
count_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
typedef struct {
int count;
} ngx_http_pagecount_node_t;
typedef struct { //共享內(nèi)存中的 “數(shù)據(jù)區(qū)”紅黑樹(shù)
ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
} ngx_http_pagecount_shm_t;
typedef struct //location 級(jí)別的配置結(jié)構(gòu)體
{
ssize_t shmsize;
ngx_slab_pool_t *shpool;
ngx_http_pagecount_shm_t *sh;
} ngx_http_pagecount_conf_t;
ngx_int_t ngx_http_pagecount_shm_init (ngx_shm_zone_t *zone, void *data) {
//zone:Nginx 共享內(nèi)存區(qū)對(duì)象,包含分配好的內(nèi)存地址等信息。
//data:上一次初始化時(shí)的配置數(shù)據(jù)
ngx_http_pagecount_conf_t *conf;
ngx_http_pagecount_conf_t *oconf = data;
conf = (ngx_http_pagecount_conf_t*)zone->data;
if (oconf) {//已經(jīng)初始化過(guò)了 復(fù)用紅黑樹(shù)和 slab 內(nèi)存池指針
conf->sh = oconf->sh;
conf->shpool = oconf->shpool;
return NGX_OK;
}
//沒(méi)初始化過(guò) 新建紅黑樹(shù)和 slab 內(nèi)存池
conf->shpool = (ngx_slab_pool_t*)zone->shm.addr;
conf->sh = ngx_slab_alloc(conf->shpool, sizeof(ngx_http_pagecount_shm_t));
if (conf->sh == NULL) {
return NGX_ERROR;
}
//綁定紅黑樹(shù) 到Nginx內(nèi)置new slab 內(nèi)存池
conf->shpool->data = conf->sh;
//初始化紅黑樹(shù)
ngx_rbtree_init(&conf->sh->rbtree, &conf->sh->sentinel,
ngx_http_pagecount_rbtree_insert_value);
return NGX_OK;
}
static char *ngx_http_pagecount_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_shm_zone_t *shm_zone;
ngx_str_t name = ngx_string("pagecount_slab_shm");
ngx_http_pagecount_conf_t *mconf = (ngx_http_pagecount_conf_t*)conf;
//創(chuàng)建Nginx內(nèi)置的配置結(jié)構(gòu)體corecf
ngx_http_core_loc_conf_t *corecf;
mconf->shmsize = 1024*1024;
//創(chuàng)建一個(gè)名為 pagecount_slab_shm 的共享內(nèi)存區(qū)(通過(guò)ngx內(nèi)置的共享內(nèi)存管理函數(shù) )
shm_zone = ngx_shared_memory_add(cf, &name, mconf->shmsize, &ngx_http_pagecount_module);
if (NULL == shm_zone) {
return NGX_CONF_ERROR;
}
//把創(chuàng)建的共享內(nèi)存區(qū)初始化
shm_zone->init = ngx_http_pagecount_shm_init;
shm_zone->data = mconf;
//獲取Nginx內(nèi)置的配置空間,在里面設(shè)置handler,讓Nginx知道遇到HTTP請(qǐng)求時(shí)調(diào)用我們寫(xiě)的處理函數(shù)
corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
corecf->handler = ngx_http_pagecount_handler;
return NGX_CONF_OK;
}
void *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf) {
//為每個(gè) location 創(chuàng)建并初始化配置結(jié)構(gòu)體
ngx_http_pagecount_conf_t *conf;
conf = ngx_palloc(cf->pool, sizeof(ngx_http_pagecount_conf_t));
if (NULL == conf) {
return NULL;
}
conf->shmsize = 0;
return conf;
}
static void
ngx_http_pagecount_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
//自定義紅黑樹(shù)的插入規(guī)則,據(jù)節(jié)點(diǎn)的 key(IP 地址的數(shù)值)決定插入到樹(shù)的哪個(gè)位
ngx_rbtree_node_t **p;
for (;;)
{
if (node->key < temp->key)
{
p = &temp->left;
}
else if (node->key > temp->key) {
p = &temp->right;
}
else
{
return ;
}
if (*p == sentinel)
{
break;
}
temp = *p;
}
*p = node;
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}
static ngx_int_t ngx_http_pagecount_lookup(ngx_http_request_t *r, ngx_http_pagecount_conf_t *conf, ngx_uint_t key) {
//在紅黑樹(shù)中查找指定 key(IP),如果找到則計(jì)數(shù)加一;如果沒(méi)找到則插入新節(jié)點(diǎn)并初始化計(jì)數(shù)為 1
ngx_rbtree_node_t *node, *sentinel;
node = conf->sh->rbtree.root;
sentinel = conf->sh->rbtree.sentinel;
ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_lookup 111 --> %x\n", key);
while (node != sentinel) {
if (key < node->key) {
node = node->left;
continue;
} else if (key > node->key) {
node = node->right;
continue;
} else { // key == node
node->data ++;
return NGX_OK;
}
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_lookup 222 --> %x\n", key);
// insert rbtree
node = ngx_slab_alloc_locked(conf->shpool, sizeof(ngx_rbtree_node_t));
if (NULL == node) {
return NGX_ERROR;
}
node->key = key;
node->data = 1;
ngx_rbtree_insert(&conf->sh->rbtree, node);
ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " insert success\n");
return NGX_OK;
}
static int ngx_encode_http_page_rb(ngx_http_pagecount_conf_t *conf, char *html) {
//遍歷紅黑樹(shù),將所有 IP 及其訪問(wèn)次數(shù)以 HTML 格式輸出到字符串 html,用于頁(yè)面展示
sprintf(html, "<h1>67777 </h1>");
strcat(html, "<h2>");
ngx_rbtree_node_t *node = ngx_rbtree_min(conf->sh->rbtree.root, conf->sh->rbtree.sentinel);
do {//遍歷紅黑樹(shù)節(jié)點(diǎn)
char str[INET_ADDRSTRLEN] = {0};
char buffer[128] = {0};
sprintf(buffer, "req from : %s, count: %d
",
inet_ntop(AF_INET, &node->key, str, sizeof(str)), node->data);
strcat(html, buffer);
node = ngx_rbtree_next(&conf->sh->rbtree, node);
} while (node);
strcat(html, "</h2>");
return NGX_OK;
}
static ngx_int_t ngx_http_pagecount_handler(ngx_http_request_t *r) {
// HTTP 請(qǐng)求的處理函數(shù),統(tǒng)計(jì)當(dāng)前客戶端 IP 的訪問(wèn)次數(shù),并生成統(tǒng)計(jì)頁(yè)面返回給用戶
u_char html[1024] = {0};
int len = sizeof(html);
ngx_rbtree_key_t key = 0;
struct sockaddr_in *client_addr = (struct sockaddr_in*)r->connection->sockaddr;
ngx_http_pagecount_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_pagecount_module);
key = (ngx_rbtree_key_t)client_addr->sin_addr.s_addr;
ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_handler --> %x\n", key);
//先對(duì)共享內(nèi)存池加鎖,保證多進(jìn)程/多線程安全(共享內(nèi)存池自帶的鎖)
ngx_shmtx_lock(&conf->shpool->mutex);
ngx_http_pagecount_lookup(r, conf, key);
ngx_shmtx_unlock(&conf->shpool->mutex);
ngx_encode_http_page_rb(conf, (char*)html);
//header
r->headers_out.status = 200;
ngx_str_set(&r->headers_out.content_type, "text/html");
ngx_http_send_header(r);
//body
ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
ngx_chain_t out;
out.buf = b;
out.next = NULL;
b->pos = html;
b->last = html+len;
b->memory = 1;
b->last_buf = 1;
return ngx_http_output_filter(r, &out);
}upstream模塊
ginx 的 upstream 模塊用于實(shí)現(xiàn)反向代理和負(fù)載均衡??梢酝ㄟ^(guò)配置 upstream 塊,將請(qǐng)求分發(fā)到多個(gè)后端服務(wù)器。對(duì)于upstream模塊,直接用 Nginx conf配置實(shí)現(xiàn) 自己的upstream,比如:
http {
//定義了一個(gè)后端服務(wù)器組,包含兩個(gè)服務(wù)器
upstream backend {
server 192.168.159.130:9002 weight=2;//可以是任意的服務(wù)器地址 不一定是本機(jī)的
server 192.168.159.130:9003 weight=2;
}
server {
listen 9000;
location / {//路由規(guī)則
#root /home/king/share/nginx/html9000/;
proxy_pass http://backend; # ...轉(zhuǎn)發(fā)給 backend 服務(wù)器池處理
}
}到此這篇關(guān)于Nginx中間件的文章就介紹到這了,更多相關(guān)Nginx中間件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
nginx server_name配置多個(gè)域名時(shí)的坑
Nginx配置多個(gè)server_name時(shí),$server_name默認(rèn)取第一個(gè)值,導(dǎo)致PHP獲取錯(cuò)誤,下面就來(lái)介紹一下該問(wèn)題的解決,感興趣的可以了解一下2025-05-05
Nginx使用Gzip算法對(duì)報(bào)文進(jìn)行壓縮詳解
這篇文章主要給大家介紹了關(guān)于Nginx的Gzip功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Nginx具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Nginx服務(wù)器高性能優(yōu)化的配置方法小結(jié)
這篇文章主要介紹了Nginx服務(wù)器高性能優(yōu)化的配置方法小結(jié),包括一些內(nèi)核參數(shù)的優(yōu)化介紹,需要的朋友可以參考下2015-12-12
Nginx日志統(tǒng)計(jì)分析的常用命令總結(jié)
這篇文章主要給大家總結(jié)了關(guān)于Nginx日志統(tǒng)計(jì)分析的一些常用命令,其中包括IP相關(guān)統(tǒng)計(jì)、頁(yè)面訪問(wèn)統(tǒng)計(jì)、性能分析、蜘蛛抓取統(tǒng)計(jì)、TCP連接統(tǒng)計(jì)等相關(guān)命令的總結(jié),相信對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-07-07
Nginx 訪問(wèn) /root/下 403 Forbidden問(wèn)題解決
在使用Nginx作為Web服務(wù)器時(shí),可能會(huì)遇到403 Forbidden錯(cuò)誤,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-05-05
Nginx下讓W(xué)ordPress支持固定鏈接的偽靜態(tài)規(guī)則
Nginx下讓W(xué)ordPress支持固定鏈接的偽靜態(tài)規(guī)則,要讓nginx支持wordpress固定鏈接非常簡(jiǎn)單,需要自己進(jìn)行添加點(diǎn)配置代碼2013-02-02
kubernetes啟用PHP+Nginx網(wǎng)頁(yè)環(huán)境教程
這篇文章主要介紹了kubernetes啟用PHP+Nginx網(wǎng)頁(yè)環(huán)境教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

