choosebackend.c (11774B)
1 /************************************************************************* 2 * This file is part of Crosroads 1.23, a load balancer and fail over 3 * utility for TCP. Copyright (c) Karel Kubat, distributed under GPL. 4 * Visit http://crossroads.e-tunity.com for information. 5 *************************************************************************/ 6 #include "crossroads.h" 7 8 void choose_backend () { 9 int backends[MAX_BACKEND], nbackends = 0, i, j, k, 10 target_set = 0, flat_weights = 1, weights[MAX_BACKEND], *sel_weights, 11 tot_weights, lo_val, hi_val, done; 12 unsigned long long values[MAX_BACKEND], nbest; 13 unsigned nclients; 14 char *exthandler; 15 FILE *f; 16 char buf[80]; 17 # ifndef HAVE_SRANDDEV 18 struct timeval tv; 19 # endif 20 21 /* Check that we're allowed to accept at all. */ 22 if (activeservice->maxconnections && 23 servicereport->nclients >= activeservice->maxconnections) { 24 warning ("Service %s: max clients %d exceeded", 25 activeservice->name, 26 activeservice->maxconnections); 27 current_backend = -1; 28 return; 29 } 30 31 /* Populate the array of selectable backends. */ 32 for (i = 0; i < activeservice->nbackend; i++) { 33 /* Backend i is a candidate if: 34 * (a) it is available, AND 35 * (b) there's no max of connections, or the actual nr of connections 36 * is below max 37 */ 38 if (servicereport->backendstate[i].avail == st_available && 39 (activeservice->backend[i].maxconnections == 0 || 40 activeservice->backend[i].maxconnections > 41 servicereport->backendstate[i].nclients)) { 42 msg ("Service %s: " 43 "candidate back end: %d (max clients %d, active %d, " 44 "state %s)", 45 activeservice->name, 46 i, activeservice->backend[i].maxconnections, 47 servicereport->backendstate[i].nclients, 48 state_to_string(servicereport->backendstate[i].avail)); 49 backends[nbackends++] = i; 50 } 51 } 52 53 /* No backends? Nogo. */ 54 if (!nbackends) { 55 current_backend = -1; 56 warning ("No active backends to select!"); 57 return; 58 } 59 60 /* Only one backend? Then it's always the first one, 61 * whatever you do. */ 62 if (nbackends == 1) { 63 current_backend = backends[0]; 64 servicereport->last_backend = current_backend; 65 msg ("Service %s: only 1 backend to select (%d)", 66 activeservice->name, current_backend); 67 return; 68 } 69 70 /* More than 1 backend available.. make a wise choice. */ 71 switch (activeservice->dispatchtype) { 72 73 case ds_roundrobin: 74 /* Find the currently used backend, go one forward. */ 75 msg ("Service %s: last roundrobin back end was %d", 76 activeservice->name, servicereport->last_backend); 77 for (i = 0; i < nbackends; i++) 78 if (backends[i] == servicereport->last_backend) { 79 i++; 80 i %= nbackends; 81 current_backend = backends[i]; 82 servicereport->last_backend = current_backend; 83 msg ("Service %s: next roundrobin back end is %d", 84 activeservice->name, current_backend); 85 return; 86 } 87 /* None found.. try the first one (run to momma). */ 88 current_backend = backends[0]; 89 servicereport->last_backend = current_backend; 90 msg ("Service %s: " 91 "chosen backend (roundrobin, without historical info): %d", 92 activeservice->name, current_backend); 93 return; 94 95 case ds_random: 96 /* Re-randomize. */ 97 # ifdef HAVE_SRANDDEV 98 sranddev(); 99 msg ("Service %s: randomier seeded with randdev", activeservice->name); 100 # else 101 gettimeofday (&tv, 0); 102 srand ((unsigned) tv.tv_usec); 103 msg ("Service %s: randomizer seeded with %u", 104 activeservice->name, (unsigned) tv.tv_usec); 105 # endif 106 107 /* First of all let's see if all the weights are the same. */ 108 tot_weights = 0; 109 for (i = 0; i < nbackends; i++) { 110 values[i] = activeservice->backend[backends[i]].weight; 111 if (activeservice->backend[backends[i]].weight != 1) 112 flat_weights = 0; 113 } 114 115 if (! flat_weights) { 116 /* Non-flat weight random balancing. Determine the chance 117 * to hit a given back end. First we populate the weights array. 118 * Eg imagine we have 4 back ends with weights 1,2,3,4. Then 119 * ultimately we need to get a selection array of indices: 120 * 0000111223, meaning that the chance to hit index 0 is 4/10 121 * and so on */ 122 123 /* First of all we reverse the values vector, to get 4,3,2,1 */ 124 done = 0; 125 while (!done) { 126 done = 1; 127 lo_val = 9999999; 128 hi_val = 0; 129 /* Find highest/lowest weights in values vector. */ 130 for (i = 0; i < nbackends; i++) { 131 if (!values[i]) 132 continue; 133 done = 0; 134 if (lo_val >= values[i]) 135 lo_val = values[i]; 136 if (hi_val <= values[i]) 137 hi_val = values[i]; 138 } 139 140 if (done) 141 break; 142 143 /* Stick the values into the duplicate 'weights' array, 144 but the other way 'round. */ 145 for (i = 0; i < nbackends; i++) 146 if (values[i] == lo_val) 147 weights[i] = hi_val; 148 else if (values[i] == hi_val) 149 weights[i] = lo_val; 150 151 for (i = 0; i < nbackends; i++) 152 if (values[i] == lo_val) { 153 tot_weights += hi_val; 154 values[i] = 0; 155 } else if (values[i] == hi_val) { 156 tot_weights += lo_val; 157 values[i] = 0; 158 } 159 } 160 161 /* The 'weights' vector is now the reverse of 'values'. 162 * Create a selection array. */ 163 sel_weights = xmalloc (tot_weights * sizeof(int)); 164 k = 0; 165 for (i = 0; i < nbackends; i++) 166 for (j = 0; j < weights[i]; j++) { 167 sel_weights[k] = i; 168 k++; 169 } 170 171 /* Debugging: 172 * for (k = 0; k < tot_weights; k++) 173 * msg ("random: sel_weight[%d] = %d", k, sel_weights[k]); 174 */ 175 176 /* Select the next random back end. Algorithm is: 177 * i = rand() % tot_weights; 178 * j = sel_weights[i]; 179 * k = backends[j]; 180 * current_backend = k; 181 * msg ("i=%d j=%d, k=%d", i, j, k); 182 */ 183 i = rand() % tot_weights; 184 current_backend = backends[sel_weights[i]]; 185 servicereport->last_backend = current_backend; 186 free (sel_weights); 187 msg ("Service %s: " 188 "chosen backend (weighted random): %d at index %d", 189 activeservice->name, current_backend, i); 190 return; 191 } 192 193 /* ELSE: Choose a random one of the availables. */ 194 current_backend = backends[rand() % nbackends]; 195 servicereport->last_backend = current_backend; 196 msg ("Service %s: chosen backend (flat-weight random): %d", 197 activeservice->name, current_backend); 198 return; 199 200 case ds_bysize: 201 /* Fill the values */ 202 for (i = 0; i < nbackends; i++) { 203 /* When dispatch over #-connections is in place, then we 204 * compute using the averaged value. However if the averaged 205 * value is 0 (not yet computed), we take the true value. */ 206 if (activeservice->dispatchover && 207 servicereport->backendstate[backends[i]].avg_nbytes) 208 values[i] = 209 servicereport->backendstate[backends[i]].avg_nbytes * 210 activeservice->backend[backends[i]].weight; 211 else 212 values[i] = servicereport->backendstate[backends[i]].nbytes * 213 activeservice->backend[backends[i]].weight; 214 msg ("Service %s: " 215 "by size weighing backend %d has weight %d, value %u" 216 " (bytes=%u, avgbytes=%u)", 217 activeservice->name, 218 backends[i], 219 activeservice->backend[backends[i]].weight, 220 (unsigned) values[i], 221 (unsigned) servicereport->backendstate[backends[i]].nbytes, 222 (unsigned) 223 servicereport->backendstate[backends[i]].avg_nbytes); 224 } 225 /* Get the backend that has transported the least bytes */ 226 for (i = 0; i < nbackends; i++) 227 if (!target_set++ || values[i] < nbest) { 228 nbest = values[i]; 229 current_backend = backends[i]; 230 servicereport->last_backend = current_backend; 231 msg ("Service %s: " 232 "by size weighing: best so far is %d (value %g)", 233 activeservice->name, current_backend, nbest); 234 } 235 msg ("Service %s: chosen backend (by size): %d", 236 activeservice->name, current_backend); 237 return; 238 239 case ds_byduration: 240 /* Fill the values */ 241 for (i = 0; i < nbackends; i++) { 242 /* When dispatch over #-connections is in place, then we 243 * compute using the averaged value. However if the averaged 244 * value is 0 (not yet computed), we take the true value. */ 245 if (activeservice->dispatchover && 246 servicereport->backendstate[backends[i]].avg_nsec) 247 values[i] = 248 servicereport->backendstate[backends[i]].avg_nsec * 249 1000000 * 250 activeservice->backend[backends[i]].weight; 251 else 252 values[i] = servicereport->backendstate[backends[i]].nsec * 253 1000000 * 254 activeservice->backend[backends[i]].weight; 255 msg ("Service %s: By duration weighing: backend %d has value %lu" 256 " (sec=%g, avgsec=%g, weight=%d)", 257 activeservice->name, 258 backends[i], values[i], 259 servicereport->backendstate[backends[i]].nsec, 260 servicereport->backendstate[backends[i]].avg_nsec, 261 activeservice->backend[backends[i]].weight); 262 } 263 /* Get the backend that has transported the least connection secs */ 264 for (i = 0; i < nbackends; i++) 265 if (!target_set++ || values[i] < nbest) { 266 nbest = values[i]; 267 current_backend = backends[i]; 268 servicereport->last_backend = current_backend; 269 msg ("Service %s: " 270 "by duration weighing: best so far is %d (value %g)", 271 activeservice->name, current_backend, nbest); 272 } 273 msg ("Service %s: chosen backend (by duration): %d", 274 activeservice->name, current_backend); 275 return; 276 277 case ds_byorder: 278 /* Get the first available back end in line. */ 279 current_backend = backends[0]; 280 servicereport->last_backend = current_backend; 281 msg ("Service %s: chosen backend (by order): %d", 282 activeservice->name, current_backend); 283 return; 284 285 case ds_byconnections: 286 /* Get the weighing values. */ 287 for (i = 0; i < nbackends; i++) { 288 values[i] = 289 servicereport->backendstate[backends[i]].nclients * 290 activeservice->backend[backends[i]].weight; 291 msg ("Service %s: " 292 "by connections weighing: backend %d has value %u", 293 activeservice->name, backends[i], values[i]); 294 } 295 /* Find the minimum in the values array. */ 296 for (i = 0; i < nbackends; i++) 297 if (!target_set++ || values[i] < nclients) { 298 nclients = values[i]; 299 current_backend = backends[i]; 300 servicereport->last_backend = current_backend; 301 msg ("Service %s: " 302 "by connections weighing: best so far is %d (value %u)", 303 activeservice->name, current_backend, nclients); 304 } 305 msg ("Service %s: chosen backend (by connections): %d", 306 activeservice->name, current_backend); 307 return; 308 309 case ds_externalhandler: 310 /* External handler to be called */ 311 exthandler = str_expand_format (activeservice->dispatchext); 312 msg ("Service %s: calling external handler '%s'", 313 activeservice->name, exthandler); 314 uid_assume(); 315 if (! (f = popen (exthandler, "r")) ) { 316 warning ("Service %s: failed to start external handler '%s': %s", 317 activeservice->name, exthandler, strerror(errno)); 318 current_backend = backends[0]; 319 servicereport->last_backend = current_backend; 320 uid_restore(); 321 return; 322 } 323 while (1) { 324 if (fscanf (f, " %80s ", buf) < 1) { 325 msg ("Service %s: external handler signals end", 326 activeservice->name); 327 pclose (f); 328 uid_restore(); 329 break; 330 } 331 msg ("Service %s: external handler says '%s'", 332 activeservice->name, buf); 333 for (i = 0; i < nbackends; i++) 334 if (!strcmp (buf, activeservice->backend[backends[i]].name)) { 335 msg ("Service %s: selecting back end %s due to " 336 "external handler", activeservice->name, buf); 337 current_backend = backends[i]; 338 servicereport->last_backend = current_backend; 339 pclose (f); 340 uid_restore(); 341 return; 342 } 343 } 344 warning ("Service %s: external handler didn't reply with " 345 "a selectable back end", activeservice->name); 346 current_backend = backends[0]; 347 servicereport->last_backend = current_backend; 348 return; 349 350 default: 351 /* Internal fry.. */ 352 error ("Service %s: internal error: unhandled dispatch type %d", 353 activeservice->name, activeservice->dispatchtype); 354 } 355 }