Commit 2502e03a authored by Poul-Henning Kamp's avatar Poul-Henning Kamp Committed by Tollef Fog Heen

Implement a consistent retry policy in the random/client/hash director:

If the first (policy-chosen) backend fails to get us a connection,
retry a random backend (still according to their weight) until
retries are exhausted.

Kristian sent a proof of concept patch, I just cleaned it up and made
it compile.

Thanks to:	Kristian

Fixes	#977
parent 693a89be
/*- /*-
* Copyright (c) 2006 Verdens Gang AS * Copyright (c) 2006 Verdens Gang AS
* Copyright (c) 2006-2010 Varnish Software AS * Copyright (c) 2006-2011 Varnish Software AS
* All rights reserved. * All rights reserved.
* *
* Author: Poul-Henning Kamp <phk@phk.freebsd.dk> * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
...@@ -26,17 +26,22 @@ ...@@ -26,17 +26,22 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE. * SUCH DAMAGE.
* *
* This code is shared between the random and hash directors, because they * This code is shared between the random, client and hash directors, because
* share the same properties and most of the same selection logic. * they share the same properties and most of the same selection logic.
* *
* The random director picks a backend on random, according to weight, * The random director picks a backend on random.
* from the healty subset of backends. *
* The hash director picks based on the hash from vcl_hash{}
*
* The client director picks based on client identity or IP-address
*
* In all cases, the choice is by weight of the healthy subset of
* configured backends.
*
* Failures to get a connection are retried, here all three policies
* fall back to a deterministically random choice, by weight in the
* healthy subset.
* *
* The hash director first tries to locate the "canonical" backend from
* the full set, according to weight, and if it is healthy selects it.
* If the canonical backend is not healthy, we pick a backend according
* to weight from the healthy subset. That way only traffic to unhealthy
* backends gets redistributed.
*/ */
#include "config.h" #include "config.h"
...@@ -46,6 +51,7 @@ ...@@ -46,6 +51,7 @@
#include <stdio.h> #include <stdio.h>
#include <errno.h> #include <errno.h>
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
...@@ -77,114 +83,117 @@ struct vdi_random { ...@@ -77,114 +83,117 @@ struct vdi_random {
unsigned nhosts; unsigned nhosts;
}; };
static struct vbc * /*
vdi_random_getfd(const struct director *d, struct sess *sp) * Applies sha256 using the given context and input/length, and returns
* a double in the range [0...1[ based on the hash.
*/
static double
vdi_random_sha(const char *input, ssize_t len)
{ {
int i, k;
struct vdi_random *vs;
double r, s1;
unsigned u = 0;
struct vbc *vbe;
struct director *d2;
struct SHA256Context ctx; struct SHA256Context ctx;
uint8_t sign[SHA256_LEN], *hp = NULL; uint8_t sign[SHA256_LEN];
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC); AN(input);
CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC); SHA256_Init(&ctx);
CAST_OBJ_NOTNULL(vs, d->priv, VDI_RANDOM_MAGIC); SHA256_Update(&ctx, input, len);
SHA256_Final(sign, &ctx);
return (vle32dec(sign) / exp2(32));
}
/*
* Sets up the initial seed for picking a backend according to policy.
*/
static double
vdi_random_init_seed(const struct vdi_random *vs, const struct sess *sp)
{
const char *p;
double retval;
if (vs->criteria == c_client) { switch (vs->criteria) {
/* case c_client:
* Hash the client IP# ascii representation, rather than
* rely on the raw IP# being a good hash distributor, since
* experience shows this not to be the case.
* We do not hash the port number, to make everybody behind
* a given NAT gateway fetch from the same backend.
*/
SHA256_Init(&ctx);
AN(sp->addr);
if (sp->client_identity != NULL) if (sp->client_identity != NULL)
SHA256_Update(&ctx, sp->client_identity, p = sp->client_identity;
strlen(sp->client_identity));
else else
SHA256_Update(&ctx, sp->addr, strlen(sp->addr)); p = sp->addr;
SHA256_Final(sign, &ctx); retval = vdi_random_sha(p, strlen(p));
hp = sign; break;
case c_hash:
AN(sp->digest);
retval = vle32dec(sp->digest) / exp2(32);
break;
case c_random:
default:
retval = random() / exp2(31);
break;
} }
if (vs->criteria == c_hash) { return (retval);
/* }
* Reuse the hash-string, the objective here is to fetch the
* same object on the same backend all the time /*
*/ * Find the healthy backend corresponding to the weight r [0...1[
hp = sp->digest; */
static struct vbc *
vdi_random_pick_one(struct sess *sp, const struct vdi_random *vs, double r)
{
double w[vs->nhosts];
int i;
double s1;
assert(r >= 0.0 && r < 1.0);
memset(w, 0, sizeof w);
/* Sum up the weights of healty backends */
s1 = 0.0;
for (i = 0; i < vs->nhosts; i++) {
if (VDI_Healthy(vs->hosts[i].backend, sp))
w[i] = vs->hosts[i].weight;
s1 += w[i];
} }
/* if (s1 == 0.0)
* If we are hashing, first try to hit our "canonical backend" return (NULL);
* If that fails, we fall through, and select a weighted backend
* amongst the healthy set. r *= s1;
*/ s1 = 0.0;
if (vs->criteria != c_random) { for (i = 0; i < vs->nhosts; i++) {
AN(hp); s1 += w[i];
u = vle32dec(hp); if (r < s1)
r = u / 4294967296.0; return(VDI_GetFd(vs->hosts[i].backend, sp));
assert(r >= 0.0 && r < 1.0);
r *= vs->tot_weight;
s1 = 0.0;
for (i = 0; i < vs->nhosts; i++) {
s1 += vs->hosts[i].weight;
if (r >= s1)
continue;
d2 = vs->hosts[i].backend;
if (!VDI_Healthy(d2, sp))
break;
vbe = VDI_GetFd(d2, sp);
if (vbe != NULL)
return (vbe);
break;
}
} }
return (NULL);
}
for (k = 0; k < vs->retries; ) { /*
/* Sum up the weights of healty backends */ * Try the specified number of times to get a backend.
s1 = 0.0; * First one according to policy, after that, deterministically
for (i = 0; i < vs->nhosts; i++) { * random by rehashing the key.
d2 = vs->hosts[i].backend; */
/* XXX: cache result of healty to avoid double work */ static struct vbc *
if (VDI_Healthy(d2, sp)) vdi_random_getfd(const struct director *d, struct sess *sp)
s1 += vs->hosts[i].weight; {
} int k;
struct vdi_random *vs;
if (s1 == 0.0) double r;
return (NULL); struct vbc *vbe;
if (vs->criteria != c_random) { CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
r = u / 4294967296.0; CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC);
} else { CAST_OBJ_NOTNULL(vs, d->priv, VDI_RANDOM_MAGIC);
/* Pick a random threshold in that interval */
r = random() / 2147483648.0; /* 2^31 */ r = vdi_random_init_seed(vs, sp);
}
assert(r >= 0.0 && r < 1.0); for (k = 0; k < vs->retries; k++) {
r *= s1; vbe = vdi_random_pick_one(sp, vs, r);
if (vbe != NULL)
s1 = 0.0; return (vbe);
for (i = 0; i < vs->nhosts; i++) { r = vdi_random_sha((void *)&r, sizeof(r));
d2 = vs->hosts[i].backend;
if (!VDI_Healthy(d2, sp))
continue;
s1 += vs->hosts[i].weight;
if (r >= s1)
continue;
vbe = VDI_GetFd(d2, sp);
if (vbe != NULL)
return (vbe);
break;
}
k++;
} }
return (NULL); return (NULL);
} }
/*
* Healthy if just a single backend is...
*/
static unsigned static unsigned
vdi_random_healthy(const struct director *d, const struct sess *sp) vdi_random_healthy(const struct director *d, const struct sess *sp)
{ {
......
varnishtest "Test proper fallbacks of client director" varnishtest "Test proper fallbacks of client director"
server s1 -repeat 1 { server s1 {
rxreq
txresp -status 200
accept
rxreq rxreq
txresp -status 200 txresp -status 200
} -start } -start
...@@ -12,17 +15,32 @@ varnish v1 -vcl+backend { ...@@ -12,17 +15,32 @@ varnish v1 -vcl+backend {
{ .backend = { .host = "${bad_ip}"; .port = "9090"; } .weight = 1; } { .backend = { .host = "${bad_ip}"; .port = "9090"; } .weight = 1; }
{ .backend = s1; .weight = 1;} { .backend = s1; .weight = 1;}
} }
director bar client{
.retries = 1;
{ .backend = { .host = "${bad_ip}"; .port = "9090"; } .weight = 1; }
{ .backend = s1; .weight = 1;}
}
sub vcl_recv { sub vcl_recv {
set req.backend = foo; if (req.url ~ "/one") {
set client.identity = "44.452"; set req.backend = foo;
} else {
set req.backend = bar;
}
# Carefully chosen seed that'll give us bad backend on
# first try and good on second.
set client.identity = "1.4";
return (pass); return (pass);
} }
} -start } -start
client c1 { client c1 {
txreq txreq -url "/one"
rxresp rxresp
expect resp.status == 200 expect resp.status == 200
txreq -url "/two"
rxresp
expect resp.status == 503
} -run } -run
varnish v1 -expect backend_fail == 1 varnish v1 -expect backend_fail == 2
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment