mod_proxy_balancer.c revision e8f95a682820a599fe41b22977010636be5c2717
/* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
* applicable.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Load balancer module for Apache proxy */
#define CORE_PRIVATE
#include "mod_proxy.h"
#include "ap_mpm.h"
#include "apr_version.h"
#include "apr_hooks.h"
{
const char *err;
apr_port_t port = 0;
url += 9;
}
else {
return DECLINED;
}
"proxy: BALANCER: canonicalising URL %s", url);
/* do syntatic check.
* We break the URL into host, port, path, search
*/
if (err) {
"error parsing URL %s: %s",
return HTTP_BAD_REQUEST;
}
/* N.B. if this isn't a true proxy request, then the URL _path_
* has already been decoded. True proxy requests have r->uri
* == r->unparsed_uri, and no others have that property.
*/
if (r->uri == r->unparsed_uri) {
*(search++) = '\0';
}
else
/* process path */
return HTTP_BAD_REQUEST;
return OK;
}
{
int i;
++workers;
}
/* Set to the original configuration */
}
/* Set default number of attempts to the number of
* workers.
*/
}
return 0;
}
/* Retrieve the parameter with the given name
* Something like 'JSESSIONID=12345...N'
*/
const char *name)
{
if (*path == '=') {
/*
* Session path was found, get it's value
*/
++path;
char *q;
*q = '\0';
return path;
}
}
}
return NULL;
}
{
const char *cookies;
const char *start_cookie;
if (start_cookie == cookies ||
++start_cookie;
/*
* Session cookie was found, get it's value
*/
char *end_cookie, *cookie;
++start_cookie;
*end_cookie = '\0';
*end_cookie = '\0';
return cookie;
}
}
}
}
return NULL;
}
/* Find the worker that has the 'route' defined
*/
const char *route)
{
int i;
return worker;
}
worker++;
}
return NULL;
}
request_rec *r,
char **route,
char **url)
{
return NULL;
/* Try to find the sticky route inside url */
if (!*route)
"proxy: BALANCER: Found value %s for "
/*
* If we found a value for sticksession, find the first '.' within.
* Everything after '.' (if present) is our route.
*/
(*route)++;
"proxy: BALANCER: Found route %s", *route);
/* We have a route in path or in cookie
* Find the worker that has this route defined.
*/
/* We have a worker that is unusable.
* It can be in error or disabled, but in case
* it has a redirection set use that redirection worker.
* This enables to safely remove the member from the
* balancer. Of course you will need a some kind of
* session replication between those two remote.
*/
/* Check if the redirect worker is usable */
}
return worker;
}
else
return NULL;
}
request_rec *r)
{
return NULL;
/*
PROXY_THREAD_UNLOCK(balancer);
return NULL;
*/
/* All the workers are in error state or disabled.
* If the balancer has a timeout sleep for a while
* and try again to find the worker. The chances are
* that some other thread will release a connection.
* By default the timeout is not set, and the server
* returns SERVER_BUSY.
*/
#if APR_HAS_THREADS
/* XXX: This can perhaps be build using some
* smarter mechanism, like tread_cond.
* But since the statuses can came from
* different childs, use the provided algo.
*/
/* Set the timeout to 0 so that we don't
* end in infinite loop
*/
/* Try again */
break;
}
/* restore the timeout */
}
#endif
}
return candidate;
}
char **url)
{
if (scheme)
/* we break the URL into host, port, uri */
if (!worker) {
"missing worker. URI cannot be parsed: ", *url,
NULL));
}
return OK;
}
request_rec *r,
{
int access_status;
/* Step 1: check if the url is for us
* The url we can handle starts with 'balancer://'
* If balancer is already provided skip the search
* for balancer, because this is failover attempt.
*/
if (!*balancer &&
return DECLINED;
/* Step 2: find the session route */
/* Lock the LoadBalancer
* XXX: perhaps we need the process lock here
*/
"proxy: BALANCER: lock");
return DECLINED;
}
if (runtime) {
int i, total_factor = 0;
/* We have a sticky load balancer
* Update the workers status
* so that even session routes get
* into account.
*/
/* Take into calculation only the workers that are
* not in error state or not disabled.
*/
if (PROXY_WORKER_IS_USABLE(workers)) {
}
workers++;
}
}
"proxy: BALANCER: (%s). All workers are in error state for route (%s)",
return HTTP_SERVICE_UNAVAILABLE;
}
if (!*worker) {
if (!runtime) {
"proxy: BALANCER: (%s). All workers are in error state",
return HTTP_SERVICE_UNAVAILABLE;
}
}
/* Rewrite the url from 'balancer://url'
* to the 'worker_scheme://worker_hostname[:worker_port]/url'
* This replaces the balancers fictional name with the
* real hostname of the elected worker.
*/
/* Add the session route to request notes if present */
if (route) {
}
"proxy: BALANCER (%s) worker (%s) rewritten to %s",
return access_status;
}
request_rec *r,
{
"proxy: BALANCER: lock");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* TODO: calculate the bytes transferred
* This will enable to elect the worker that has
* the lowest load.
* The bytes transferred depends on the protocol
* used, so each protocol handler should keep the
* track on that.
*/
return OK;
}
{
int i;
/* Recalculate lbfactors */
/* Special case if there is only one worker it's
* load factor will always be 1
*/
return;
}
/* Update the status entries */
}
}
/* Manages the loadfactors and member status
*/
static int balancer_handler(request_rec *r)
{
int access_status;
int i, n;
const char *name;
/* is this for us? */
return DECLINED;
if (r->method_number != M_GET)
return DECLINED;
if (r->args) {
*val++ = '\0';
*tok++ = '\0';
/*
* Special case: workers are allowed path information
*/
return access_status;
}
else
return HTTP_BAD_REQUEST;
}
}
if (ws) {
break;
}
++worker;
}
}
}
/* First set the params */
if (bsel) {
const char *val;
else
}
if (ival >= 0)
}
if (ival >= 0)
}
if (provider) {
}
}
}
if (wsel) {
const char *val;
if (bsel)
}
}
else
}
else
}
}
}
ap_set_content_type(r, "text/xml");
ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", r);
ap_rputs("<httpd:manager xmlns:httpd=\"http://httpd.apache.org\">\n", r);
ap_rputs(" <httpd:balancers>\n", r);
ap_rputs(" <httpd:balancer>\n", r);
ap_rputs(" <httpd:workers>\n", r);
ap_rputs(" <httpd:worker>\n", r);
"</httpd:scheme>\n", NULL);
"</httpd:hostname>\n", NULL);
ap_rprintf(r, " <httpd:loadfactor>%d</httpd:loadfactor>\n",
ap_rputs(" </httpd:worker>\n", r);
++worker;
}
ap_rputs(" </httpd:workers>\n", r);
ap_rputs(" </httpd:balancer>\n", r);
++balancer;
}
ap_rputs(" </httpd:balancers>\n", r);
ap_rputs("</httpd:manager>", r);
}
else {
ap_set_content_type(r, "text/html");
"<html><head><title>Balancer Manager</title></head>\n", r);
ap_rputs("<body><h1>Load Balancer Manager for ", r);
ap_rvputs(r, "<dl><dt>Server Version: ",
ap_rvputs(r, "<dt>Server Built: ",
ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
"\">", NULL);
ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
"<th>StickySession</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
"</tr>\n<tr>", r);
ap_rprintf(r, "<td>%s</td>\n",
ap_rputs("</table>\n<br />", r);
ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
"<th>Worker URL</th>"
"<th>Route</th><th>RouteRedir</th>"
"<th>Factor</th><th>Status</th>"
"</tr>\n", r);
"\">", NULL);
ap_rputs("Dis", r);
ap_rputs("Err", r);
ap_rputs("Ok", r);
else
ap_rputs("-", r);
ap_rputs("</td></tr>\n", r);
++worker;
}
ap_rputs("</table>\n", r);
++balancer;
}
ap_rputs("<hr />\n", r);
ap_rputs("<h3>Edit worker settings for ", r);
ap_rputs("<table><tr><td>Load factor:</td><td><input name=\"lf\" type=text ", r);
ap_rputs("<tr><td>Route:</td><td><input name=\"wr\" type=text ", r);
ap_rputs("\"></td><tr>\n", r);
ap_rputs("<tr><td>Route Redirect:</td><td><input name=\"rr\" type=text ", r);
ap_rputs("\"></td><tr>\n", r);
ap_rputs("<tr><td>Status:</td><td>Disabled: <input name=\"dw\" value=\"Disable\" type=radio", r);
ap_rputs(" checked", r);
ap_rputs("> | Enabled: <input name=\"dw\" value=\"Enable\" type=radio", r);
ap_rputs(" checked", r);
ap_rputs("></td><tr>\n", r);
ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
"\">\n</form>\n", NULL);
ap_rputs("<hr />\n", r);
}
else if (bsel) {
ap_rputs("<h3>Edit balancer settings for ", r);
ap_rputs("<table><tr><td>StickySession Identifier:</td><td><input name=\"ss\" type=text ", r);
ap_rputs("></td><tr>\n<tr><td>Timeout:</td><td><input name=\"tm\" type=text ", r);
ap_rputs("<tr><td>Failover Attempts:</td><td><input name=\"fa\" type=text ", r);
ap_rprintf(r, "value=\"%d\"></td></tr>\n",
bsel->max_attempts);
ap_rputs("<tr><td>LB Method:</td><td><select name=\"lm\">", r);
{
int i;
method++;
}
}
ap_rputs("</select></td></tr>\n", r);
ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
"\">\n</form>\n", NULL);
ap_rputs("<hr />\n", r);
}
ap_rputs("</body></html>\n", r);
}
return OK;
}
{
while (s) {
void *sconf = s->module_config;
int i;
/* Initialize shared scoreboard data */
balancer++;
}
s = s->next;
}
}
/*
* The idea behind the find_best_byrequests scheduler is the following:
*
* lbfactor is "how much we expect this worker to work", or "the worker's
* normalized work quota".
*
* lbstatus is "how urgent this worker has to work to fulfill its quota
* of work".
*
* We distribute each worker's work quota to the worker, and then look
* which of them needs to work most urgently (biggest lbstatus). This
* worker is then selected for work, and its lbstatus reduced by the
* total work quota we distributed to all workers. Thus the sum of all
* lbstatus does not change.(*)
*
* If some workers are disabled, the others will
* still be scheduled correctly.
*
* If a balancer is configured as follows:
*
* worker a b c d
* lbfactor 25 25 25 25
*
* And b gets disabled, the following schedule is produced:
*
* a c d a c d a c d ...
*
* Note that the above lbfactor setting is the *exact* same as:
*
* worker a b c d
* lbfactor 1 1 1 1
*
* Asymmetric configurations work as one would expect. For
* example:
*
* worker a b c d
* lbfactor 1 1 1 2
*
* would have a, b and c all handling about the same
* amount of load with d handling twice what a or b
* or c handles individually. So we could see:
*
* b a d c d a c d b d ...
*
*/
request_rec *r)
{
int i;
int total_factor = 0;
"proxy: Entering byrequests for BALANCER (%s)",
/* First try to see if we have available candidate */
/* If the worker is in error state run
* retry on that worker. It will be marked as
* operational if the retry timeout is elapsed.
* The worker might still be unusable, but we try
* anyway.
*/
if (!PROXY_WORKER_IS_USABLE(worker))
/* Take into calculation only the workers that are
* not in error state or not disabled.
*/
if (PROXY_WORKER_IS_USABLE(worker)) {
}
worker++;
}
if (mycandidate) {
mycandidate->s->elected++;
}
return mycandidate;
}
/*
* The idea behind the find_best_bytraffic scheduler is the following:
*
* We know the amount of traffic (bytes in and out) handled by each
* worker. We normalize that traffic by each workers' weight. So assuming
* a setup as below:
*
* worker a b c
* lbfactor 1 1 3
*
* the scheduler will allow worker c to handle 3 times the
* same amount of traffic, then c would be accessed 3 times as
* often as a or b. If, for example, a handled a request that
* resulted in a large i/o bytecount, then b and c would be
* chosen more often, to even things out.
*/
request_rec *r)
{
int i;
"proxy: Entering bytraffic for BALANCER (%s)",
/* First try to see if we have available candidate */
/* If the worker is in error state run
* retry on that worker. It will be marked as
* operational if the retry timeout is elapsed.
* The worker might still be unusable, but we try
* anyway.
*/
if (!PROXY_WORKER_IS_USABLE(worker))
/* Take into calculation only the workers that are
* not in error state or not disabled.
*/
if (PROXY_WORKER_IS_USABLE(worker)) {
}
}
worker++;
}
if (mycandidate) {
mycandidate->s->elected++;
}
return mycandidate;
}
/*
* How to add additional lbmethods:
* 1. Create func which determines "best" candidate worker
* (eg: find_best_bytraffic, above)
* 2. Register it as a provider.
*/
static const proxy_balancer_method byrequests =
{
"byrequests",
};
static const proxy_balancer_method bytraffic =
{
"bytraffic",
};
static void ap_proxy_balancer_register_hook(apr_pool_t *p)
{
/* Only the mpm_winnt has child init hook handler.
* make sure that we are called after the mpm
* initializes and after the mod_proxy
*/
/* manager handler */
}
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
NULL, /* command apr_table_t */
ap_proxy_balancer_register_hook /* register hooks */
};