MINOR: lbprm: implement true "sticky" balance algo

As previously mentioned in cd352c0db ("MINOR: log/balance: rename
"log-sticky" to "sticky""), let's define a sticky algorithm that may be
used from any protocol. Sticky algorithm sticks on the same server as
long as it remains available.

The documentation was updated accordingly.
This commit is contained in:
Aurelien DARRAGON 2024-03-28 17:24:53 +01:00
parent d0692d7019
commit 9aea6df81f
9 changed files with 276 additions and 7 deletions

View File

@ -939,7 +939,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/mux_h1.o src/tcpcheck.o \
src/ebmbtree.o src/cfgcond.o src/action.o src/xprt_handshake.o \
src/protocol.o src/proto_uxst.o src/proto_udp.o src/lb_map.o \
src/fix.o src/ev_select.o src/arg.o src/sock_inet.o src/event_hdl.o \
src/mworker-prog.o src/hpack-dec.o src/cfgparse-tcp.o \
src/mworker-prog.o src/hpack-dec.o src/cfgparse-tcp.o src/lb_ss.o \
src/sock_unix.o src/shctx.o src/proto_uxdg.o src/fcgi.o \
src/eb64tree.o src/clock.o src/chunk.o src/cfgdiag.o src/signal.o \
src/regex.o src/lru.o src/eb32tree.o src/eb32sctree.o \

View File

@ -5366,8 +5366,7 @@ balance url_param <param> [check_post]
the log messages. When the server goes DOWN, the next server
in the list takes its place. When a previously DOWN server
goes back UP it is added at the end of the list so that the
sticky server doesn't change until it becomes DOWN. This
algorithm is only usable for backends in LOG mode.
sticky server doesn't change until it becomes DOWN.
<arguments> is an optional list of arguments which may be needed by some
algorithms. Right now, only "url_param", "uri" and "log-hash"

View File

@ -28,6 +28,7 @@
#include <haproxy/lb_fwlc-t.h>
#include <haproxy/lb_fwrr-t.h>
#include <haproxy/lb_map-t.h>
#include <haproxy/lb_ss-t.h>
#include <haproxy/server-t.h>
#include <haproxy/thread-t.h>
@ -58,6 +59,9 @@
#define BE_LB_CB_LC 0x00000000 /* least-connections */
#define BE_LB_CB_FAS 0x00000001 /* first available server (opposite of leastconn) */
/* BE_LB_SA_* is used with BE_LB_KIND_SA */
#define BE_LB_SA_SS 0x00000000 /* stick to server as long as it is available */
#define BE_LB_PARM 0x000000FF /* mask to get/clear the LB param */
/* Required input(s) */
@ -73,6 +77,7 @@
#define BE_LB_KIND_RR 0x00010000 /* round-robin */
#define BE_LB_KIND_CB 0x00020000 /* connection-based */
#define BE_LB_KIND_HI 0x00030000 /* hash of input (see hash inputs above) */
#define BE_LB_KIND_SA 0x00040000 /* standalone (specific algorithms, cannot be grouped) */
#define BE_LB_KIND 0x00070000 /* mask to get/clear LB algorithm */
/* All known variants of load balancing algorithms. These can be cleared using
@ -83,6 +88,7 @@
#define BE_LB_ALGO_RND (BE_LB_KIND_RR | BE_LB_NEED_NONE | BE_LB_RR_RANDOM) /* random value */
#define BE_LB_ALGO_LC (BE_LB_KIND_CB | BE_LB_NEED_NONE | BE_LB_CB_LC) /* least connections */
#define BE_LB_ALGO_FAS (BE_LB_KIND_CB | BE_LB_NEED_NONE | BE_LB_CB_FAS) /* first available server */
#define BE_LB_ALGO_SS (BE_LB_KIND_SA | BE_LB_NEED_NONE | BE_LB_SA_SS) /* sticky */
#define BE_LB_ALGO_SRR (BE_LB_KIND_RR | BE_LB_NEED_NONE | BE_LB_RR_STATIC) /* static round robin */
#define BE_LB_ALGO_SH (BE_LB_KIND_HI | BE_LB_NEED_ADDR | BE_LB_HASH_SRC) /* hash: source IP */
#define BE_LB_ALGO_UH (BE_LB_KIND_HI | BE_LB_NEED_HTTP | BE_LB_HASH_URI) /* hash: HTTP URI */
@ -91,7 +97,6 @@
#define BE_LB_ALGO_RCH (BE_LB_KIND_HI | BE_LB_NEED_DATA | BE_LB_HASH_RDP) /* hash: RDP cookie value */
#define BE_LB_ALGO_SMP (BE_LB_KIND_HI | BE_LB_NEED_DATA | BE_LB_HASH_SMP) /* hash: sample expression */
#define BE_LB_ALGO_LH (BE_LB_KIND_HI | BE_LB_NEED_LOG | BE_LB_HASH_SMP) /* log hash: sample expression */
#define BE_LB_ALGO_LS (BE_LB_KIND_CB | BE_LB_NEED_LOG | BE_LB_CB_FAS) /* log sticky */
#define BE_LB_ALGO (BE_LB_KIND | BE_LB_NEED | BE_LB_PARM ) /* mask to clear algo */
/* Higher bits define how a given criterion is mapped to a server. In fact it
@ -147,6 +152,7 @@ struct lbprm {
struct lb_fwlc fwlc;
struct lb_chash chash;
struct lb_fas fas;
struct lb_ss ss;
struct {
struct server **srv; /* array containing in-use log servers */
struct list avail; /* servers available for lb are registered in this list */

32
include/haproxy/lb_ss-t.h Normal file
View File

@ -0,0 +1,32 @@
/*
* include/haproxy/lb_ss-t.h
* Types for sticky load-balancing
*
* Copyright 2024 HAProxy Technologies
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HAPROXY_LB_LH_T_H
#define _HAPROXY_LB_LH_T_H
#include <haproxy/api-t.h>
#include <haproxy/server-t.h>
struct lb_ss {
struct server *srv; /* sticked server */
};
#endif /* _HAPROXY_LB_LH_T_H */

