当前位置: 首页 > 工具软件 > ngx_cc > 使用案例 >

tengine ngx_http_reqstat_module​​​​​​​​​​​​​​源码分析&移植

谭勇
2023-12-01

简介

ngx_http_reqstat_module - The Tengine Web Server

该模块功能为监视tengine运行状况,包括:连接数、请求数、各种响应码范围的请求数、输入输出流量、rt、upstream访问等。

该模块是tengine自带的module,如我们想在openresty中使用该模块,不仅需要add-module编译至openresty的nginx中,还需要改一些nginx相关的代码。

本文目的就是分析该模块,并把该tengine模块集成到openresty中。之后,reqstat监视的数据用来对接云监控系统,监控olwaf引擎运行状态。

只能按照server块的粒度来进行统计状态码。

示例配置如下:


req_status_zone server_status "$server_addr:$server_port:$upstream_addr" 100M;
req_status server_status;


server {
    listen 10.10.10.10:82;
    location /server_status {
        req_status_show req_status;
    }
}

Syntax: req_status_zone zone_name value size
Defaultnone
Contextmain

创建统计使用的共享内存k-v格式。zone_name是共享内存的名称,value用于定义key,通常设置为nginx内置变量。size是共享内存的大小。

例如:创建名为“server_status”的共享内存,大小100M,使用“$host,$server_addr:$server_port”为key作为统计粒度。

req_status_zone server_status "$server_addr:$server_port:$upstream_addr" 100M;

req_status_zone指令对应的函数ngx_http_reqstat_zone分析。

将这个字符串进行编译放入ctx->value,ctx为reqstat_module的上下文。

ngx_http_reqstat_zone函数

static char * ngx_http_reqstat_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

{

...

    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_reqstat_ctx_t));

    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

    ccv.cf = cf;

    ccv.value = &value[2];

    ccv.complex_value = &ctx->value; //指向ctx->value

    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {  

        return NGX_CONF_ERROR;

    }

    //经过compile后ctx的value存储这一格式

   

    ctx->val = ngx_palloc(cf->pool, sizeof(ngx_str_t));

    if (ctx->val == NULL) {

        return NGX_CONF_ERROR;

    }

    //ctx->val是string类型记录配置中key

    *ctx->val = value[2];

...

}

nginx中ngx_http_compile_complex_value解析变量配置使用方法:

typedef struct{

    ngx_conf_t *cf;

    ngx_str_t *value;

    ngx_http_complex_value_t *complex_value;

    //...

} ngx_http_compile_complex_value_t; //代码中常用ccv作为变量名

//ccv->cf和cc->value(带变量配置的字符串)为入参

//ccv->complex_value 为出参,常保存在xxx_conf_t中。

//该结构体常常与ngx_http_compile_complex_value函数一起配合使用。

//ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv)。

//编译解析带变量的配置如上文中$server_addr:$server_port:$upstream_addr。

--------------上面是带变量配置解析动作,下面就是使用获取变量配置动作-------------------

//入参为r和val,val通过ngx_http_compile_complex_value()获得。

//value为变量求值后的具体值。

//ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val, ngx_str_t *value);

ngx_http_reqstat函数

ngx_http_reqstat函数处理对应的req_status_zone配置相应的共享内存。

每个ngx_module_t关联一个ngx_command_t数组。

每个ngx_command_t结构对应nginx.conf配置文件中的一个指令。

ngx_command_t结构体定义如下:

struct ngx_command_s {
    ngx_str_t              name;     //name: 指令名称
    ngx_uint_t            type;       //type: 指令级别和指令参数定义
    char                 *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);      //set: 指令解析函数
    ngx_uint_t            conf;      //conf: 结构指针偏移
    ngx_uint_t            offset;    //offset: 结构体成员偏移
    void                    *post;      //post: 不常用
};

static char * ngx_http_reqstat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

{

    //cmd->type为NGX_HTTP_MAIN_CONF

    //cmd->conf结构指针偏移,为这个指令修改的配置最终偏移

...

    // ngx_conf_handler为NGX_MAIN_CONF从中取出配置

    //配置里面把ngx_http_reqstat放在http下面

    ngx_http_reqstat_conf_t      *rlcf = conf;

    value = cf->args->elts;

    //smcf为从cf->ctx中获取reqstat main配置。这与上面rlcf不一样。

    smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_reqstat_module);

    //rlcf monitor初始化

    rlcf->monitor = ngx_array_create(cf->pool, cf->args->nelts - 1, sizeof(ngx_shm_zone_t *));

    for (i = 1; i < cf->args->nelts; i++) {

        // 添加对应配置数量的shm_zone放入rlcf->monitor数组

        shm_zone = ngx_shared_memory_add(cf, &value[i], 0, &ngx_http_reqstat_module);

        z = ngx_array_push(rlcf->monitor);

        *z = shm_zone;

        z = smcf->monitor->elts;

        for (j = 0; j < smcf->monitor->nelts; j++) {

            if (!ngx_strcmp(value[i].data, z[j]->shm.name.data)) {

                break;

            }

        }

        // smcf存一个全量

        if (j == smcf->monitor->nelts) {

            z = ngx_array_push(smcf->monitor);

            if (z == NULL) {

                return NGX_CONF_ERROR;

            }

            *z = shm_zone;

        }

    }

