Sticky Load Balancing with Varnish
Niklas Norberg
niklas.norberg at bahnhof.se
Wed Apr 14 14:28:11 CEST 2010
Hi,
last week I started writing a sticky load balancer.
At that time (2.0.6) I lacked that req.* wasn't available in vcl_deliver
so I had to use some global C-variables to share data from vcl_recv to
vcl_deliver. Because of this I had to add some thread guard C-code and
all together it worked but I wasn't fully satisfied. The guard code
contained sleep in order to wait for other thread so that just one
thread at a time would go through this "set/get" cycle.
This week I discovered that 2.1.0 was released and that:
- req.* is now available in vcl_deliver.
So I rewrote it, just by removing the thread C-code, and here it is.
I've tested it with JMeter and it balances correct (i.e. according to
the defined weights).
So I also vote for keeping this as a documented configuration rather
than a built-in feature. Unless the planned sticky load balancing will
have something above the rudimentary.
Comments?
With kind regards,
Niklas Norberg
LBsubs.vcl (which I also atach for row break sanity):
C{
// Obs This string is also used hard coded in pure VCL code
static const char VARNISH_LB_COOKIE_NAME[] = "VARNISH_LB=";
// Let STICKY balance everything(/) for four hours a time:
static const char VARNISH_LB_ENDING[] =
"; path=/; Max-Age=14400; Comment=Varnish Sticky Load
Balancing Cookie";
static const int CANDIDATE_CNT = 3;
static const int MAX_LEN_LB_ID = 2; // covers 1-99
// Load balancing weights:
// The first value should be the sum of the others.
static const int LB_FACTOR[4] = {10, 7, 1, 2};
// The first is not used, just to keep indexes same in the arrays.
static int lbStatus[4] = {-1, 0, 0, 0};
static int lastCandidate = 1;
/**
Load Balancing according to Request Counting Algorithm, see:
http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html#requests
or write your own.
*/
int getCandidate4LB() {
/*
for each worker in workers
worker lbstatus += worker lbfactor
total factor += worker lbfactor
if worker lbstatus > candidate lbstatus
candidate = worker
candidate lbstatus -= total factor
*/
int worker=1;
// This local copy is really overkill
int candidate = lastCandidate;
while ( worker<=CANDIDATE_CNT ) {
lbStatus[worker] += LB_FACTOR[worker];
if ( lbStatus[worker] > lbStatus[candidate] )
candidate = worker;
worker++;
}
//int totalFactor = LB_FACTOR[0];
//lbStatus[candidate] -= totalFactor;
lbStatus[candidate] -= LB_FACTOR[0];
lastCandidate = candidate;
return candidate;
}
}C
/**
Check if a LB cookie is present.
If it is
- Copy the LB cookie value to a marker.
- set backend
- unset marker
If it isn't
- find candidate for LB and set value to marker
- set backend
- don't unset marker so a cookie can be set in vcl_deliver.
*/
sub recv_loadBalancingOnStickyCookie {
if (req.http.Cookie ~ "VARNISH_LB=") {
set req.http.StickyVarnish =
regsub( req.http.Cookie, "^.*?VARNISH_LB=([^;]*);*.*$",
"\1" );
call chooseBackend;
unset req.http.StickyVarnish;
} else {
call findCandidate4LB;
call chooseBackend;
}
}
/**
Get candidate from some fancy smanchy algorithm.
Set value to marker.
*/
sub findCandidate4LB {
// set req.http.StickyVarnish = "X";:
C{
// get index for backend
int idxBackend = getCandidate4LB();
// Store index in a header marker so we can use this later
char strBackendIndex[MAX_LEN_LB_ID + 1];
sprintf(strBackendIndex, "%d", idxBackend);
VRT_SetHdr(sp, HDR_REQ, "\016StickyVarnish:", strBackendIndex,
vrt_magic_string_end);
}C
// C{
// //Debug test:
// int i=1;
// while(i<=100) {
// syslog( LOG_INFO, "getCandidate4LB(): %d",
getCandidate4LB() );
// i++;
// }
// }C
}
/**
Choose backend (via director) from the marker ("StickyVarnish").
Change this as necessary.
Directors and Backends are defined elsewhere.
*/
sub chooseBackend {
// if else in weight order, switch statements would have been better
if (req.http.StickyVarnish == "1") {
set req.backend = Backends1;
} else
if (req.http.StickyVarnish == "3") {
set req.backend = Backends3;
} else
if (req.http.StickyVarnish == "2") {
set req.backend = Backends2;
}
}
/**
Set the Sticky Varnish Load Balancing Cookie as:
"VARNISH_LB=X; ..."
*/
sub deliver_setLBCookie {
if (req.http.StickyVarnish) {
C{
// Also send cookie from backend if any
char* existing_set_cookie = VRT_GetHdr(sp, HDR_OBJ,
"\013Set-Cookie:");
int len;
if (existing_set_cookie == NULL)
len = 0;
else
len = strlen(existing_set_cookie) + 1; // + 1 for "\r"
len += strlen(VARNISH_LB_COOKIE_NAME);
len += MAX_LEN_LB_ID;
len += strlen(VARNISH_LB_ENDING);
char set_cookie[len + 1];
set_cookie[0] = '\0';
if (existing_set_cookie != 0) {
strcat( set_cookie, existing_set_cookie );
strcat( set_cookie, "\r" );
}
strcat( set_cookie, VARNISH_LB_COOKIE_NAME );
char* strBackend = VRT_GetHdr(sp, HDR_REQ,
"\016StickyVarnish:");
strcat( set_cookie, strBackend );
strcat( set_cookie, VARNISH_LB_ENDING );
// Send cookie(s)
VRT_SetHdr(sp, HDR_RESP, "\013Set-Cookie:", set_cookie,
vrt_magic_string_end);
}C
}
}
/**
Call the LB subs from the subs:
- vcl_recv
- vcl_deliver
*/
/*
sub vcl_recv {
call recv_loadBalancingOnStickyCookie;
}
sub vcl_deliver {
call deliver_setLBCookie;
}
*/
/**
If directors are defined as below we can in practice:
Load balance to a specific backend by choosing a director
and still have a fallback function.
Risk for wrong backend = (1+1)/4294967295 = 5 * 10^-10,
a price I'm willing to take for this fallback function.
This way I can restart the backends and still keep the
load balancing intact when all is up again.
*/
/*
director Backends1 random {
{ .backend = www1; .weight = 4294967295;}
{ .backend = www2; .weight = 1;}
{ .backend = www3; .weight = 1;}
}
director Backends2 random {
{ .backend = www1; .weight = 1;}
{ .backend = www2; .weight = 4294967295;}
{ .backend = www3; .weight = 1;}
}
director Backends3 random {
{ .backend = www1; .weight = 1;}
{ .backend = www2; .weight = 1;}
{ .backend = www3; .weight = 4294967295;}
}
*/
-------------- next part --------------
C{
// Obs This string is also used hard coded in pure VCL code
static const char VARNISH_LB_COOKIE_NAME[] = "VARNISH_LB=";
// Let STICKY balance everything(/) for four hours a time:
static const char VARNISH_LB_ENDING[] =
"; path=/; Max-Age=14400; Comment=Varnish Sticky Load Balancing Cookie";
static const int CANDIDATE_CNT = 3;
static const int MAX_LEN_LB_ID = 2; // covers 1-99
// Load balancing weights:
// The first value should be the sum of the others.
static const int LB_FACTOR[4] = {10, 7, 1, 2};
// The first is not used, just to keep indexes same in the arrays.
static int lbStatus[4] = {-1, 0, 0, 0};
static int lastCandidate = 1;
/**
Load Balancing according to Request Counting Algorithm, see:
http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html#requests
or write your own.
*/
int getCandidate4LB() {
/*
for each worker in workers
worker lbstatus += worker lbfactor
total factor += worker lbfactor
if worker lbstatus > candidate lbstatus
candidate = worker
candidate lbstatus -= total factor
*/
int worker=1;
// This local copy is really overkill
int candidate = lastCandidate;
while ( worker<=CANDIDATE_CNT ) {
lbStatus[worker] += LB_FACTOR[worker];
if ( lbStatus[worker] > lbStatus[candidate] )
candidate = worker;
worker++;
}
//int totalFactor = LB_FACTOR[0];
//lbStatus[candidate] -= totalFactor;
lbStatus[candidate] -= LB_FACTOR[0];
lastCandidate = candidate;
return candidate;
}
}C
/**
Check if a LB cookie is present.
If it is
- Copy the LB cookie value to a marker.
- set backend
- unset marker
If it isn't
- find candidate for LB and set value to marker
- set backend
- don't unset marker so a cookie can be set in vcl_deliver.
*/
sub recv_loadBalancingOnStickyCookie {
if (req.http.Cookie ~ "VARNISH_LB=") {
set req.http.StickyVarnish =
regsub( req.http.Cookie, "^.*?VARNISH_LB=([^;]*);*.*$", "\1" );
call chooseBackend;
unset req.http.StickyVarnish;
} else {
call findCandidate4LB;
call chooseBackend;
}
}
/**
Get candidate from some fancy smanchy algorithm.
Set value to marker.
*/
sub findCandidate4LB {
// set req.http.StickyVarnish = "X";:
C{
// get index for backend
int idxBackend = getCandidate4LB();
// Store index in a header marker so we can use this later
char strBackendIndex[MAX_LEN_LB_ID + 1];
sprintf(strBackendIndex, "%d", idxBackend);
VRT_SetHdr(sp, HDR_REQ, "\016StickyVarnish:", strBackendIndex, vrt_magic_string_end);
}C
// C{
// //Debug test:
// int i=1;
// while(i<=100) {
// syslog( LOG_INFO, "getCandidate4LB(): %d", getCandidate4LB() );
// i++;
// }
// }C
}
/**
Choose backend (via director) from the marker ("StickyVarnish").
Change this as necessary.
Directors and Backends are defined elsewhere.
*/
sub chooseBackend {
// if else in weight order, switch statements would have been better
if (req.http.StickyVarnish == "1") {
set req.backend = Backends1;
} else
if (req.http.StickyVarnish == "3") {
set req.backend = Backends3;
} else
if (req.http.StickyVarnish == "2") {
set req.backend = Backends2;
}
}
/**
Set the Sticky Varnish Load Balancing Cookie as:
"VARNISH_LB=X; ..."
*/
sub deliver_setLBCookie {
if (req.http.StickyVarnish) {
C{
// Also send cookie from backend if any
char* existing_set_cookie = VRT_GetHdr(sp, HDR_OBJ, "\013Set-Cookie:");
int len;
if (existing_set_cookie == NULL)
len = 0;
else
len = strlen(existing_set_cookie) + 1; // + 1 for "\r"
len += strlen(VARNISH_LB_COOKIE_NAME);
len += MAX_LEN_LB_ID;
len += strlen(VARNISH_LB_ENDING);
char set_cookie[len + 1];
set_cookie[0] = '\0';
if (existing_set_cookie != 0) {
strcat( set_cookie, existing_set_cookie );
strcat( set_cookie, "\r" );
}
strcat( set_cookie, VARNISH_LB_COOKIE_NAME );
char* strBackend = VRT_GetHdr(sp, HDR_REQ, "\016StickyVarnish:");
strcat( set_cookie, strBackend );
strcat( set_cookie, VARNISH_LB_ENDING );
// Send cookie(s)
VRT_SetHdr(sp, HDR_RESP, "\013Set-Cookie:", set_cookie, vrt_magic_string_end);
}C
}
}
/**
Call the LB subs from the subs:
- vcl_recv
- vcl_deliver
*/
/*
sub vcl_recv {
call recv_loadBalancingOnStickyCookie;
}
sub vcl_deliver {
call deliver_setLBCookie;
}
*/
/**
If directors are defined as below we can in practice:
Load balance to a specific backend by choosing a director
and still have a fallback function.
Risk for wrong backend = (1+1)/4294967295 = 5 * 10^-10,
a price I'm willing to take for this fallback function.
This way I can restart the backends and still keep the
load balancing intact when all is up again.
*/
/*
director Backends1 random {
{ .backend = www1; .weight = 4294967295;}
{ .backend = www2; .weight = 1;}
{ .backend = www3; .weight = 1;}
}
director Backends2 random {
{ .backend = www1; .weight = 1;}
{ .backend = www2; .weight = 4294967295;}
{ .backend = www3; .weight = 1;}
}
director Backends3 random {
{ .backend = www1; .weight = 1;}
{ .backend = www2; .weight = 1;}
{ .backend = www3; .weight = 4294967295;}
}
*/
More information about the varnish-misc
mailing list