1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "mod_proxy.h" 18#include "scoreboard.h" 19#include "ap_mpm.h" 20#include "apr_version.h" 21#include "ap_hooks.h" 22 23module AP_MODULE_DECLARE_DATA lbmethod_byrequests_module; 24 25static int (*ap_proxy_retry_worker_fn)(const char *proxy_function, 26 proxy_worker *worker, server_rec *s) = NULL; 27 28/* 29 * The idea behind the find_best_byrequests scheduler is the following: 30 * 31 * lbfactor is "how much we expect this worker to work", or "the worker's 32 * normalized work quota". 33 * 34 * lbstatus is "how urgent this worker has to work to fulfill its quota 35 * of work". 36 * 37 * We distribute each worker's work quota to the worker, and then look 38 * which of them needs to work most urgently (biggest lbstatus). This 39 * worker is then selected for work, and its lbstatus reduced by the 40 * total work quota we distributed to all workers. Thus the sum of all 41 * lbstatus does not change.(*) 42 * 43 * If some workers are disabled, the others will 44 * still be scheduled correctly. 45 * 46 * If a balancer is configured as follows: 47 * 48 * worker a b c d 49 * lbfactor 25 25 25 25 50 * 51 * And b gets disabled, the following schedule is produced: 52 * 53 * a c d a c d a c d ... 54 * 55 * Note that the above lbfactor setting is the *exact* same as: 56 * 57 * worker a b c d 58 * lbfactor 1 1 1 1 59 * 60 * Asymmetric configurations work as one would expect. For 61 * example: 62 * 63 * worker a b c d 64 * lbfactor 1 1 1 2 65 * 66 * would have a, b and c all handling about the same 67 * amount of load with d handling twice what a or b 68 * or c handles individually. So we could see: 69 * 70 * b a d c d a c d b d ... 71 * 72 */ 73 74static proxy_worker *find_best_byrequests(proxy_balancer *balancer, 75 request_rec *r) 76{ 77 int i; 78 int total_factor = 0; 79 proxy_worker **worker; 80 proxy_worker *mycandidate = NULL; 81 int cur_lbset = 0; 82 int max_lbset = 0; 83 int checking_standby; 84 int checked_standby; 85 86 if (!ap_proxy_retry_worker_fn) { 87 ap_proxy_retry_worker_fn = 88 APR_RETRIEVE_OPTIONAL_FN(ap_proxy_retry_worker); 89 if (!ap_proxy_retry_worker_fn) { 90 /* can only happen if mod_proxy isn't loaded */ 91 return NULL; 92 } 93 } 94 95 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01207) 96 "proxy: Entering byrequests for BALANCER (%s)", 97 balancer->s->name); 98 99 /* First try to see if we have available candidate */ 100 do { 101 checking_standby = checked_standby = 0; 102 while (!mycandidate && !checked_standby) { 103 worker = (proxy_worker **)balancer->workers->elts; 104 for (i = 0; i < balancer->workers->nelts; i++, worker++) { 105 if (!checking_standby) { /* first time through */ 106 if ((*worker)->s->lbset > max_lbset) 107 max_lbset = (*worker)->s->lbset; 108 } 109 if ( 110 ((*worker)->s->lbset != cur_lbset) || 111 (checking_standby ? !PROXY_WORKER_IS_STANDBY(*worker) : PROXY_WORKER_IS_STANDBY(*worker)) || 112 (PROXY_WORKER_IS_DRAINING(*worker)) 113 ) { 114 continue; 115 } 116 117 /* If the worker is in error state run 118 * retry on that worker. It will be marked as 119 * operational if the retry timeout is elapsed. 120 * The worker might still be unusable, but we try 121 * anyway. 122 */ 123 if (!PROXY_WORKER_IS_USABLE(*worker)) 124 ap_proxy_retry_worker_fn("BALANCER", *worker, r->server); 125 /* Take into calculation only the workers that are 126 * not in error state or not disabled. 127 */ 128 if (PROXY_WORKER_IS_USABLE(*worker)) { 129 (*worker)->s->lbstatus += (*worker)->s->lbfactor; 130 total_factor += (*worker)->s->lbfactor; 131 if (!mycandidate || (*worker)->s->lbstatus > mycandidate->s->lbstatus) 132 mycandidate = *worker; 133 } 134 } 135 checked_standby = checking_standby++; 136 } 137 cur_lbset++; 138 } while (cur_lbset <= max_lbset && !mycandidate); 139 140 if (mycandidate) { 141 mycandidate->s->lbstatus -= total_factor; 142 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01208) 143 "proxy: byrequests selected worker \"%s\" : busy %" APR_SIZE_T_FMT " : lbstatus %d", 144 mycandidate->s->name, mycandidate->s->busy, mycandidate->s->lbstatus); 145 146 } 147 148 return mycandidate; 149} 150 151/* assumed to be mutex protected by caller */ 152static apr_status_t reset(proxy_balancer *balancer, server_rec *s) { 153 int i; 154 proxy_worker **worker; 155 worker = (proxy_worker **)balancer->workers->elts; 156 for (i = 0; i < balancer->workers->nelts; i++, worker++) { 157 (*worker)->s->lbstatus = 0; 158 } 159 return APR_SUCCESS; 160} 161 162static apr_status_t age(proxy_balancer *balancer, server_rec *s) { 163 return APR_SUCCESS; 164} 165 166/* 167 * How to add additional lbmethods: 168 * 1. Create func which determines "best" candidate worker 169 * (eg: find_best_bytraffic, above) 170 * 2. Register it as a provider. 171 */ 172static const proxy_balancer_method byrequests = 173{ 174 "byrequests", 175 &find_best_byrequests, 176 NULL, 177 &reset, 178 &age 179}; 180 181static void register_hook(apr_pool_t *p) 182{ 183 /* Only the mpm_winnt has child init hook handler. 184 * make sure that we are called after the mpm 185 * initializes and after the mod_proxy 186 */ 187 ap_register_provider(p, PROXY_LBMETHOD, "byrequests", "0", &byrequests); 188} 189 190AP_DECLARE_MODULE(lbmethod_byrequests) = { 191 STANDARD20_MODULE_STUFF, 192 NULL, /* create per-directory config structure */ 193 NULL, /* merge per-directory config structures */ 194 NULL, /* create per-server config structure */ 195 NULL, /* merge per-server config structures */ 196 NULL, /* command apr_table_t */ 197 register_hook /* register hooks */ 198}; 199