Sky

Tengine Dynamic Dns 的解析

This project is maintained by wangfakang

对NGX_HTTP_UPSTREAM_DYNAMIC_MODULE的分析:

内容:

一:配置说明

二:代码解析

三:总结注意点

配置说明

upstream backend {
        dynamic_resolve fallback=stale fail_timeout=30s;

        server www.baidu.com;

    }

    server {


        location / {
            proxy_pass http://backend;
        }
    }

指定在某个upstream中启用动态域名解析功能。

解析:
其中有一个结构体类型:

ngx_http_upstream_dynamic_srv_t  {
    ngx_int_t     enabled;
    ngx_int_t     fallback;
    time_t        fail_timeout;
    time_t        fail_check;

    ngx_http_upstream_init_pt  original_init_get_peer;
        ngx_http_upstream_init_peer_t  original_init_peer;
}

属性含义:

代码解析

//发现dynamic_resolve指令的时候调用该函数进行处理
static char *
ngx_http_upstream_dynamic(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_upstream_srv_conf_t            *uscf;
    ngx_http_upstream_dynamic_srv_conf_t    *dcf;
    ngx_str_t   *value, s;
    ngx_uint_t   i;
    time_t       fail_timeout;
    ngx_int_t    fallback;

    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);

    dcf = ngx_http_conf_upstream_srv_conf(uscf,
                                          ngx_http_upstream_dynamic_module);

    if (dcf->original_init_upstream) {
        return "is duplicate";
    }
    //设置原有的回调函数(可以叫做记录,本来应该赋给uscf->peer.init_upstream的)
    dcf->original_init_upstream = uscf->peer.init_upstream
                                  ? uscf->peer.init_upstream
                                  : ngx_http_upstream_init_round_robin;

    uscf->peer.init_upstream = ngx_http_upstream_init_dynamic;

    /* read options */

    return NGX_CONF_OK;
}

上面设置的uscf->peer.init_upstream函数在 ngx_http_upstream_init_main_conf中调用,其uscf->peer.init_upstream 指向原型如下:

static ngx_int_t
ngx_http_upstream_init_dynamic(ngx_conf_t *cf,
    ngx_http_upstream_srv_conf_t *us)
{

    //此处执行一个后端的负载均衡算法,默认是round_robin
    if (dcf->original_init_upstream(cf, us) != NGX_OK) {
        return NGX_ERROR;
    }

    if (us->servers) {
        server = us->servers->elts;

        for (i = 0; i < us->servers->nelts; i++) {
            host = server[i].host;
            //将ip地址转换为长整形数字
            if (ngx_inet_addr(host.data, host.len) == INADDR_NONE) {
                break;
            }
        }

        if (i == us->servers->nelts) {
            dcf->enabled = 0;

            return NGX_OK;
        }
    }
    // us->peer.init 默认是init_round_robin_peer函数  
    dcf->original_init_peer = us->peer.init;
    //下面是当一个请求来到的时候进行调用该函数
    us->peer.init = ngx_http_upstream_init_dynamic_peer;
    //把其设置为激活状态
    dcf->enabled = 1;

    return NGX_OK;
}

其中在ngx_http_upstream_init_request中调用 us->peer.init,其中在上面函数中其指向 ngx_http_upstream_init_dynamic_peer如下:

static ngx_int_t
ngx_http_upstream_init_dynamic_peer(ngx_http_request_t *r,
    ngx_http_upstream_srv_conf_t *us)
{

    //默认是init_round_robin_peer
    if (dcf->original_init_peer(r, us) != NGX_OK) {
        return NGX_ERROR;
    }
    //下面就是一点小的技巧,修改原有回调指向,其实就想在原来的函数基础上增加自己的功能,自己的函数中再次调用
    //原来的函数
    //这个思想值得学习
    dp->conf = dcf;
    dp->upstream = r->upstream;
    dp->data = r->upstream->peer.data;
    dp->original_get_peer = r->upstream->peer.get;
    dp->original_free_peer = r->upstream->peer.free;
    dp->request = r;

    r->upstream->peer.data = dp;
    r->upstream->peer.get = ngx_http_upstream_get_dynamic_peer;
    r->upstream->peer.free = ngx_http_upstream_free_dynamic_peer;

    return NGX_OK;
}

