原文:http://www.vimer.cn/2012/05/nginx%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E5%9D%97%E7%BC%96%E5%86%99-%E5%AE%9E%E6%97%B6%E7%BB%9F%E8%AE%A1%E6%A8%A1%E5%9D%97.html

不是第一次写nginx的自定义模块了,之前有写过根据POST数据转发请求的模块(参见nginx自定义模块编写-根据post参数路由到不同服务器),不过上次写的是处理模块,而这次写的是过滤模块,还是有一些区别的。

在正式开始前,先说一下写nginx自定义模块要注意的几个点:

  1. 上次的文章提到,在函数里用r-connection.log打印log会core,今天发现是ngx头文件和lua头文件引用顺序的问题,把ngx的头文件放在最前面即可解决
  2. nginx的一个字符串类型 ngx_str_t 有两个参数, len 和 data,这两个参数一定要一起使用,因为data的\0结尾,不一定是len的长度,这一点千万要注意
  3. 需要和cpp文件联合编译是,在ngx的编译参数里面加上–with-ld-opt=”-lstdc++”

OK,废话不多说,开始正式说我这次写的统计模块吧

需求背景呢,就是现在已经在nginx后面挂了很多服务器,需要用nginx来统计成功率,响应时间等等参数,在网上翻了半天,大部分居然是用access_log,然后用程序扫描$request_time来实现的,这对一个每秒几千次访问的服务器是不可忍受的,所以最终没办法,那就自己写一个呗~

重新看了nginx自定义模块的开发文档,整个调用过程如下:

但是实在是没找到请求整个结束时的回调函数,最接近的也就是用filter模块了(即过滤模块),当然这样统计出来的请求时间,可能会比实际时间短一些。

OK,定了要写那种模块后,我们来考虑一下具体的实现

  1. 为了性能最大话,上报使用UDP协议,并且不收取回包,socket创建之后不释放
  2. 既然涉及到网络上报,就需要设置上报ip,port,来源等参数
  3. 过滤模块有两个函数,分别是ngx_http_output_header_filter_pt和ngx_http_output_body_filter_pt
  4. 上报的字段应该包括,method,uri,request_time,http状态码,目标IP,等等

UDP上报client这里,因为是用的公司的库,而且又很简单,这里就不细说了,大家看代码也会看到,我在里面用的是static变量来保证不析构:

 
1
static COpenApiMonitorClient client;

参数配置这里,因为上报库的要求,需要ip,port,来源,所以配置代码如下:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct {
    ngx_str_t  host;
    ngx_int_t  port;
    ngx_str_t  collect_point;
} ngx_http_stat_report_conf_t;
 
static ngx_command_t  ngx_http_stat_report_filter_commands[] = {
    { ngx_string("stat_report_host"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_stat_report_conf_t, host),
        NULL },
 
    { ngx_string("stat_report_port"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_num_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_stat_report_conf_t, port),
        NULL },
 
    { ngx_string("stat_report_collect_point"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_stat_report_conf_t, collect_point),
        NULL },
 
    ngx_null_command
};

对于是选择ngx_http_output_header_filter_pt还是ngx_http_output_body_filter_pt这里,我最终选择了ngx_http_output_header_filter_pt,虽然说计算请求时间上会有差别,但是因为ngx_http_output_body_filter_pt会进入多次,写起来更复杂些,所以就走简单的了~

代码如下:

 
1
2
3
4
5
6
7
8
9
10
11
static ngx_int_t
ngx_http_stat_report_header_filter(ngx_http_request_t *r)
{
    ngx_http_stat_report_conf_t  *conf;
 
    conf = (ngx_http_stat_report_conf_t  *)ngx_http_get_module_loc_conf(r, ngx_http_stat_report_filter_module);
 
    SendStatReport(r, conf);
 
    return ngx_http_next_header_filter(r);
}