33
include/haproxy/lb_ss.h Normal file
View File

@ -0,0 +1,33 @@
/*
* include/haproxy/lb_ss.h
* sticky load-balancing
*
* Copyright 2024 HAProxy Technologies
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HAPROXY_LB_SS_H
#define _HAPROXY_LB_SS_H
#include <haproxy/api.h>
#include <haproxy/proxy-t.h>
#include <haproxy/server-t.h>
void recalc_server_ss(struct proxy *px);
void init_server_ss(struct proxy *px);
struct server *ss_get_server(struct proxy *px);
#endif /* _HAPROXY_LB_SS_H */

View File

@ -39,6 +39,7 @@
#include <haproxy/lb_fwlc.h>
#include <haproxy/lb_fwrr.h>
#include <haproxy/lb_map.h>
#include <haproxy/lb_ss.h>
#include <haproxy/log.h>
#include <haproxy/namespace.h>
#include <haproxy/obj_type.h>
@ -813,6 +814,14 @@ int assign_server(struct stream *s)
break;
default:
if ((s->be->lbprm.algo & BE_LB_KIND) == BE_LB_KIND_SA) {
/* some special algos that cannot be grouped together */
if ((s->be->lbprm.algo & BE_LB_PARM) == BE_LB_SA_SS)
srv = ss_get_server(s->be);
break;
}
/* unknown balancing algorithm */
err = SRV_STATUS_INTERNAL;
goto out;
@ -2878,7 +2887,7 @@ int backend_parse_balance(const char **args, char **err, struct proxy *curproxy)
}
else if (strcmp(args[0], "sticky") == 0) {
curproxy->lbprm.algo &= ~BE_LB_ALGO;
curproxy->lbprm.algo |= BE_LB_ALGO_LS;
curproxy->lbprm.algo |= BE_LB_ALGO_SS;
}
else {
memprintf(err, "only supports 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri', 'url_param', 'hash', 'hdr(name)', 'rdp-cookie(name)', 'log-hash' and 'sticky' options.");

View File

@ -64,6 +64,7 @@
#include <haproxy/lb_fwlc.h>
#include <haproxy/lb_fwrr.h>
#include <haproxy/lb_map.h>
#include <haproxy/lb_ss.h>
#include <haproxy/listener.h>
#include <haproxy/log.h>
#include <haproxy/sink.h>
@ -3766,6 +3767,12 @@ int check_config_validity()
init_server_map(curproxy);
}
break;
case BE_LB_KIND_SA:
if ((curproxy->lbprm.algo & BE_LB_PARM) == BE_LB_SA_SS) {
curproxy->lbprm.algo |= BE_LB_PROP_DYN;
init_server_ss(curproxy);
}
break;
}
skip_server_lb_init:
HA_RWLOCK_INIT(&curproxy->lbprm.lock);

183
src/lb_ss.c Normal file
View File

