src/http/modules/ngx_http_upstream_least_time_module.c - nginx

Global variables defined

Data types defined

Functions defined

Macros defined

Source code


  1. /*
  2. * Copyright (C) Nginx, Inc.
  3. */


  4. #include <ngx_config.h>
  5. #include <ngx_core.h>
  6. #include <ngx_http.h>


  7. #define NGX_HTTP_UPSTREAM_LT_HEADER     0
  8. #define NGX_HTTP_UPSTREAM_LT_LAST_BYTE  1


  9. typedef struct {
  10.     ngx_uint_t                         mode;
  11.     ngx_uint_t                         use_inflight;
  12.                                                 /* unsigned  use_inflight:1; */
  13. } ngx_http_upstream_lt_conf_t;


  14. typedef struct {
  15.     /* the round robin data must be first */
  16.     ngx_http_upstream_rr_peer_data_t   rrp;

  17.     ngx_http_upstream_lt_conf_t       *conf;
  18.     ngx_uint_t                         inflight;  /* unsigned  inflight:1; */
  19.     ngx_http_upstream_t               *upstream;
  20. } ngx_http_upstream_lt_peer_data_t;


  21. static ngx_int_t ngx_http_upstream_init_least_time_peer(ngx_http_request_t *r,
  22.     ngx_http_upstream_srv_conf_t *us);
  23. static ngx_int_t ngx_http_upstream_get_least_time_peer(
  24.     ngx_peer_connection_t *pc, void *data);
  25. static ngx_uint_t ngx_http_upstream_least_time_eta(
  26.     ngx_http_upstream_lt_peer_data_t *ltp, ngx_http_upstream_rr_peer_t *peer);
  27. static void ngx_http_upstream_least_time_notify(ngx_peer_connection_t *pc,
  28.     void *data, ngx_uint_t type);
  29. static void ngx_http_upstream_least_time_inflight_done(
  30.     ngx_http_upstream_lt_peer_data_t *ltp, ngx_http_upstream_rr_peers_t *peers,
  31.     ngx_http_upstream_rr_peer_t *peer, ngx_msec_t last);
  32. static void ngx_http_upstream_free_least_time_peer(ngx_peer_connection_t *pc,
  33.     void *data, ngx_uint_t state);

  34. static void *ngx_http_upstream_least_time_create_conf(ngx_conf_t *cf);
  35. static char *ngx_http_upstream_least_time(ngx_conf_t *cf, ngx_command_t *cmd,
  36.     void *conf);


  37. static ngx_conf_enum_t  ngx_http_upstream_least_time_mode[] = {
  38.     { ngx_string("header"), NGX_HTTP_UPSTREAM_LT_HEADER },
  39.     { ngx_string("last_byte"), NGX_HTTP_UPSTREAM_LT_LAST_BYTE },
  40.     { ngx_null_string, 0 }
  41. };


  42. static ngx_command_t  ngx_http_upstream_least_time_commands[] = {

  43.     { ngx_string("least_time"),
  44.       NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12,
  45.       ngx_http_upstream_least_time,
  46.       NGX_HTTP_SRV_CONF_OFFSET,
  47.       offsetof(ngx_http_upstream_lt_conf_t, mode),
  48.       &ngx_http_upstream_least_time_mode },

  49.       ngx_null_command
  50. };


  51. static ngx_http_module_t  ngx_http_upstream_least_time_module_ctx = {
  52.     NULL,                                  /* preconfiguration */
  53.     NULL,                                  /* postconfiguration */

  54.     NULL,                                  /* create main configuration */
  55.     NULL,                                  /* init main configuration */

  56.     ngx_http_upstream_least_time_create_conf,
  57.                                            /* create server configuration */
  58.     NULL,                                  /* merge server configuration */

  59.     NULL,                                  /* create location configuration */
  60.     NULL                                   /* merge location configuration */
  61. };


  62. ngx_module_t  ngx_http_upstream_least_time_module = {
  63.     NGX_MODULE_V1,
  64.     &ngx_http_upstream_least_time_module_ctx, /* module context */
  65.     ngx_http_upstream_least_time_commands, /* module directives */
  66.     NGX_HTTP_MODULE,                       /* module type */
  67.     NULL,                                  /* init master */
  68.     NULL,                                  /* init module */
  69.     NULL,                                  /* init process */
  70.     NULL,                                  /* init thread */
  71.     NULL,                                  /* exit thread */
  72.     NULL,                                  /* exit process */
  73.     NULL,                                  /* exit master */
  74.     NGX_MODULE_V1_PADDING
  75. };


  76. static ngx_int_t
  77. ngx_http_upstream_init_least_time(ngx_conf_t *cf,
  78.     ngx_http_upstream_srv_conf_t *us)
  79. {
  80.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
  81.                    "init least time");

  82.     if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
  83.         return NGX_ERROR;
  84.     }

  85.     us->peer.init = ngx_http_upstream_init_least_time_peer;

  86.     return NGX_OK;
  87. }


  88. static ngx_int_t
  89. ngx_http_upstream_init_least_time_peer(ngx_http_request_t *r,
  90.     ngx_http_upstream_srv_conf_t *us)
  91. {
  92.     ngx_http_upstream_lt_conf_t       *ltcf;
  93.     ngx_http_upstream_lt_peer_data_t  *ltp;

  94.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  95.                    "init least time peer");

  96.     ltp = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_lt_peer_data_t));
  97.     if (ltp == NULL) {
  98.         return NGX_ERROR;
  99.     }

  100.     r->upstream->peer.data = &ltp->rrp;

  101.     if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
  102.         return NGX_ERROR;
  103.     }

  104.     r->upstream->peer.get = ngx_http_upstream_get_least_time_peer;
  105.     r->upstream->peer.free = ngx_http_upstream_free_least_time_peer;

  106.     ltp->upstream = r->upstream;

  107.     ltcf = ngx_http_conf_upstream_srv_conf(us,
  108.                                            ngx_http_upstream_least_time_module);

  109.     if (ltcf->use_inflight) {
  110.         r->upstream->peer.notify = ngx_http_upstream_least_time_notify;
  111.     }

  112.     ltp->conf = ltcf;

  113.     return NGX_OK;
  114. }


  115. static ngx_int_t
  116. ngx_http_upstream_get_least_time_peer(ngx_peer_connection_t *pc, void *data)
  117. {
  118.     ngx_http_upstream_lt_peer_data_t *ltp = data;

  119.     time_t                             now;
  120.     uintptr_t                          m;
  121.     ngx_int_t                          rc, total;
  122.     ngx_uint_t                         i, n, p, many, eta, best_eta;
  123.     ngx_msec_t                         ift;
  124.     ngx_http_upstream_rr_peer_t       *peer, *best;
  125.     ngx_http_upstream_rr_peers_t      *peers;
  126.     ngx_http_upstream_rr_peer_data_t  *rrp;

  127.     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  128.                    "get least time peer, try: %ui", pc->tries);

  129.     rrp = &ltp->rrp;

  130.     if (rrp->peers->single) {
  131.         return ngx_http_upstream_get_round_robin_peer(pc, rrp);
  132.     }

  133.     pc->cached = 0;
  134.     pc->connection = NULL;

  135.     now = ngx_time();

  136.     peers = rrp->peers;

  137.     ngx_http_upstream_rr_peers_wlock(peers);

  138. #if (NGX_HTTP_UPSTREAM_ZONE)
  139.     if (peers->config && rrp->config != *peers->config) {
  140.         goto busy;
  141.     }
  142. #endif

  143.     best = NULL;
  144.     total = 0;

  145. #if (NGX_SUPPRESS_WARN)
  146.     many = 0;
  147.     p = 0;
  148.     best_eta = 0;
  149. #endif

  150. #if (NGX_HTTP_UPSTREAM_SID)
  151.     best = ngx_http_upstream_get_rr_peer_by_sid(rrp, pc->hint, &p, 0);

  152.     if (best) {
  153.         goto best_chosen;
  154.     }
  155. #endif

  156.     for (peer = peers->peer, i = 0;
  157.          peer;
  158.          peer = peer->next, i++)
  159.     {
  160.         n = i / (8 * sizeof(uintptr_t));
  161.         m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));

  162.         if (rrp->tried[n] & m) {
  163.             continue;
  164.         }

  165.         if (peer->down) {
  166.             continue;
  167.         }

  168.         if (peer->max_fails
  169.             && peer->fails >= peer->max_fails
  170.             && now - peer->checked <= peer->fail_timeout)
  171.         {
  172.             continue;
  173.         }

  174.         if (peer->max_conns && peer->conns >= peer->max_conns) {
  175.             continue;
  176.         }

  177.         if (peer->inflight_reqs > 0) {

  178.             ift = peer->inflight_last / peer->inflight_reqs
  179.                   + (ngx_current_msec - peer->inflight_reqs_changed);

  180.             ngx_http_upstream_response_time_avg(&peer->inflight_time, ift);
  181.         }

  182.         /*
  183.          * select peer with least estimated time of processing; if there are
  184.          * multiple peers with the same time, select based on round-robin
  185.          */

  186.         eta = ngx_http_upstream_least_time_eta(ltp, peer);

  187.         if (best == NULL
  188.             || eta * best->weight < best_eta * peer->weight)
  189.         {
  190.             best = peer;
  191.             best_eta = eta;
  192.             many = 0;
  193.             p = i;

  194.         } else if (eta * best->weight == best_eta * peer->weight) {
  195.             many = 1;
  196.         }
  197.     }

  198.     if (best == NULL) {
  199.         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  200.                        "get least time peer, no peer found");

  201.         goto failed;
  202.     }

  203.     if (many) {
  204.         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  205.                        "get least time peer, many");

  206.         for (peer = best, i = p;
  207.              peer;
  208.              peer = peer->next, i++)
  209.         {
  210.             n = i / (8 * sizeof(uintptr_t));
  211.             m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));

  212.             if (rrp->tried[n] & m) {
  213.                 continue;
  214.             }

  215.             if (peer->down) {
  216.                 continue;
  217.             }

  218.             eta = ngx_http_upstream_least_time_eta(ltp, peer);

  219.             if (eta * best->weight != best_eta * peer->weight) {
  220.                 continue;
  221.             }

  222.             if (peer->max_fails
  223.                 && peer->fails >= peer->max_fails
  224.                 && now - peer->checked <= peer->fail_timeout)
  225.             {
  226.                 continue;
  227.             }

  228.             if (peer->max_conns && peer->conns >= peer->max_conns) {
  229.                 continue;
  230.             }

  231.             peer->current_weight += peer->effective_weight;
  232.             total += peer->effective_weight;

  233.             if (peer->effective_weight < peer->weight) {
  234.                 peer->effective_weight++;
  235.             }

  236.             if (peer->current_weight > best->current_weight) {
  237.                 best = peer;
  238.                 p = i;
  239.             }
  240.         }
  241.     }

  242.     best->current_weight -= total;

  243. #if (NGX_HTTP_UPSTREAM_SID)
  244. best_chosen:
  245. #endif

  246.     if (ltp->conf->use_inflight) {
  247.         if (best->inflight_reqs > 0) {
  248.             /* account time spent by inflight requests */
  249.             best->inflight_last +=
  250.                                (ngx_current_msec - best->inflight_reqs_changed)
  251.                                * best->inflight_reqs;
  252.         }

  253.         best->inflight_reqs_changed = ngx_current_msec;
  254.         best->inflight_reqs++;

  255.         ltp->inflight = 1;
  256.     }

  257.     if (now - best->checked > best->fail_timeout) {
  258.         best->checked = now;
  259.     }

  260.     pc->sockaddr = best->sockaddr;
  261.     pc->socklen = best->socklen;
  262.     pc->name = &best->name;

  263. #if (NGX_HTTP_UPSTREAM_SID)
  264.     pc->sid = &best->sid;
  265. #endif

  266.     best->conns++;

  267.     rrp->current = best;
  268.     ngx_http_upstream_rr_peer_ref(peers, best);

  269.     n = p / (8 * sizeof(uintptr_t));
  270.     m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));

  271.     rrp->tried[n] |= m;

  272.     ngx_http_upstream_rr_peers_unlock(peers);

  273.     return NGX_OK;

  274. failed:

  275.     if (peers->next) {
  276.         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  277.                        "get least time peer, backup servers");

  278.         rrp->peers = peers->next;

  279.         n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
  280.                 / (8 * sizeof(uintptr_t));

  281.         for (i = 0; i < n; i++) {
  282.              rrp->tried[i] = 0;
  283.         }

  284.         ngx_http_upstream_rr_peers_unlock(peers);

  285.         rc = ngx_http_upstream_get_least_time_peer(pc, rrp);

  286.         if (rc != NGX_BUSY) {
  287.             return rc;
  288.         }

  289.         ngx_http_upstream_rr_peers_wlock(peers);
  290.     }

  291. #if (NGX_HTTP_UPSTREAM_ZONE)
  292. busy:
  293. #endif

  294.     ngx_http_upstream_rr_peers_unlock(peers);

  295.     pc->name = peers->name;

  296.     return NGX_BUSY;
  297. }


  298. static ngx_uint_t
  299. ngx_http_upstream_least_time_eta(ngx_http_upstream_lt_peer_data_t *ltp,
  300.     ngx_http_upstream_rr_peer_t *peer)
  301. {
  302.     time_t      now;
  303.     ngx_msec_t  rt;

  304.     switch (ltp->conf->mode) {

  305.     case NGX_HTTP_UPSTREAM_LT_HEADER:
  306.         rt = peer->header_time;
  307.         break;

  308.     default: /* NGX_HTTP_UPSTREAM_LT_LAST_BYTE */
  309.         rt = peer->response_time;
  310.     }

  311.     now = ngx_time();

  312.     if (now - peer->checked > peer->fail_timeout) {
  313.         /*
  314.          * once in fail_timeout make response time of a peer 2 times
  315.          * lower to give chances to slow peers
  316.          */
  317.         rt >>= (now - peer->checked) / (peer->fail_timeout + 1);
  318.     }

  319.     if (peer->inflight_reqs > 0) {
  320.         /*
  321.          * average inflight time exceeding average response time indicates
  322.          * bad (low priority) peer
  323.          */
  324.         rt = ngx_max(rt, peer->inflight_time);
  325.     }

  326.     if (rt > 5000) {
  327.         /*
  328.          * consider peers with response time greater than max equally bad
  329.          * and thus fallback to least_conns
  330.          */
  331.         rt = 5000;

  332.     } else {
  333.         /*
  334.          * divide response times into clusters to allow round-robin for peers
  335.          * with close response times
  336.          */
  337.         rt += 20 - rt % 20;
  338.     }

  339.     /*
  340.      * estimated time peer has to spend to finish processing current requests
  341.      */
  342.     return rt * (1 + peer->conns);
  343. }


  344. static void
  345. ngx_http_upstream_least_time_notify(ngx_peer_connection_t *pc, void *data,
  346.     ngx_uint_t type)
  347. {
  348.     ngx_http_upstream_lt_peer_data_t *ltp = data;

  349.     ngx_msec_t                         last, *metric;
  350.     ngx_http_upstream_t               *u;
  351.     ngx_http_upstream_rr_peer_t       *peer;
  352.     ngx_http_upstream_rr_peers_t      *peers;
  353.     ngx_http_upstream_rr_peer_data_t  *rrp;

  354.     rrp = &ltp->rrp;

  355.     peers = rrp->peers;
  356.     peer = rrp->current;

  357.     u = ltp->upstream;

  358.     /*
  359.      * Only update average time here if needed for balancing.
  360.      * Otherwise, it will be updated in peer.free().
  361.      */

  362.     switch (type) {

  363.     case NGX_HTTP_UPSTREAM_NOTIFY_HEADER:

  364.         if (ltp->conf->mode != NGX_HTTP_UPSTREAM_LT_HEADER) {
  365.             return;
  366.         }

  367.         last = u->state->header_time;
  368.         metric = &peer->header_time;
  369.         break;

  370.     default:
  371.         return;
  372.     }

  373.     ngx_http_upstream_rr_peers_rlock(peers);
  374.     ngx_http_upstream_rr_peer_lock(peers, peer);

  375.     ngx_http_upstream_response_time_avg(metric, last);

  376.     if (ltp->inflight) {
  377.         ngx_http_upstream_least_time_inflight_done(ltp, peers, peer, last);
  378.     }

  379.     ngx_http_upstream_rr_peer_unlock(peers, peer);
  380.     ngx_http_upstream_rr_peers_unlock(peers);
  381. }


  382. static void
  383. ngx_http_upstream_least_time_inflight_done(
  384.     ngx_http_upstream_lt_peer_data_t *ltp, ngx_http_upstream_rr_peers_t *peers,
  385.     ngx_http_upstream_rr_peer_t *peer, ngx_msec_t last)
  386. {
  387.     if (peer->inflight_reqs == 1) {
  388.         /* no more inflight requests */
  389.         peer->inflight_last = 0;

  390.     } else {

  391.         /*
  392.          * account time spent by inflight requests and forget about
  393.          * request "completed" right now
  394.          */
  395.         peer->inflight_last += (ngx_current_msec - peer->inflight_reqs_changed)
  396.                                * peer->inflight_reqs - last;
  397.         peer->inflight_reqs_changed = ngx_current_msec;
  398.     }

  399.     peer->inflight_reqs--;
  400.     ltp->inflight = 0;
  401. }


  402. static void
  403. ngx_http_upstream_free_least_time_peer(ngx_peer_connection_t *pc,
  404.     void *data, ngx_uint_t state)
  405. {
  406.     ngx_http_upstream_lt_peer_data_t *ltp = data;

  407.     ngx_http_upstream_t               *u;
  408.     ngx_http_upstream_rr_peer_t       *peer;
  409.     ngx_http_upstream_rr_peers_t      *peers;
  410.     ngx_http_upstream_rr_peer_data_t  *rrp;

  411.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "free least time peer");

  412.     rrp = &ltp->rrp;
  413.     peers = rrp->peers;
  414.     peer = rrp->current;

  415.     u = ltp->upstream;

  416.     ngx_http_upstream_rr_peers_rlock(peers);
  417.     ngx_http_upstream_rr_peer_lock(peers, peer);

  418.     if (ltp->inflight) {
  419.         ngx_http_upstream_least_time_inflight_done(ltp, peers, peer,
  420.                                                    u->state->response_time);
  421.     }

  422.     /*
  423.      * only successful attempts are accounted to mitigate preferring
  424.      * of failing peers
  425.      */
  426.     if (state & (NGX_PEER_FAILED|NGX_PEER_NEXT)) {
  427.         goto done;
  428.     }

  429.     if (u->state->header_time == (ngx_msec_t) -1) {
  430.         goto done;
  431.     }

  432.     ngx_http_upstream_response_time_avg(&peer->response_time,
  433.                                         u->state->response_time);

  434.     if (ltp->conf->use_inflight
  435.         && ltp->conf->mode == NGX_HTTP_UPSTREAM_LT_HEADER)
  436.     {
  437.         goto done;
  438.     }

  439.     ngx_http_upstream_response_time_avg(&peer->header_time,
  440.                                         u->state->header_time);

  441. done:

  442.     ngx_http_upstream_free_round_robin_peer_locked(pc, rrp, state);
  443. }


  444. static void *
  445. ngx_http_upstream_least_time_create_conf(ngx_conf_t *cf)
  446. {
  447.     ngx_http_upstream_lt_conf_t  *conf;

  448.     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_lt_conf_t));
  449.     if (conf == NULL) {
  450.         return NULL;
  451.     }

  452.     /*
  453.      * set by ngx_pcalloc():
  454.      *
  455.      *     conf->use_inflight = 0;
  456.      */

  457.     conf->mode = NGX_CONF_UNSET_UINT;

  458.     return conf;
  459. }


  460. static char *
  461. ngx_http_upstream_least_time(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  462. {
  463.     ngx_str_t                     *value;
  464.     ngx_http_upstream_lt_conf_t   *ltcf;
  465.     ngx_http_upstream_srv_conf_t  *uscf;

  466.     uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);

  467.     if (uscf->peer.init_upstream) {
  468.         ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
  469.                            "load balancing method redefined");
  470.     }

  471.     uscf->peer.init_upstream = ngx_http_upstream_init_least_time;

  472.     uscf->flags = NGX_HTTP_UPSTREAM_CREATE
  473.                   |NGX_HTTP_UPSTREAM_MODIFY
  474.                   |NGX_HTTP_UPSTREAM_WEIGHT
  475.                   |NGX_HTTP_UPSTREAM_MAX_CONNS
  476.                   |NGX_HTTP_UPSTREAM_MAX_FAILS
  477.                   |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
  478.                   |NGX_HTTP_UPSTREAM_DOWN
  479.                   |NGX_HTTP_UPSTREAM_BACKUP;

  480.     if (cf->args->nelts == 3) {
  481.         value = cf->args->elts;
  482.         ltcf = ngx_http_conf_upstream_srv_conf(uscf,
  483.                                           ngx_http_upstream_least_time_module);
  484.         if (ngx_strcmp(value[2].data, "inflight") == 0) {
  485.             ltcf->use_inflight = 1;

  486.         } else {
  487.             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  488.                                "invalid parameter \"%V\"", &value[2]);
  489.             return NGX_CONF_ERROR;
  490.         }
  491.     }

  492.     return ngx_conf_set_enum_slot(cf, cmd, conf);
  493. }