对于上报字段这里,主要是ngx_http_request_t每个字段的意义搞了我很久时间,这里也不多解释了,直接贴代码,大家应该能直接看懂了

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
ngx_http_request_body_t* rb = r->request_body;
 
char* body = NULL;
int body_size = 0;
 
if (rb && rb->buf)
{  
    body = (char*)rb->buf->pos;
    body_size = rb->buf->last - rb->buf->pos;
}  
 
string str_uri = r->uri.data ? string((char*)r->uri.data,r->uri.len) : "";
 
string protocol = r->http_protocol.data ? string((char*)r->http_protocol.data, r->http_protocol.len) : "";
 
string str_data;
 
map<string,string> params;
 
if (r->method == 2) // get
{  
    pkg.method = "GET";
    str_data = r->args.data ? string((char*)r->args.data, r->args.len) : "";
}  
else if (r->method == 8)
{  
    pkg.method = "POST";
    str_data = (body && body_size>0) ? string(body, body_size) : "";
}  
else
{  
    return -1;
}  
//ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "args:%s uri:%s protocol:%s end", str_data.c_str(), str_uri.c_str(), protocol.c_str());
 
Trans2MapParams(str_data, params);
 
ngx_msec_int_t ms = get_pass_time_ms(r);
double time_sec = ((double)ms)/1000;
 
pkg.appid = strtoul(params["appid"].c_str(), NULL, 10);
pkg.rc = 0;
if (r->headers_out.status >= 400) // 就代表有问题
{  
    pkg.rc = r->headers_out.status;
}  
pkg.timestamp = time(NULL);
pkg.time = time_sec;
pkg.pf = params["pf"];
//转发的IP和port
pkg.svr_name = "";
if (r->upstream && r->upstream->peer.name && r->upstream->peer.name->data)
{  
    pkg.svr_name = string((char*)r->upstream->peer.name->data, r->upstream->peer.name->len);
}
pkg.interface = str_uri;
pkg.protocol = ParseProtocol(protocol);
pkg.collect_point = conf->collect_point.data ? string((char*)conf->collect_point.data, conf->collect_point.len) : "";

OK,整个代码基本就是这样了

惯例,下面又发现的新问题,纠结了我好久,现在还是没解决

  1. ngx_log_error在打印%u的时候,会导致进程退出,至今不知道为啥,解决方式就是打印成%d
  2. ngx_log_error在打印%f的时候,会打印成int,而且即使指定%.2f之类的,打印的结果也不对,不知为啥,解决方式就是把值*1000变成int

最后,代码已经上传的googlecode
https://vimercode.googlecode.com/svn/trunk/nginx_stat_report