最后在ngx_http_upstream_init_request函数中调用ngx_http_upstream_connect(r, u)在其又调用 ngx_event_connect_peer(&u->peer) rc = pc->get(pc, pc->data);其中pc->get就是指向 ngx_http_upstream_get_dynamic_peer;此时就完成了在upstream模块选择使用动态 dns查询后端peer了。其中ngx_http_upstream_get_dynamic_peer如下:

static ngx_int_t
 ngx_http_upstream_get_dynamic_peer(ngx_peer_connection_t *pc, void *data)
{
    .....

    if (pc->resolved == NGX_HTTP_UPSTREAM_DR_OK) {
        return NGX_OK;
    }

    dscf = bp->conf;
    r = bp->request;
    u = r->upstream;
   //当dns无法解析的时候  按照配置文件所选择的方式进行执行   
    if (pc->resolved == NGX_HTTP_UPSTREAM_DR_FAILED) {

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "resolve failed! fallback: %ui", dscf->fallback);

        switch (dscf->fallback) {
        //当无法访问的时候还是使用旧的解析结果
        case NGX_HTTP_UPSTREAM_DYN_RESOLVE_STALE:
            return NGX_OK;
          //当无法访问的时候把当其关闭
        case NGX_HTTP_UPSTREAM_DYN_RESOLVE_SHUTDOWN:
            ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
            return NGX_YIELD;
          //当无法访问的时候默认使用下一个server
        default:
            /* default fallback action: check next upstream */
            return NGX_DECLINED;
        }

        return NGX_DECLINED;
    }
    //当没有失败的时候则进行超时时间的判断,在timeout时间内dns是无效的
    if (dscf->fail_check
        && (ngx_time() - dscf->fail_check < dscf->fail_timeout))
    {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "in fail timeout period, fallback: %ui", dscf->fallback);

        switch (dscf->fallback) {

        case NGX_HTTP_UPSTREAM_DYN_RESOLVE_STALE:
            return bp->original_get_peer(pc, bp->data);

        case NGX_HTTP_UPSTREAM_DYN_RESOLVE_SHUTDOWN:
            ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
            return NGX_YIELD;

        default:
            /* default fallback action: check next upstream, still need
             * to get peer in fail timeout period
             */
            return bp->original_get_peer(pc, bp->data);
        }

        return NGX_DECLINED;
    }

    //bp->original_get_peer 其指向各种负载均衡算法的(ip_hash、round_robin、consistent_hash)get_peer

    rc = bp->original_get_peer(pc, bp->data);

    if (rc != NGX_OK) {
        return rc;
    }

    /* resolve name */

    if (pc->host == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "load balancer doesn't support dyn resolve!");
        return NGX_OK;
    }
    //判断该host是不是一个IP  若是一个IP则直接返回
    if (ngx_inet_addr(pc->host->data, pc->host->len) != INADDR_NONE) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "host is an IP address, connect directly!");
        return NGX_OK;
    }

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    if (clcf->resolver == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "resolver has not been configured!");
        return NGX_OK;
    }

    temp.name = *pc->host;
    //开始准备DNS解析 
    ctx = ngx_resolve_start(clcf->resolver, &temp);
    if (ctx == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "resolver start failed!");
        return NGX_OK;
    }
    //当没有配置dns服务器的时候
    if (ctx == NGX_NO_RESOLVER) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "resolver started but no resolver!");
        return NGX_OK;
    }

    ctx->name = *pc->host;
    /* TODO remove */
    // ctx->type = NGX_RESOLVE_A;
    /* END */
    //设置其回调函数
    //此函数主要是做一些后续处理,如当dns查询成功或是失败修改一些状态  
    //以及函数ngx_resolve_name_done的调用,用来释放一些空间以及调用ngx_resolver_expire
    //上面函数做一些rn结点的过期检查 
    ctx->handler = ngx_http_upstream_dynamic_handler;
    ctx->data = r;
    ctx->timeout = clcf->resolver_timeout;

    u->dyn_resolve_ctx = ctx;
    //根据名字进行dns查询
    if (ngx_resolve_name(ctx) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, pc->log, 0,
                      "resolver name failed!\n");

        u->dyn_resolve_ctx = NULL;

        return NGX_OK;
    }

    return NGX_YIELD;
}

总结注意点

注意点:

欢迎一起交流学习

在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流

Thx

Author