...

}

配置指定完之后,调用ngx_http_reqstat_init来把两个handler放入对应阶段中。

ngx_http_reqstat_init函数

static ngx_int_t ngx_http_reqstat_init(ngx_conf_t *cf)

{

...

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);

    *h = ngx_http_reqstat_log_handler;

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);

    *h = ngx_http_reqstat_init_handler;

...

}

每个请求都要经过ngx_http_reqstat_init_handler在rewrite阶段。

创建初始化ngx_http_reqstat_store_t    store。

ngx_http_reqstat_init_handler函数

static ngx_int_t ngx_http_reqstat_init_handler(ngx_http_request_t *r)

{

...

    ngx_http_reqstat_conf_t      *rmcf, *rlcf;

    ngx_http_reqstat_store_t     *store;

    store = ngx_http_get_module_ctx(r, ngx_http_reqstat_module);

    rmcf = ngx_http_get_module_main_conf(r, ngx_http_reqstat_module);

    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_reqstat_module);

    if (store == NULL) {

        if (r->variables[rmcf->index].valid) {

            return NGX_DECLINED;

        }

        // 创建store

        store = ngx_http_reqstat_create_store(r, rlcf);

        if (store == NULL) {

            return NGX_ERROR;

        }

        // store关联当前的r,一旦这个请求r ngx_http_request_t销毁,就没有了。

        // 因此每个新的请求都要创建一个store。

        ngx_http_set_ctx(r, store, ngx_http_reqstat_module);

    }

...

}

ngx_http_reqstat_create_store函数

static ngx_http_reqstat_store_t *

ngx_http_reqstat_create_store(ngx_http_request_t *r,  ngx_http_reqstat_conf_t *rlcf)

{

...

    store = ngx_pcalloc(r->pool, sizeof(ngx_http_reqstat_store_t));

    // 创建monitor index和value index

    if (ngx_array_init(&store->monitor_index, r->pool, rlcf->monitor->nelts,

                       sizeof(ngx_http_reqstat_rbnode_t *)) == NGX_ERROR)

    {

        return NULL;

    }

    if (ngx_array_init(&store->value_index, r->pool, rlcf->monitor->nelts,

                       sizeof(ngx_str_t)) == NGX_ERROR)

    {

        return NULL;

    }

    // 在shm_zone里查找val找到node,每个shm_zone对应store的value_index和monitor_index

    shm_zone = rlcf->monitor->elts;

    for (i = 0; i < rlcf->monitor->nelts; i++) {

        z = shm_zone[i];

        ctx = z->data;

        if (ngx_http_complex_value(r, &ctx->value, &val) != NGX_OK) {

            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,

                          "failed to reap the key \"%V\"", ctx->val);

            continue;

        }

        value = ngx_array_push(&store->value_index);

        *value = val;

        fnode = ngx_http_reqstat_rbtree_lookup(shm_zone[i], &val);

        fnode_store = ngx_array_push(&store->monitor_index);

        *fnode_store = fnode;

...

}

ngx_http_reqstat_log_handler函数

static ngx_int_t

ngx_http_reqstat_log_handler(ngx_http_request_t *r)

{

...

    shm_zone = rcf->monitor->elts;

    // 获取共享内存node和value数组。

    fnode_store = store->monitor_index.elts;

    value = store->value_index.elts;

    // 遍历之前的monitor_index与shm_zone数量是对应的

    // 给每个shm_zone加入这个计数

    for (i = 0; i < store->monitor_index.nelts; i++) {

        if (fnode_store[i] == NULL) {

            continue;

        }

        // 获取z

        z = shm_zone[i];

        // 获取上下文ctx

        ctx = z->data;

        if (rcf->lazy) {

            // 判断是否是lazy,是的话使用r重新compile val

            // 这里用到了上面介绍的ngx_http_compile_complex_value获取变量值

            if (ngx_http_complex_value(r, &ctx->value, &val) != NGX_OK) {

                ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "failed to reap the key \"%V\"", ctx->val);

                continue;

            }

            // 如果val与之前编译的value一致,无需在reqstat_rbtree里搜索

            if (value[i].len == val.len && ngx_strncmp(value[i].data, val.data, val.len) == 0)

            {

                fnode = fnode_store[i];

            } else {

                // 否则需要在reqstat_rbtree里搜索,找到了fnode

                fnode = ngx_http_reqstat_rbtree_lookup(shm_zone[i], &val);

                if (fnode == NULL) {

                    ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,

                                  "failed to alloc node in zone \"%V\", "

                                  "enlarge it please",

                                  &z->shm.name);

                    fnode = fnode_store[i];

                } else {

                    //如果fnode不为空则开始计数

                    ngx_http_reqstat_count(fnode, NGX_HTTP_REQSTAT_BYTES_OUT,

                                           r->connection->sent);

                    ngx_http_reqstat_count(fnode, NGX_HTTP_REQSTAT_BYTES_IN,

                                           r->connection->received);

                    if (store) {

                        store->recv = r->connection->received;

                    }

                }

            }

        } else {

            fnode = fnode_store[i];

        }