nginx自定义模块编写-实时统计模块--转载的更多相关文章

  1. nginx自定义模块编写-根据post参数路由到不同服务器

    nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器,然而当我们需要根据http包体来进行请求路由时,nginx默认的配置规则就捉襟见肘了,但是没关系,nginx提供了强大的自 ...

  2. Nginx自定义模块编写:根据post参数路由到不同服务器

    Nginx自定义模块编写:根据post参数路由到不同服务器 2014-05-05 15:27 blogread IT技术博客 字号:T | T Nginx可以轻松实现根据不同的url 或者 get参数 ...

  3. nginx自定义模块记录上游服务器特定响应头

    功能,服务器通过扩展自定义命令,记录上游的服务器返回的特定响应头内容,记录到本地文件中 代码如下: /* * Copyright (C) Ciaos */ #include <ngx_confi ...

  4. Nginx模块开发1_明白自定义模块的编译流程

    自定义模块的编译流程 --add-module参数 configure使用--add-module参数指定添加模块目录. config脚本 由--add-module指定的目录保存为$ngx-addo ...

  5. Nginx相关模块学习使用实践指南

    转载自:https://www.bilibili.com/read/cv16150654?spm_id_from=333.999.0.0 0x01 Nginx 常用模块使用实践 官方模块使用手册:ht ...

  6. nginx -- handler模块(100%)

    handler模块简介 相信大家在看了前一章的模块概述以后,都对nginx的模块有了一个基本的认识.基本上作为第三方开发者最可能开发的就是三种类型的模块,即handler,filter和load-ba ...

  7. 实现Nginx Upload 模块 功能上传文件。

    分析(也许我表达的让人难以理解,但是我想说一句,直接实践是最好的.....): 一.Ningx 上传( 1.安装Nginx 的模块文件(upload):https://www.nginx.com/re ...

  8. mac下Nginx+lua模块编译安装

    Nginx的nb之处就不说了,lua也是一个小巧的脚本语言,由标准C编写而成,几乎可以运行在所有的平台上,也非常强大,其他特性请自行度娘.nginx_lua_module是由淘宝的工程师清无(王晓哲) ...

  9. Func系列3:自定义模块

    简介 Func自带的模块已经非常丰富,但在日常系统运维当中,尤其是面对大规模的服务器集群.不同类别的业务平台,次是Func自带的模块或许已经不能满足我们的需求,所以有必要通过自定义模块来填补这块的不足 ...

随机推荐

  1. 【转】在Tomcat配置JNDI数据源的三种方式

    在我过去工作的过程中,开发用服务器一般都是Tomcat 数据源的配置往往都是在applicationContext.xml中配置一个dataSource的bean 然后在部署时再修改JNDI配置 我猜 ...

  2. Spring中的事务管理

    事务简介: 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性 事务就是一系列的动作,它们被当作一个单独的工作单元.这些动作要么全部完成,要么全部不起作用 事务的四个关键属性( ...

  3. ODAC连接远程Oracle数据库时,数据源名称orcl改为gscloud

    今天用ODAC连接远程Oracle数据库时,怎么也连接不上, 更改配置文件的tnsname.ora,使之都一样,并完全配置正确还是出现错误,连接不上. 最后请大神一世,原来是数据源名称的问题. 把数据 ...

  4. HDU 5313 Bipartite Graph

    题意:给一个二分图,问想让二分图变成完全二分图最多能加多少条边. 解法:图染色+dp+bitset优化.设最终的完全二分图两部分点集为A和B,A中点个数为x,B中点个数为y,边数则为x × y,答案即 ...

  5. tcxtreelist 展示图片 图像

    procedure TfrmstPostImageCollate.LoadCollateImage(AOrderID: integer);var mySql: string; node: TcxTre ...

  6. PHP基本语法的小结

    一.PHP能做什么? PHP能做什么?我觉得它很强大,只要我能想到的,它都能做,只是我技术能力还不行╮(╯﹏╰)╭.好吧,一张图,基本了解一下吧(ps:PHP的功能不局限于此( ^_^ )) 图像有点 ...

  7. 【po3693】Maximum repetition substring

    题意: 给定一个字符串 求重复次数最多的连续重复子串 并输出字典序最小方案 题解: 枚举子串长度L 显然如果重复次数>1 那么答案串肯定包含s[1],s[1+L],s[1+L*2],...中的两 ...

  8. delphi 调用 webservice (.NET C#版)

    uses XMLIntf, XMLDoc; XML to XTR文件转换 .File-->open打开你要分析的XML文件 .在左边选择你要分析的接点,双击加到中间的转换列表中 .Create- ...

  9. iOS几个效果动画-------------------(实例详讲)qq粘性效果

    这几天做了一些简单iOS的效果图,感觉苹果官方已经帮我们做了很多了,我们只是站在巨人的肩膀上编程,这些也没什么难的,最难的也就是用到了初中的三角函数,先让大家看看这几个动画吧.先列这几个把,由上而下分 ...

  10. 用EPOLL进行压力测试

    在以前的博客中提到的一个服务端,在以前压力测试的过程中,发现单核CPU最多能达到1000TPS 还以为是服务端性能不够好,所以一直想着怎么去优化它. 但优化的思路明显不多,所以就考虑换一种压力测试的方 ...