Nginx模塊開發之http handler實現流量統計(2)

文章目錄

  • 一、概述
  • 二、Nginx handler模塊開發
    • 2.1、代碼實現
    • 2.2、編寫config文件
    • 2.3、編譯模塊到Nginx源碼中
    • 2.4、修改conf文件
    • 2.5、執行效果
  • 總結

一、概述

上一篇【Nginx模塊開發之http handler實現流量統計(1)】使用數組在單進程實現了IP的流量統計,這一篇將進行優化,使用紅黑樹的數據結構以及共享內存的方式實現進程間通信。

進程間通信的方式:
(1)進程在不同的機器中,使用網絡進行通信。
(2)進程在同一個機器,并且進程間的關系是父子進程關系,可以使用共享內存。
(3)使用unix_sock,比如文件soket。在MySQL使用的就是這種進程通信方式。
(4)pipe,管道。

二、Nginx handler模塊開發

2.1、代碼實現

在重點地方添加了注釋,主要是紅黑樹的添加和使用,以及共享內存的使用。

核心:
1.nginx獲取請求。ngx_command_t中設置ngx_http_pagecount_set。
2.conf文件解析到模塊的cmd時,初始化共享內存以及互斥鎖。
3.紅黑樹的初始化。
4.組織網頁時,需要遍歷紅黑樹找到IP并做流量統計。


#include <ngx_http.h>
#include <ngx_config.h>
#include <ngx_core.h>#define ENABLE_RBTREE	1static char *ngx_http_pagecount_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_pagecount_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_pagecount_init(ngx_conf_t *cf);
static void  *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf);
static ngx_int_t ngx_http_pagecount_shm_init (ngx_shm_zone_t *zone, void *data);
static void ngx_http_pagecount_rbtree_insert_value(ngx_rbtree_node_t *temp,ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);// 命令解析
static ngx_command_t count_commands[] = {{ngx_string("count"),NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,ngx_http_pagecount_set,//遇到模塊命令時調用NGX_HTTP_LOC_CONF_OFFSET,0, NULL},ngx_null_command
};static ngx_http_module_t count_ctx = {NULL,ngx_http_pagecount_init,//初始化NULL,NULL,NULL,NULL,// conf文件解析到location時調用ngx_http_pagecount_create_location_conf,NULL,
};//ngx_http_count_module 
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; //count
} ngx_http_pagecount_node_t;typedef struct {ngx_rbtree_t rbtree;ngx_rbtree_node_t sentinel;} ngx_http_pagecount_shm_t;// 共享內存結構體
typedef struct
{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) {ngx_http_pagecount_conf_t *conf;ngx_http_pagecount_conf_t *oconf = data;conf = (ngx_http_pagecount_conf_t*)zone->data;if (oconf) {conf->sh = oconf->sh;conf->shpool = oconf->shpool;return NGX_OK;}//printf("ngx_http_pagecount_shm_init 0000\n");// 初始化鎖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;}conf->shpool->data = conf->sh;//printf("ngx_http_pagecount_shm_init 1111\n");// 共享內存創建完之后初始化紅黑樹,// 要提供插入函數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;ngx_http_core_loc_conf_t *corecf;//ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_pagecount_set000");// 設置共享內存的大小mconf->shmsize = 1024*1024;shm_zone = ngx_shared_memory_add(cf, &name, mconf->shmsize, &ngx_http_pagecount_module);if (NULL == shm_zone) {return NGX_CONF_ERROR;}shm_zone->init = ngx_http_pagecount_shm_init;shm_zone->data = mconf;corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);// 計數統計主函數corecf->handler = ngx_http_pagecount_handler;return NGX_CONF_OK;
}ngx_int_t   ngx_http_pagecount_init(ngx_conf_t *cf) {return NGX_OK;
}// conf文件解析到location時進入此函數。
// 此函數為模塊創建所需要的結構體, 后面會使用。
void  *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf) {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;//ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_pagecount_create_location_conf");// init conf data// ... 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)
{ngx_rbtree_node_t **p;//ngx_http_testslab_node_t *lrn, *lrnt;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);
}// 查找IP和訪問計數統計
static ngx_int_t ngx_http_pagecount_lookup(ngx_http_request_t *r, ngx_http_pagecount_conf_t *conf, ngx_uint_t key) {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 == nodenode->data ++;return NGX_OK;}}ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_lookup 222 --> %x\n", key);// insert rbtreenode = 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) {sprintf(html, "<h1>Source Insight </h1>");strcat(html, "<h2>");//ngx_rbtree_traversal(&ngx_pv_tree, ngx_pv_tree.root, ngx_http_count_rbtree_iterator, html);ngx_rbtree_node_t *node = ngx_rbtree_min(conf->sh->rbtree.root, conf->sh->rbtree.sentinel);do {char str[INET_ADDRSTRLEN] = {0};char buffer[128] = {0};sprintf(buffer, "req from : %s, count: %d <br/>",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) {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);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);//headerr->headers_out.status = 200;ngx_str_set(&r->headers_out.content_type, "text/html");ngx_http_send_header(r);//bodyngx_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);}

