IT技术博客大学习 共学习 共进步

nginx自定义模块编写-实时统计模块

Vimer 2012-05-10 23:55:58 累计浏览 8,663 次
本机暂存

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

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

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

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

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

        1

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

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

  • 为了性能最大话,上报使用UDP协议,并且不收取回包,socket创建之后不释放
  • 既然涉及到网络上报,就需要设置上报ip,port,来源等参数
  • 过滤模块有两个函数,分别是ngx_http_output_header_filter_pt和ngx_http_output_body_filter_pt
  • 上报的字段应该包括,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 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,整个代码基本就是这样了

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

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

    建议继续学习

    1. 配置Nginx+uwsgi更方便地部署python应用 (累计阅读 106,960)
    2. 搜狐闪电邮箱的 Nginx/Postfix 使用模式 (累计阅读 33,820)
    3. 记录一个软中断问题 (累计阅读 16,882)
    4. 解析nginx负载均衡 (累计阅读 16,500)
    5. server日志的路径分析 (累计阅读 11,183)
    6. Nginx模块开发入门 (累计阅读 11,100)
    7. 检查nginx配置,重载配置以及重启的方法 (累计阅读 10,781)
    8. Cacti 添加 Nginx 监控 (累计阅读 10,521)
    9. 使用Squid缓存视频 (累计阅读 10,280)
    10. fsockopen 异步处理 (累计阅读 10,281)