...

}

ngx_http_reqstat_module集成到openresty

把tengine的ngx_http_reqstat.h和ngx_http_reqstat_module.c文件,拷贝到openresty的bundle->nginx->src->http->modules文件夹中。


然后nginx->auto->options文件增加
--with-http_reqstat_module)        HTTP_REQ_STATUS=YES        ;;
--with-http_reqstat_module         enable ngx_http_reqstat_module

nginx->src->core->ngx_connection.h文件添加

struct ngx_connection_s {
    void               *data;
    ngx_event_t        *read;
    ngx_event_t        *write;

    ngx_socket_t        fd;

    ngx_recv_pt         recv;
    ngx_send_pt         send;
    ngx_recv_chain_pt   recv_chain;
    ngx_send_chain_pt   send_chain;

    ngx_listening_t    *listening;

    off_t               sent;
    off_t               received; #增加received 

    ngx_log_t          *log;

    ngx_pool_t         *pool;

    int                 type;

    struct sockaddr    *sockaddr;
    socklen_t           socklen;
    ngx_str_t           addr_text;

    ngx_proxy_protocol_t  *proxy_protocol;

#if (NGX_SSL || NGX_COMPAT)
    ngx_ssl_connection_t  *ssl;
#endif

    ngx_udp_connection_t  *udp;

    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;

    ngx_buf_t          *buffer;

    ngx_queue_t         queue;

    ngx_atomic_uint_t   number;

    ngx_uint_t          requests;

    ......

}


nginx->src->http->ngx_http.c文件增加

ngx_http_output_header_filter_pt  ngx_http_top_header_filter;
ngx_http_output_body_filter_pt    ngx_http_top_body_filter;
ngx_http_request_body_filter_pt   ngx_http_top_request_body_filter;

ngx_int_t  (*ngx_http_top_input_body_filter) (ngx_http_request_t *r,
    ngx_buf_t *buf); #增加


ngx_str_t  ngx_http_html_default_types[] = {
    ngx_string("text/html"),
    ngx_null_string
};

nginx->src->http->ngx_http.h文件增加

extern ngx_http_output_body_filter_pt    ngx_http_top_body_filter;
extern ngx_http_request_body_filter_pt   ngx_http_top_request_body_filter;
extern ngx_http_input_body_filter_pt     ngx_http_top_input_body_filter;  #增加

nginx->src->http->ngx_http_upstream.h文件增加

typedef struct {
    ngx_uint_t                       status;
    ngx_msec_t                       response_time;
    ngx_msec_t                       connect_time;
    ngx_msec_t                       header_time;
   
    time_t                           header_sec; #增加
    ngx_uint_t                       header_msec; #增加
    ngx_msec_t                       queue_time;
    off_t                            response_length;
    off_t                            bytes_received;
    off_t                            bytes_sent;

    ngx_str_t                       *peer;
} ngx_http_upstream_state_t;

nginx->src->http->ngx_http_write_filter_module.c文件增加

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


static ngx_int_t ngx_http_write_filter_init(ngx_conf_t *cf);

ngx_int_t (*ngx_http_write_filter_stat)(ngx_http_request_t *r) = NULL; #增加

nginx->src->http->ngx_http_core_module.h文件增加

struct ngx_http_core_loc_conf_s {
    ngx_str_t     name;          /* location name */

#if (NGX_PCRE)
    ngx_http_regex_t  *regex;
#endif

    unsigned      noname:1;   /* "if () {}" block or limit_except */
    unsigned      lmt_excpt:1;
    unsigned      named:1;

    unsigned      exact_match:1;
    unsigned      noregex:1;

    unsigned      auto_redirect:1;
    //......

    ngx_bufs_t    client_body_buffers;     //增加
    size_t        client_body_postpone_size; //增加
    size_t        client_body_buffer_size; /* client_body_buffer_size */
    size_t        send_lowat;              /* send_lowat */
    size_t        postpone_output;         /* postpone_output */
    size_t        sendfile_max_chunk;      /* sendfile_max_chunk */
    size_t        read_ahead;              /* read_ahead */
    size_t        subrequest_output_buffer_size;
                                           /* subrequest_output_buffer_size */
    //......
    ngx_uint_t    server_tag_type;         /* server tag type 增加*/
    ngx_str_t     server_tag;              /* customized server tag 增加*/
    ngx_str_t     server_tag_header;       /* server tag header 增加*/

    //......
}

ngx_int_t ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name);

ngx_http_cleanup_t *ngx_http_cleanup_add(ngx_http_request_t *r, size_t size);

typedef ngx_int_t (*ngx_http_input_body_filter_pt)
    (ngx_http_request_t *r, ngx_buf_t *buf); //增加
typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
typedef ngx_int_t (*ngx_http_output_body_filter_pt)
    (ngx_http_request_t *r, ngx_chain_t *chain);
typedef ngx_int_t (*ngx_http_request_body_filter_pt)
    (ngx_http_request_t *r, ngx_chain_t *chain);

 类似资料: