diff --git a/Makefile b/Makefile index e2244982a..c904094dc 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/doc/configuration.txt b/doc/configuration.txt index 80778a1e0..57f1a775f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -5366,8 +5366,7 @@ balance url_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. is an optional list of arguments which may be needed by some algorithms. Right now, only "url_param", "uri" and "log-hash" diff --git a/include/haproxy/backend-t.h b/include/haproxy/backend-t.h index 02a2cc5e4..e556a3584 100644 --- a/include/haproxy/backend-t.h +++ b/include/haproxy/backend-t.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -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 */ diff --git a/include/haproxy/lb_ss-t.h b/include/haproxy/lb_ss-t.h new file mode 100644 index 000000000..9014bce0e --- /dev/null +++ b/include/haproxy/lb_ss-t.h @@ -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 +#include + +struct lb_ss { + struct server *srv; /* sticked server */ +}; + +#endif /* _HAPROXY_LB_LH_T_H */ diff --git a/include/haproxy/lb_ss.h b/include/haproxy/lb_ss.h new file mode 100644 index 000000000..6ec31531f --- /dev/null +++ b/include/haproxy/lb_ss.h @@ -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 +#include +#include + +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 */ diff --git a/src/backend.c b/src/backend.c index 18df7ea0f..aaee78686 100644 --- a/src/backend.c +++ b/src/backend.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -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."); diff --git a/src/cfgparse.c b/src/cfgparse.c index e9e4e8a7e..69830c33b 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/src/lb_ss.c b/src/lb_ss.c new file mode 100644 index 000000000..4af031b29 --- /dev/null +++ b/src/lb_ss.c @@ -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 +#include +#include +#include + +/* this function updates the stick server according to server '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 '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; +} diff --git a/src/log.c b/src/log.c index c1e44779b..e9f26d67e 100644 --- a/src/log.c +++ b/src/log.c @@ -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