@ -0,0 +1,183 @@
/*
* sticky load-balancing
*
* Copyright 2024 HAProxy Technologies
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <haproxy/api.h>
#include <haproxy/backend.h>
#include <haproxy/lb_ss.h>
#include <haproxy/server-t.h>
/* this function updates the stick server according to server <srv>'s new state.
*
* The server's lock must be held. The lbprm's lock will be used.
*/
static void ss_set_server_status_down(struct server *srv)
{
struct proxy *p = srv->proxy;
if (!srv_lb_status_changed(srv))
return;
if (srv_willbe_usable(srv))
goto out_update_state;
HA_RWLOCK_WRLOCK(LBPRM_LOCK, &p->lbprm.lock);
if (!srv_currently_usable(srv))
/* server was already down */
goto out_update_backend;
if (srv->flags & SRV_F_BACKUP) {
p->lbprm.tot_wbck -= srv->cur_eweight;
p->srv_bck--;
} else {
p->lbprm.tot_wact -= srv->cur_eweight;
p->srv_act--;
}
if (srv == p->lbprm.ss.srv) {
/* sticked server is down, elect a new server
* that we will be sticking on.
*/
recalc_server_ss(p);
}
out_update_backend:
/* check/update tot_used, tot_weight */
update_backend_weight(p);
HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &p->lbprm.lock);
out_update_state:
srv_lb_commit_status(srv);
}
/* This function updates the stick server according to server <srv>'s new state.
*
* The server's lock must be held. The lbprm's lock will be used.
*/
static void ss_set_server_status_up(struct server *srv)
{
struct proxy *p = srv->proxy;
if (!srv_lb_status_changed(srv))
return;
if (!srv_willbe_usable(srv))
goto out_update_state;
HA_RWLOCK_WRLOCK(LBPRM_LOCK, &p->lbprm.lock);
if (srv_currently_usable(srv))
/* server was already up */
goto out_update_backend;
if (srv->flags & SRV_F_BACKUP) {
p->lbprm.tot_wbck += srv->next_eweight;
p->srv_bck++;
} else {
p->lbprm.tot_wact += srv->next_eweight;
p->srv_act++;
}
if (!p->lbprm.ss.srv ||
((p->lbprm.ss.srv->flags & SRV_F_BACKUP) && !(srv->flags & SRV_F_BACKUP))) {
/* we didn't have a server or were sticking on a backup server,
* but now we have an active server, let's switch to it
*/
p->lbprm.ss.srv = srv;
}
out_update_backend:
/* check/update tot_used, tot_weight */
update_backend_weight(p);
HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &p->lbprm.lock);
out_update_state:
srv_lb_commit_status(srv);
}
/* This function elects a new stick server for proxy px.
*
* The lbprm's lock must be held.
*/
void recalc_server_ss(struct proxy *px)
{
struct server *cur, *first;
int flag;
if (!px->lbprm.tot_used)
return; /* no server */
/* here we *know* that we have some servers */
if (px->srv_act)
flag = 0;
else
flag = SRV_F_BACKUP;
first = NULL;
for (cur = px->srv; cur; cur = cur->next) {
if ((cur->flags & SRV_F_BACKUP) == flag &&
srv_willbe_usable(cur)) {
first = cur;
break;
}
}
px->lbprm.ss.srv = first;
}
/* This function is responsible for preparing sticky LB algorithm.
* It should be called only once per proxy, at config time.
*/
void init_server_ss(struct proxy *p)
{
struct server *srv;
p->lbprm.set_server_status_up = ss_set_server_status_up;
p->lbprm.set_server_status_down = ss_set_server_status_down;
p->lbprm.update_server_eweight = NULL;
if (!p->srv)
return;
for (srv = p->srv; srv; srv = srv->next) {
srv->next_eweight = 1; /* ignore weights, all servers have the same weight */
srv_lb_commit_status(srv);
}
/* recounts servers and their weights */
recount_servers(p);
update_backend_weight(p);
recalc_server_ss(p);
}
/*
* This function returns the server that we're sticking on. If any server
* is found, it will be returned. If no valid server is found, NULL is
* returned.
*
* The lbprm's lock will be used.
*/
struct server *ss_get_server(struct proxy *px)
{
struct server *srv = NULL;
HA_RWLOCK_RDLOCK(LBPRM_LOCK, &px->lbprm.lock);
srv = px->lbprm.ss.srv;
HA_RWLOCK_RDUNLOCK(LBPRM_LOCK, &px->lbprm.lock);
return srv;
}

View File

@ -981,7 +981,7 @@ static int _postcheck_log_backend_compat(struct proxy *be)
}
if (balance_algo != BE_LB_ALGO_RR &&
balance_algo != BE_LB_ALGO_RND &&
balance_algo != BE_LB_ALGO_LS &&
balance_algo != BE_LB_ALGO_SS &&
balance_algo != BE_LB_ALGO_LH) {
ha_alert("in %s '%s': \"balance\" only supports 'roundrobin', 'random', 'sticky' and 'log-hash'.\n", proxy_type_str(be), be->id);
err_code |= ERR_ALERT | ERR_FATAL;
@ -2328,7 +2328,7 @@ static inline void __do_send_log_backend(struct proxy *be, struct log_header hdr
*/
targetid = HA_ATOMIC_FETCH_ADD(&be->lbprm.log.lastid, 1) % nb_srv;
}
else if ((be->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_LS) {
else if ((be->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SS) {
/* sticky mode: use first server in the pool, which will always stay
* first during dequeuing and requeuing, unless it becomes unavailable
* and will be replaced by another one