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

Nginx驚群
Nginx的驚群現(xiàn)象及應對方法是什么?
Nginx 的“驚群現(xiàn)象”是指:
在多進程(worker)模式下,多個進程同時等待某個事件(如新連接),當事件發(fā)生時,所有進程都被喚醒,但只有一個能成功 accept,其他進程再次休眠,造成資源浪費和性能下降。
應對方法:
在 worker 進程間引入 accept_mutex(接受互斥鎖)。只有獲得鎖的進程才會去 accept 新連接,其他進程等待,避免所有進程一起被喚醒。通過配置 conf中events模塊的accept_mutex on實現(xiàn)
二、Nginx組件拆出來使用
Nginx內(nèi)部實現(xiàn)了很多組件,在鏈接對應頭文件的基礎上,可以直接編寫代碼,調(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模塊開發(fā)
過濾器模塊
接收到后端的response,返回response給瀏覽器客戶端
任務:開發(fā)一個在 HTTP 響應的 HTML 內(nèi)容前插入一段固定的文本?(如作者信息和鏈接)的Nginx HTTP Filter 模塊,ngx_http_prefix_filter_module.c。
流程:在讀取到conf的"add_prefix"后,會執(zhí)行對應的set函數(shù),將對應的值保存到讀取到location模塊初始化創(chuàng)建的配置結(jié)構(gòu)體里面。當conf文件讀取結(jié)束,會觸發(fā)prefix過濾器初始化,將我們的prefix_filter加入到Nginx的Filter鏈表里面。當后端傳來了HTTP響應,就會觸發(fā)我們的header_top和body_top過濾器函數(shù),在header函數(shù)中里面檢查和標記請求,在body函數(shù)中加入對應的文本,然后傳入下一個過濾器。
#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; #標記是否需要插入前綴(0=不插入,1=需要插入,2=已插入)
} ngx_http_prefix_filter_ctx_t;
三個核心處理函數(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)建一個獨立的配置結(jié)構(gòu)體,并初始化默認值。
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ī)則:如果子配置未設置,則繼承父配置的值
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, #遇到命令的時候執(zhí)行這個set函數(shù)
NGX_HTTP_LOC_CONF_OFFSET, #讀取到conf中add_prefix值后,存儲的位置(location 級別)
offsetof(ngx_http_prefix_filter_conf_t, enable), #具體存儲在結(jié)構(gòu)體的哪個位置
NULL
},
ngx_null_command //結(jié)尾標識
};
static ngx_http_module_t ngx_http_prefix_filter_module_ctx = {
NULL,
ngx_http_prefix_filter_init, #讀完最后一行conf時執(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ù) 會創(chuàng)建一個獨立的配置結(jié)構(gòu)體,并初始化默認值
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, #模塊的類型
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 響應時,會依次調(diào)用兩類 Filter:?Header Filter?:處理響應頭;?Body Filter?:處理響應體(如修改 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) {
#檢查并標記請求
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);
}
// 檢查是否已處理過(防止重復處理)
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);
}
// 為需要加入的前綴 分配空間,隨后把這個指針和當前的HTTP請求綁定
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);
// 僅對 text/html 響應插入前綴
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;// 標記需要插入前綴
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);
}
#標記已處理
ctx->add_prefix = 2;
# 創(chuàng)建包含前綴內(nèi)容的緩沖區(qū),存放預設的前綴文本(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ū)插入到響應體鏈表的頭部
ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);
cl->buf = b;
cl->next = in;
return ngx_http_next_body_filter(r, cl);
}編寫ngx_http_prefix_filter_module.c后,還需要在同一目錄下編寫config文件,讓Nginx識別到這個模塊。
#模塊的名字 ngx_addon_name=ngx_http_prefix_filter_module #Makefile中的關鍵字 在其后面加上我們的庫 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模塊
接收瀏覽器客戶端請求,直接返回請求給瀏覽器客戶端
任務:統(tǒng)計頁面訪問次數(shù),可以直接在Nginx上通過handler模塊計數(shù),因為所有的數(shù)據(jù)都會經(jīng)過Nginx,可以統(tǒng)計后直接返回給客戶端。
流程:在conf文件中讀取到關鍵詞"count",執(zhí)行對應的set函數(shù),每執(zhí)行一次,都會設置一個handler函數(shù),還會創(chuàng)建一個共享內(nèi)存區(qū),并且指定對應的回調(diào)init函數(shù)?;卣{(diào)init函數(shù)會在Nginx啟動和reload的時候執(zhí)行。handler函數(shù)的執(zhí)行時機是當客戶端請求發(fā)送來的時候,每來一個請求,都會執(zhí)行handler函數(shù)。在handler函數(shù)里面,就能拿到請求方的ip地址,根據(jù)ip的最后一位1-255,search查找ngx_rbtree是否有相同節(jié)點,決定是否插入。通過encode_page函數(shù)組織html網(wǎng)頁,遍歷ngx_rbtree獲取總訪問次數(shù),將內(nèi)容都寫入。最后組織一個header和body,返回給客戶端。
亮點:采用ngx_rbtree增強健壯性,增加slab共享內(nèi)存,解決多進程間通信的問題。所有 worker 進程都能訪問同一份計數(shù)數(shù)據(jù),通過slab自帶的鎖依次通過lookup函數(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
#讀到關鍵字的時候調(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);
#有請求的時候調(diào)用,加鎖統(tǒng)計當前客戶端 IP 的訪問次數(shù),并生成統(tǒng)計頁面返回給用戶
static ngx_int_t ngx_http_pagecount_handler(ngx_http_request_t *r);
#讀到location模塊調(diào)用,為每個 location 創(chuàng)建并初始化配置結(jié)構(gòu)體
static void *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf);
#開啟/reload Nginx時候調(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)用,自定義紅黑樹的插入規(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)用,遍歷紅黑樹統(tǒng)計數(shù)量
static int ngx_encode_http_page_rb(ngx_http_pagecount_conf_t *conf, char *html);
#在handler中調(diào)用,對于一個新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是關鍵字,并且指定其處理函數(shù)set
ngx_string("count"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, //指令可以出現(xiàn)的位置
ngx_http_pagecount_set, //讀到conf中的count指令時調(diào)用
NGX_HTTP_LOC_CONF_OFFSET,
0, NULL
},
ngx_null_command
};
static ngx_http_module_t count_ctx = {
NULL,
NULL,
//沒用上了 因為handler和共享內(nèi)存池(包括紅黑樹)都在set函數(shù)里創(chuàng)建并且綁定回調(diào)了,Nginx啟動的時候會自動調(diào)用回調(diào)
NULL,
NULL,
NULL,
NULL,
ngx_http_pagecount_create_location_conf, //讀到location配置時調(diào)用,確保每個 location 都有獨立的配置空間
NULL,
};
//ngx_http_count_module 最先編寫的內(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ū)”紅黑樹
ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
} ngx_http_pagecount_shm_t;
typedef struct //location 級別的配置結(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ū)對象,包含分配好的內(nèi)存地址等信息。
//data:上一次初始化時的配置數(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)初始化過了 復用紅黑樹和 slab 內(nèi)存池指針
conf->sh = oconf->sh;
conf->shpool = oconf->shpool;
return NGX_OK;
}
//沒初始化過 新建紅黑樹和 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;
}
//綁定紅黑樹 到Nginx內(nèi)置new slab 內(nèi)存池
conf->shpool->data = conf->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)建一個名為 pagecount_slab_shm 的共享內(nèi)存區(qū)(通過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)置的配置空間,在里面設置handler,讓Nginx知道遇到HTTP請求時調(diào)用我們寫的處理函數(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) {
//為每個 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)
{
//自定義紅黑樹的插入規(guī)則,據(jù)節(jié)點的 key(IP 地址的數(shù)值)決定插入到樹的哪個位
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) {
//在紅黑樹中查找指定 key(IP),如果找到則計數(shù)加一;如果沒找到則插入新節(jié)點并初始化計數(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) {
//遍歷紅黑樹,將所有 IP 及其訪問次數(shù)以 HTML 格式輸出到字符串 html,用于頁面展示
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 {//遍歷紅黑樹節(jié)點
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 請求的處理函數(shù),統(tǒng)計當前客戶端 IP 的訪問次數(shù),并生成統(tǒng)計頁面返回給用戶
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);
//先對共享內(nèi)存池加鎖,保證多進程/多線程安全(共享內(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 模塊用于實現(xiàn)反向代理和負載均衡??梢酝ㄟ^配置 upstream 塊,將請求分發(fā)到多個后端服務器。對于upstream模塊,直接用 Nginx conf配置實現(xiàn) 自己的upstream,比如:
http {
//定義了一個后端服務器組,包含兩個服務器
upstream backend {
server 192.168.159.130:9002 weight=2;//可以是任意的服務器地址 不一定是本機的
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 服務器池處理
}
}到此這篇關于Nginx中間件的文章就介紹到這了,更多相關Nginx中間件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Nginx進行平滑升級的實戰(zhàn)指南(不中斷服務版本更新)
Nginx的平滑升級(也稱為熱升級)是一種在不停止服務的情況下更新Nginx版本或添加模塊的方法,這種升級方式確保了服務的高可用性,避免了因升級導致的請求中斷問題,以下是實現(xiàn)Nginx平滑升級的具體步驟,需要的朋友可以參考下2025-08-08
在nginx中實現(xiàn)單位時間內(nèi)限制訪問頻率的教程
這篇文章主要介紹了在nginx中實現(xiàn)單位時間內(nèi)限制訪問頻率的教程,并非針對IP而是全局的訪問量限制,需要的朋友可以參考下2015-04-04
Windows Server環(huán)境下使用Nginx部署Flask應用的全過程
Flask是一個輕量級的Python Web框架,它允許開發(fā)者快速搭建Web應用或服務,Nginx是一款高性能的HTTP和反向代理服務器,用于部署靜態(tài)文件服務、負載均衡以及作為HTTP緩存等,本文給大家介紹了Windows Server環(huán)境下使用Nginx部署Flask應用的全過程,需要的朋友可以參考下2025-04-04
Nginx下ModSecurity模塊安裝的實現(xiàn)步驟
本文提供了在Nginx 1.28.0上安裝ModSecurity v3的完整教程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-09-09
Nginx listen 監(jiān)聽端口的實現(xiàn)配置
本文將介紹Nginx的listen指令及其在配置文件中的應用,通過了解listen指令,我們可以知道Nginx如何監(jiān)聽端口,并配置相應的服務器塊來處理進入的請求2023-12-12

