技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> Apache --> nginx自定义模块编写-实时统计模块

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

浏览:7618次  出处信息

    不是第一次写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

    QQ技术交流群:445447336,欢迎加入!
    扫一扫订阅我的微信号:IT技术博客大学习
    © 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

    京ICP备15002552号-1