2.2、編寫config文件

創建:

touch config

內容:

ngx_addon_name=ngx_http_pagecount_module
HTTP_MODULES="$HTTP_MODULES ngx_http_pagecount_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_pagecount_module.c"

注意,config文件要和模塊的代碼在相同目錄。

2.3、編譯模塊到Nginx源碼中

(1)配置中添加模塊:

./configure --prefix=/usr/local/nginx --with-http_realip_module --with-http_addition_module 
--with-http_gzip_static_module --with-http_secure_link_module --with-http_stub_status_module 
--with-stream --with-pcre=/home/fly/workspace/pcre-8.41 --with-zlib=/home/fly/workspace/zlib-1.2.11 
--with-openssl=/home/fly/workspace/openssl-1.1.0g 
--add-module=/mnt/hgfs/sourcecode_learning/nginx-module/ngx_http_pagecount_module

注意模塊路徑要正確。出現如下表示成功:

configuring additional modules
adding module in /mnt/hgfs/sourcecode_learning/nginx-module/ngx_http_pagecount_module+ ngx_http_pagecount_module was configured
creating objs/Makefile

(2)編譯安裝:

make && sudo make install

2.4、修改conf文件

編譯安裝完成后,conf文件添加count;


worker_processes 4;events {worker_connections 1024;
}http {upstream backend {server 192.168.7.146:8889;server 192.168.7.146:8890;}server {listen 8888;location / {proxy_pass http://backend;}}server {listen 8889;location / {count;}}server {listen 8890;}server {listen 8891;}}

2.5、執行效果

關閉已啟動的nginx:

sudo /usr/local/nginx/sbin/nginx -s stop

啟動nginx:

sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/fly.conf 

在網頁輸入IP和端口,執行效果如下:
在這里插入圖片描述

總結

  1. 實現一個基于Nginx的IP統計或訪問流量統計,可以借鑒以上代碼;只要做一些業務上的修改就可以直接使用。可能需改動的地方就是紅黑樹的key、value的數據結構,以及ngx_encode_http_page_rb函數的業務代碼,其他可以基本不用改動就可以二次開發。
  2. Nginx需要熟悉的數據結構:內存池、queue、list、array、shmem等。同時需要清楚Nginx的11個狀態。
  3. 在實際應用中,需要掌握Nginx的conf文件配置(https的配置、負載均衡的配置、反向代理、CPU親緣性配置等)以及模塊開發(filter、handler等)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/167907.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167907.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167907.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

音視頻項目—基于FFmpeg和SDL的音視頻播放器解析(二十二)

介紹 在本系列&#xff0c;我打算花大篇幅講解我的 gitee 項目音視頻播放器&#xff0c;在這個項目&#xff0c;您可以學到音視頻解封裝&#xff0c;解碼&#xff0c;SDL渲染相關的知識。您對源代碼感興趣的話&#xff0c;請查看基于FFmpeg和SDL的音視頻播放器 如果您不理解本…

MySQL 排序和分組怎么做?

文章目錄 前言MySQL 排序語法在命令提示符中使用 ORDER BY 子句在PHP腳本中使用 ORDER BY 子句 MySQL 分組GROUP BY 語法實例演示使用 WITH ROLLUP 后言 前言 hello world歡迎來到前端的新世界 &#x1f61c;當前文章系列專欄&#xff1a;Mysql &#x1f431;?&#x1f453;博…

Cookie的基本使用

JavaScript Cookie&#xff08;JavaScript 的 Cookie&#xff09;是一種在Web瀏覽器中存儲和檢索用戶信息的機制。它允許網站在用戶計算機上存儲小型數據片段&#xff0c;以便在之后的會話中使用這些數據。Cookie通常用于持久化用戶首選項、跟蹤用戶行為和提供個性化體驗。 通…

堆的實現(堆的插入、堆的刪除等)超級全

堆的實現&#xff08;堆的插入、堆的刪除等&#xff09;超級全 文章目錄 堆的實現&#xff08;堆的插入、堆的刪除等&#xff09;超級全一、前期基礎知識1.樹結構①樹的定義②樹的相關概念③二叉樹④滿二叉樹和完全二叉樹a.滿二叉樹b.完全二叉樹 ⑤二叉樹的性質⑥二叉樹順序結構…

每日OJ題_算法_雙指針_力扣11. 盛最多水的容器

力扣11. 盛最多水的容器 11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 難度 中等 給定一個長度為 n 的整數數組 height 。有 n 條垂線&#xff0c;第 i 條線的兩個端點是 (i, 0) 和 (i, height[i]) 。 找出其中的兩條線&#xff0c;使得它們與 x 軸共同構成…

2023 最新 PDF.js 在 Vue3 中的使用

因為自己寫業務要定制各種 pdf 預覽情況&#xff08;可能&#xff09;&#xff0c;所以采用了 pdf.js 而不是各種第三方封裝庫&#xff0c;主要還是為了更好的自由度。 一、PDF.js 介紹 官方地址 中文文檔 PDF.js 是一個使用 HTML5 構建的便攜式文檔格式查看器。 pdf.js 是社區…

人工智能教程(二):人工智能的歷史以及再探矩陣

目錄 前言 更多矩陣的知識 Pandas 矩陣的秩 前言 在上一章中&#xff0c;我們討論了人工智能、機器學習、深度學習、數據科學等領域的關聯和區別。我們還就整個系列將使用的編程語言、工具等做出了一些艱難的選擇。最后&#xff0c;我們還介紹了一點矩陣的知識。在本文中&am…

vue打包上傳服務器的總結筆記

經歷了3個小時教訓&#xff0c;還是自己總結一下吧&#xff0c;致未來傻x的自己。 第一步&#xff0c;打開b站。搜索【vue打包】找到一個視頻教程 前端Vue項目打包部署實戰教程_嗶哩嗶哩_bilibili 這步是在看不懂下面操作的情況下用的 第一步&#xff1a;找到nginx的配置文件…

需求變更導致估算不精準 6大措施

需求變更可能導致估算不精準、項目成本增加、進度延遲等問題&#xff0c;如果不能準確地估算項目&#xff0c;往往會造成資源浪費和開發效率的降低&#xff0c;因此亟需解決因需求變更導致地估算不精準的問題。 一般來說&#xff0c;主要是從以下6個方面入手解決&#xff1a; 1…

【maven】【IDEA】idea中使用maven編譯項目,報錯java: 錯誤: 找不到符號 【2】

idea中使用maven編譯項目,報錯java: 錯誤: 找不到符號 錯誤狀況展示: 如果報這種錯,是因為項目中真的找不到報錯的方法或者枚舉 字段之類的,但實際是 : 點擊 File Path

OSG粒子系統與陰影-霧效模擬(1)

虛擬現實中有很多效果&#xff0c;如雨效、雪效、霧效等&#xff0c;這些都可以通過粒子系統來實現。一個真實的粒子系統的模式能使三維場景達到更好的效果。 本章對OSG粒子系統的使用以及生成自定義粒子系統的方法進行了詳細介紹最后還附帶說明了陰影的使用方法。在實時的場景…

(HAL庫版)freeRTOS移植STMF103

正點原子關于freeRTOS的教程是比較好的&#xff0c;可惜移植的是標準庫&#xff0c;但是我學的是Hal庫&#xff0c;因為開發速度更快&#xff0c;從最后那個修改SYSTEM文件夾的地方開始替換為下面的內容就可以了 5.修改Systick中斷、SVC中斷、PendSV中斷 將SVC中斷、P…

pairplot

Python可視化 | Seaborn5分鐘入門(七)——pairplot - 知乎 (zhihu.com) Seaborn是基于matplotlib的Python可視化庫。它提供了一個高級界面來繪制有吸引力的統計圖形。Seaborn其實是在matplotlib的基礎上進行了更高級的API封裝&#xff0c;從而使得作圖更加容易&#xff0c;不需…

紅黑樹詳解

紅黑樹的概念與性質 前置知識 在學習紅黑樹之前&#xff0c;最好有二叉查找樹和AVL樹的基礎&#xff0c;因為紅黑樹本質就是一種特殊的二叉查找樹&#xff0c;而紅黑樹的操作中需要用到AVL樹中旋轉的相關知識。至于二叉查找樹和AVL樹&#xff0c;可以參考如下兩篇博客&#xf…

oracle安裝的肘腋之疾小合集

#臨時空間指定 export TMP/tmp export TMPDIR/tmp #圖形化顯示框不全 java問題&#xff0c;使用系統自帶的jre ./runInstaller -jreLoc/usr/local/jdk1.7.0_80/ #ins30131 Failed to access the temporary location 給/tmp/CVU*加x權限 #linux桌面太小 xrandr -s 1440x900_60…

Matplotlib圖形注釋_Python數據分析與可視化

Matplotlib圖形注釋 添加注釋文字、坐標變換 有的時候單單使用圖形無法完整清晰的表達我們的信息&#xff0c;我們還需要進行文字進行注釋&#xff0c;所以matplotlib提供了文字、箭頭等注釋可以突出圖形中重點信息。 添加注釋 為了使我們的可視化圖形讓人更加容易理解&#…

vue中父組件直接調用子組件方法

vue2 中&#xff0c;父組件如何調用子組件的方法 在Vue 2中&#xff0c;父組件可以通過使用ref屬性來引用子組件的實例&#xff0c;然后通過該實例調用子組件的方法。 首先&#xff0c;在父組件的模板中&#xff0c;給子組件添加一個ref屬性&#xff1a; <template>&l…

在工業生產環境下,服務器沒有互聯網,如何通過代理自己的電腦上互聯網?

服務器主機是CentOS7操作系統.&#xff0c;服務器的局域網是10.0.6.x網段。我的筆記本的以太網口的局域網ip是也是10.0.6.x&#xff0c;由于這個10.0.6.x的整個局域網是沒有撥號上網的所有無法訪問互聯網。 但是&#xff0c;如果筆記本臉上wifi&#xff0c;wifi的網段是192.168…

長度最小的子數組

給定一個含有 n 個正整數的數組和一個正整數 target 。 找出該數組中滿足其總和大于等于 target 的長度最小的 連續子數組 [numsl, numsl1, …, numsr-1, numsr] &#xff0c;并返回其長度。如果不存在符合條件的子數組&#xff0c;返回 0 。 示例 1&#xff1a; 輸入&#x…

MySQL 有多個普通索引時會取哪一個索引?

我們都知道MySQL在查詢時底層會進行索引的優化&#xff0c;假設有兩個普通索引&#xff0c;且where 后面也根據這兩個普通索引查詢數據&#xff0c;那么執行查詢語句時會使用到那個索引&#xff1f; 為了方便演示&#xff0c;新建users表&#xff0c;新建idx_name、idx_city這兩…