mirror of
https://git.openldap.org/openldap/openldap.git
synced 2025-12-20 22:59:34 -05:00
ITS#9599 Add bestof tier implementation
This commit is contained in:
parent
1ca559668f
commit
84dab3f961
10 changed files with 367 additions and 2 deletions
|
|
@ -2226,6 +2226,9 @@ if test $ol_enable_balancer != no ; then
|
||||||
else
|
else
|
||||||
AC_MSG_ERROR([You need libevent 2.1 or later with DNS support to build the load balancer])
|
AC_MSG_ERROR([You need libevent 2.1 or later with DNS support to build the load balancer])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
AC_CHECK_LIB(m, pow, [BALANCER_LIBS="$BALANCER_LIBS -lm"],
|
||||||
|
[AC_MSG_ERROR([could not locate pow -lm])])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dnl ----------------------------------------------------------------
|
dnl ----------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -762,6 +762,23 @@ defaults to 0 and such backends have a slight chance of being selected even
|
||||||
when a non-zero weight backend is configured in the tier. The selection process
|
when a non-zero weight backend is configured in the tier. The selection process
|
||||||
is along the lines of
|
is along the lines of
|
||||||
.BR RFC2782 .
|
.BR RFC2782 .
|
||||||
|
.TP
|
||||||
|
.B bestof
|
||||||
|
Like with
|
||||||
|
.BI weighted ,
|
||||||
|
backends accept the
|
||||||
|
.B weight=<int>
|
||||||
|
option. Average latency multiplied by
|
||||||
|
.B weight
|
||||||
|
is measured over time. The selection process chooses 2 backends at random,
|
||||||
|
compares their weighted latencies and the backend with a better (lower) score
|
||||||
|
is tried. If the backend is not available (or is busy), the other backend is
|
||||||
|
tried, then backends are chosen in a round-robin order.
|
||||||
|
|
||||||
|
Note that unlike
|
||||||
|
.BI weighted ,
|
||||||
|
the higher the weight, the higher the "effective" latency and lower the chance
|
||||||
|
a backend is selected.
|
||||||
|
|
||||||
.SH BACKEND OPTIONS
|
.SH BACKEND OPTIONS
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ NT_OBJS = nt_svc.o ../../libraries/liblutil/slapdmsg.res
|
||||||
|
|
||||||
SRCS = backend.c bind.c config.c connection.c client.c \
|
SRCS = backend.c bind.c config.c connection.c client.c \
|
||||||
daemon.c epoch.c extended.c init.c operation.c \
|
daemon.c epoch.c extended.c init.c operation.c \
|
||||||
tier.c tier_roundrobin.c tier_weighted.c \
|
tier.c tier_roundrobin.c tier_weighted.c tier_bestof.c \
|
||||||
upstream.c libevent_support.c \
|
upstream.c libevent_support.c \
|
||||||
$(@PLAT@_SRCS)
|
$(@PLAT@_SRCS)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ struct event_base *daemon_base = NULL;
|
||||||
struct evdns_base *dnsbase;
|
struct evdns_base *dnsbase;
|
||||||
|
|
||||||
struct event *lload_timeout_event;
|
struct event *lload_timeout_event;
|
||||||
|
struct event *lload_stats_event;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* global lload statistics. Not mutex protected to preserve performance -
|
* global lload statistics. Not mutex protected to preserve performance -
|
||||||
|
|
@ -1234,6 +1235,7 @@ lloadd_daemon( struct event_base *daemon_base )
|
||||||
LloadTier *tier;
|
LloadTier *tier;
|
||||||
struct event_base *base;
|
struct event_base *base;
|
||||||
struct event *event;
|
struct event *event;
|
||||||
|
struct timeval second = { 1, 0 };
|
||||||
|
|
||||||
assert( daemon_base != NULL );
|
assert( daemon_base != NULL );
|
||||||
|
|
||||||
|
|
@ -1282,6 +1284,16 @@ lloadd_daemon( struct event_base *daemon_base )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event = event_new( daemon_base, -1, EV_TIMEOUT|EV_PERSIST,
|
||||||
|
lload_tiers_update, NULL );
|
||||||
|
if ( !event ) {
|
||||||
|
Debug( LDAP_DEBUG_ANY, "lloadd: "
|
||||||
|
"failed to allocate stats update event\n" );
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
lload_stats_event = event;
|
||||||
|
event_add( event, &second );
|
||||||
|
|
||||||
event = evtimer_new( daemon_base, operations_timeout, event_self_cbarg() );
|
event = evtimer_new( daemon_base, operations_timeout, event_self_cbarg() );
|
||||||
if ( !event ) {
|
if ( !event ) {
|
||||||
Debug( LDAP_DEBUG_ANY, "lloadd: "
|
Debug( LDAP_DEBUG_ANY, "lloadd: "
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,7 @@ struct lload_tier_type {
|
||||||
LloadTierConfigCb *tier_config;
|
LloadTierConfigCb *tier_config;
|
||||||
LloadTierBackendConfigCb *tier_backend_config;
|
LloadTierBackendConfigCb *tier_backend_config;
|
||||||
LloadTierCb *tier_startup;
|
LloadTierCb *tier_startup;
|
||||||
|
LloadTierCb *tier_update;
|
||||||
LloadTierResetCb *tier_reset;
|
LloadTierResetCb *tier_reset;
|
||||||
LloadTierCb *tier_destroy;
|
LloadTierCb *tier_destroy;
|
||||||
|
|
||||||
|
|
@ -308,6 +309,7 @@ struct LloadBackend {
|
||||||
|
|
||||||
LloadTier *b_tier;
|
LloadTier *b_tier;
|
||||||
|
|
||||||
|
time_t b_last_update;
|
||||||
uintptr_t b_fitness;
|
uintptr_t b_fitness;
|
||||||
int b_weight;
|
int b_weight;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,7 @@ LDAP_SLAPD_F (int) tier_reset( LloadTier *tier, int shutdown );
|
||||||
LDAP_SLAPD_F (int) tier_destroy( LloadTier *tier );
|
LDAP_SLAPD_F (int) tier_destroy( LloadTier *tier );
|
||||||
LDAP_SLAPD_F (void) lload_tiers_shutdown( void );
|
LDAP_SLAPD_F (void) lload_tiers_shutdown( void );
|
||||||
LDAP_SLAPD_F (void) lload_tiers_reset( int shutdown );
|
LDAP_SLAPD_F (void) lload_tiers_reset( int shutdown );
|
||||||
|
LDAP_SLAPD_F (void) lload_tiers_update( evutil_socket_t s, short what, void *arg );
|
||||||
LDAP_SLAPD_F (void) lload_tiers_destroy( void );
|
LDAP_SLAPD_F (void) lload_tiers_destroy( void );
|
||||||
LDAP_SLAPD_F (struct lload_tier_type *) lload_tier_find( char *type );
|
LDAP_SLAPD_F (struct lload_tier_type *) lload_tier_find( char *type );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,21 @@ lload_tiers_reset( int shutdown )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
lload_tiers_update( evutil_socket_t s, short what, void *arg )
|
||||||
|
{
|
||||||
|
LloadTier *tier;
|
||||||
|
|
||||||
|
LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
|
||||||
|
if ( tier->t_type.tier_update ) {
|
||||||
|
tier->t_type.tier_update( tier );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern struct lload_tier_type roundrobin_tier;
|
extern struct lload_tier_type roundrobin_tier;
|
||||||
extern struct lload_tier_type weighted_tier;
|
extern struct lload_tier_type weighted_tier;
|
||||||
|
extern struct lload_tier_type bestof_tier;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
char *name;
|
char *name;
|
||||||
|
|
@ -126,6 +139,7 @@ struct {
|
||||||
} tier_types[] = {
|
} tier_types[] = {
|
||||||
{ "roundrobin", &roundrobin_tier },
|
{ "roundrobin", &roundrobin_tier },
|
||||||
{ "weighted", &weighted_tier },
|
{ "weighted", &weighted_tier },
|
||||||
|
{ "bestof", &bestof_tier },
|
||||||
|
|
||||||
{ NULL }
|
{ NULL }
|
||||||
};
|
};
|
||||||
|
|
|
||||||
315
servers/lloadd/tier_bestof.c
Normal file
315
servers/lloadd/tier_bestof.c
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
/* $OpenLDAP$ */
|
||||||
|
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||||
|
*
|
||||||
|
* Copyright 1998-2019 The OpenLDAP Foundation.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted only as authorized by the OpenLDAP
|
||||||
|
* Public License.
|
||||||
|
*
|
||||||
|
* A copy of this license is available in the file LICENSE in the
|
||||||
|
* top-level directory of the distribution or, alternatively, at
|
||||||
|
* <http://www.OpenLDAP.org/license.html>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "portable.h"
|
||||||
|
|
||||||
|
#include <ac/string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "lload.h"
|
||||||
|
#include "lutil.h"
|
||||||
|
|
||||||
|
static LloadTierInit bestof_init;
|
||||||
|
static LloadTierBackendConfigCb bestof_backend_options;
|
||||||
|
static LloadTierBackendCb bestof_add_backend;
|
||||||
|
static LloadTierBackendCb bestof_remove_backend;
|
||||||
|
static LloadTierSelect bestof_select;
|
||||||
|
|
||||||
|
struct lload_tier_type bestof_tier;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Linear Congruential Generator - we don't need
|
||||||
|
* high quality randomness, and we don't want to
|
||||||
|
* interfere with anyone else's use of srand().
|
||||||
|
*
|
||||||
|
* The PRNG here cycles thru 941,955 numbers.
|
||||||
|
*/
|
||||||
|
static float bestof_seed;
|
||||||
|
|
||||||
|
static void
|
||||||
|
bestof_srand( int seed )
|
||||||
|
{
|
||||||
|
bestof_seed = (float)seed / (float)RAND_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float
|
||||||
|
bestof_rand()
|
||||||
|
{
|
||||||
|
float val = 9821.0 * bestof_seed + .211327;
|
||||||
|
bestof_seed = val - (int)val;
|
||||||
|
return bestof_seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
bestof_cmp( const void *left, const void *right )
|
||||||
|
{
|
||||||
|
const LloadBackend *l = left;
|
||||||
|
const LloadBackend *r = right;
|
||||||
|
|
||||||
|
return l->b_fitness - r->b_fitness;
|
||||||
|
}
|
||||||
|
|
||||||
|
LloadTier *
|
||||||
|
bestof_init( void )
|
||||||
|
{
|
||||||
|
LloadTier *tier;
|
||||||
|
|
||||||
|
tier = ch_calloc( 1, sizeof(LloadTier) );
|
||||||
|
|
||||||
|
tier->t_type = bestof_tier;
|
||||||
|
ldap_pvt_thread_mutex_init( &tier->t_mutex );
|
||||||
|
LDAP_CIRCLEQ_INIT( &tier->t_backends );
|
||||||
|
|
||||||
|
bestof_srand( rand() );
|
||||||
|
|
||||||
|
return tier;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
bestof_add_backend( LloadTier *tier, LloadBackend *b )
|
||||||
|
{
|
||||||
|
assert( b->b_tier == tier );
|
||||||
|
|
||||||
|
LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, b, b_next );
|
||||||
|
if ( !tier->t_private ) {
|
||||||
|
tier->t_private = b;
|
||||||
|
}
|
||||||
|
tier->t_nbackends++;
|
||||||
|
return LDAP_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
bestof_remove_backend( LloadTier *tier, LloadBackend *b )
|
||||||
|
{
|
||||||
|
LloadBackend *next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
|
||||||
|
|
||||||
|
assert_locked( &tier->t_mutex );
|
||||||
|
assert_locked( &b->b_mutex );
|
||||||
|
|
||||||
|
assert( b->b_tier == tier );
|
||||||
|
assert( tier->t_private );
|
||||||
|
|
||||||
|
LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
|
||||||
|
LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
|
||||||
|
|
||||||
|
if ( b == next ) {
|
||||||
|
tier->t_private = NULL;
|
||||||
|
} else {
|
||||||
|
tier->t_private = next;
|
||||||
|
}
|
||||||
|
tier->t_nbackends--;
|
||||||
|
|
||||||
|
return LDAP_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
bestof_backend_options( LloadTier *tier, LloadBackend *b, char *arg )
|
||||||
|
{
|
||||||
|
struct berval weight = BER_BVC("weight=");
|
||||||
|
unsigned long l;
|
||||||
|
|
||||||
|
if ( !strncasecmp( arg, weight.bv_val, weight.bv_len ) ) {
|
||||||
|
if ( lutil_atoulx( &l, &arg[weight.bv_len], 0 ) != 0 ) {
|
||||||
|
Debug( LDAP_DEBUG_ANY, "bestof_backend_options: "
|
||||||
|
"cannot parse %s as weight\n",
|
||||||
|
arg );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
b->b_weight = l;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
connection_collect_stats( LloadConnection *c, void *arg )
|
||||||
|
{
|
||||||
|
uintptr_t count, diff, *stats = arg;
|
||||||
|
|
||||||
|
count = __atomic_exchange_n(
|
||||||
|
&( c )->c_operation_count, 0, __ATOMIC_RELAXED );
|
||||||
|
diff = __atomic_exchange_n( &( c )->c_operation_time, 0, __ATOMIC_RELAXED );
|
||||||
|
|
||||||
|
stats[0] += count;
|
||||||
|
stats[1] += diff;
|
||||||
|
|
||||||
|
return LDAP_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
bestof_update( LloadTier *tier )
|
||||||
|
{
|
||||||
|
LloadBackend *b, *first, *next;
|
||||||
|
time_t now = slap_get_time();
|
||||||
|
|
||||||
|
checked_lock( &tier->t_mutex );
|
||||||
|
first = b = tier->t_private;
|
||||||
|
checked_unlock( &tier->t_mutex );
|
||||||
|
|
||||||
|
if ( !first ) return LDAP_SUCCESS;
|
||||||
|
|
||||||
|
do {
|
||||||
|
int steps;
|
||||||
|
checked_lock( &b->b_mutex );
|
||||||
|
|
||||||
|
steps = now - b->b_last_update;
|
||||||
|
if ( b->b_weight && steps > 0 ) {
|
||||||
|
uintptr_t stats[2] = { 0, 0 };
|
||||||
|
float factor = 1;
|
||||||
|
|
||||||
|
connections_walk(
|
||||||
|
&b->b_mutex, &b->b_conns, connection_collect_stats, stats );
|
||||||
|
|
||||||
|
/* Smear values over time - rolling average */
|
||||||
|
if ( stats[0] ) {
|
||||||
|
float fitness = b->b_weight * stats[1];
|
||||||
|
|
||||||
|
/* Stretch factor accordingly favouring the latest value */
|
||||||
|
if ( steps > 10 ) {
|
||||||
|
factor = 0; /* No recent data */
|
||||||
|
} else if ( steps > 1 ) {
|
||||||
|
factor =
|
||||||
|
1 / ( pow( ( 1 / (float)factor ) + 1, steps ) - 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
b->b_fitness = ( factor * b->b_fitness + fitness / stats[0] ) /
|
||||||
|
( factor + 1 );
|
||||||
|
b->b_last_update = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
|
||||||
|
checked_unlock( &b->b_mutex );
|
||||||
|
b = next;
|
||||||
|
} while ( b != first );
|
||||||
|
|
||||||
|
return LDAP_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
bestof_select(
|
||||||
|
LloadTier *tier,
|
||||||
|
LloadOperation *op,
|
||||||
|
LloadConnection **cp,
|
||||||
|
int *res,
|
||||||
|
char **message )
|
||||||
|
{
|
||||||
|
LloadBackend *first, *next, *b, *b0, *b1;
|
||||||
|
int result = 0, rc = 0, n = tier->t_nbackends;
|
||||||
|
int i0, i1, i = 0;
|
||||||
|
|
||||||
|
checked_lock( &tier->t_mutex );
|
||||||
|
first = b0 = b = tier->t_private;
|
||||||
|
checked_unlock( &tier->t_mutex );
|
||||||
|
|
||||||
|
if ( !first ) return rc;
|
||||||
|
|
||||||
|
if ( tier->t_nbackends == 1 ) {
|
||||||
|
goto fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pick two backend indices at random */
|
||||||
|
i0 = bestof_rand() * n;
|
||||||
|
i1 = bestof_rand() * ( n - 1 );
|
||||||
|
if ( i1 >= i0 ) {
|
||||||
|
i1 += 1;
|
||||||
|
} else {
|
||||||
|
int tmp = i0;
|
||||||
|
i0 = i1;
|
||||||
|
i1 = tmp;
|
||||||
|
}
|
||||||
|
assert( i0 < i1 );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: use a static array in t_private so we don't have to do any of
|
||||||
|
* this
|
||||||
|
*/
|
||||||
|
for ( i = 0; i < i1; i++ ) {
|
||||||
|
if ( i == i0 ) {
|
||||||
|
b0 = b;
|
||||||
|
}
|
||||||
|
checked_lock( &b->b_mutex );
|
||||||
|
next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
|
||||||
|
checked_unlock( &b->b_mutex );
|
||||||
|
b = next;
|
||||||
|
}
|
||||||
|
b1 = b;
|
||||||
|
assert( b0 != b1 );
|
||||||
|
|
||||||
|
if ( bestof_cmp( b0, b1 ) < 0 ) {
|
||||||
|
checked_lock( &b0->b_mutex );
|
||||||
|
result = backend_select( b0, op, cp, res, message );
|
||||||
|
checked_unlock( &b0->b_mutex );
|
||||||
|
} else {
|
||||||
|
checked_lock( &b1->b_mutex );
|
||||||
|
result = backend_select( b1, op, cp, res, message );
|
||||||
|
checked_unlock( &b1->b_mutex );
|
||||||
|
}
|
||||||
|
|
||||||
|
rc |= result;
|
||||||
|
if ( result && *cp ) {
|
||||||
|
checked_lock( &tier->t_mutex );
|
||||||
|
tier->t_private = LDAP_CIRCLEQ_LOOP_NEXT(
|
||||||
|
&tier->t_backends, (*cp)->c_backend, b_next );
|
||||||
|
checked_unlock( &tier->t_mutex );
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preferred backends deemed unusable, do a round robin from scratch */
|
||||||
|
b = first;
|
||||||
|
fallback:
|
||||||
|
do {
|
||||||
|
checked_lock( &b->b_mutex );
|
||||||
|
next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
|
||||||
|
|
||||||
|
rc = backend_select( b, op, cp, res, message );
|
||||||
|
checked_unlock( &b->b_mutex );
|
||||||
|
|
||||||
|
if ( rc && *cp ) {
|
||||||
|
/*
|
||||||
|
* Round-robin step:
|
||||||
|
* Rotate the queue to put this backend at the end. The race here
|
||||||
|
* is acceptable.
|
||||||
|
*/
|
||||||
|
checked_lock( &tier->t_mutex );
|
||||||
|
tier->t_private = next;
|
||||||
|
checked_unlock( &tier->t_mutex );
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
b = next;
|
||||||
|
} while ( b != first );
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lload_tier_type bestof_tier = {
|
||||||
|
.tier_name = "bestof",
|
||||||
|
|
||||||
|
.tier_init = bestof_init,
|
||||||
|
.tier_startup = tier_startup,
|
||||||
|
.tier_update = bestof_update,
|
||||||
|
.tier_reset = tier_reset,
|
||||||
|
.tier_destroy = tier_destroy,
|
||||||
|
|
||||||
|
.tier_oc = BER_BVC("olcBkLloadTierConfig"),
|
||||||
|
.tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
|
||||||
|
|
||||||
|
.tier_add_backend = bestof_add_backend,
|
||||||
|
.tier_remove_backend = bestof_remove_backend,
|
||||||
|
|
||||||
|
.tier_select = bestof_select,
|
||||||
|
};
|
||||||
|
|
@ -69,6 +69,7 @@ roundrobin_remove_backend( LloadTier *tier, LloadBackend *b )
|
||||||
tier->t_private = NULL;
|
tier->t_private = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tier->t_nbackends--;
|
||||||
return LDAP_SUCCESS;
|
return LDAP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ bindconf
|
||||||
tier roundrobin
|
tier roundrobin
|
||||||
# empty tier
|
# empty tier
|
||||||
|
|
||||||
tier weighted
|
tier bestof
|
||||||
backend-server uri=@URI2@
|
backend-server uri=@URI2@
|
||||||
numconns=3
|
numconns=3
|
||||||
bindconns=3
|
bindconns=3
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue