crossroads

Git mirror of https://crossroads.e-tunity.com/
git clone git://git.finwo.net/app/crossroads
Log | Files | Refs | LICENSE

commit 2c92a238e2fe0a66b074434dc725437a6f133d07
parent c726b6b6ec94874d005b2b0ee4fdc045a6dde3d6
Author: finwo <finwo@pm.me>
Date:   Sat,  3 Jan 2026 19:37:25 +0100

2.47

Diffstat:
MChangeLog | 7+++++++
MMakefile | 2+-
Mxr/DispatchAlgorithms/leastconn/target.cc | 15+++++++++------
Mxr/DispatchAlgorithms/storedip/target.cc | 98++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mxr/Dispatchers/tcpdispatcher/dispatch.cc | 5+++++
Axr/backend/anticipated.cc | 9+++++++++
Axr/backend/anticipateless.cc | 9+++++++++
Axr/backend/anticipatemore.cc | 7+++++++
Mxr/backend/available.cc | 7+++++--
Mxr/backend/backend | 8+++++++-
Mxr/backend/backend1.cc | 2+-
Mxr/backend/backend2.cc | 1+
Mxr/config/setdispatcmode.cc | 2+-
Mxr/etc/status.xslt | 7++++++-
Mxr/webinterface/answerstatus.cc | 1+
15 files changed, 119 insertions(+), 61 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,10 @@ +2.47 [KK 2009-03-04] +- Stored-ip dispatching was enhanced to "anticipate" reconnects from + previously seen clients. The number of anticipated connections is taken + into account when dispatching a new client. +- Bugfix in command line parsing of dispatch mode lax-stored-ip (would + be recognized as strict). + 2.46 [KK 2009-02-18] - Dispatcher-related classes moved under directory xr/Dispatchers/. - UDP balancer implementation started (code stubs in place). diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ # Top-level Makefile for XR # ------------------------- -VER = 2.46 +VER = 2.47 PREFIX = $(DESTDIR)/usr BINDIR = $(PREFIX)/sbin MANDIR = $(PREFIX)/share/man diff --git a/xr/DispatchAlgorithms/leastconn/target.cc b/xr/DispatchAlgorithms/leastconn/target.cc @@ -6,25 +6,28 @@ unsigned Leastconn::target(struct in_addr clientip, PROFILE("Leastconn::target"); bool found = false; - unsigned nconn = 0, t = 0; + unsigned best_weighted = 0, t = 0; for (unsigned i = 0; i < targetlist.size(); i++) { if (! balancer.backend(targetlist[i]).available()) continue; - unsigned weighted_conn = - balancer.backend(targetlist[i]).connections() * + unsigned this_weight = + (balancer.backend(targetlist[i]).connections() + + balancer.backend(targetlist[i]).anticipated()) * balancer.backend(targetlist[i]).adjustedweight(); msg ("Back end " + balancer.backend(targetlist[i]).description() + (Mstr(": connections ") + balancer.backend(targetlist[i]).connections()) + + (Mstr(", anticipated ") + + balancer.backend(targetlist[i]).anticipated()) + (Mstr(", adjusted weight ") + balancer.backend(targetlist[i]).adjustedweight()) + - (Mstr(", weighted connections ") + weighted_conn) + + (Mstr(", weighted ") + this_weight) + "\n"); - if (!found || weighted_conn < nconn) { + if (!found || this_weight < best_weighted) { t = targetlist[i]; - nconn = balancer.backend(t).connections(); + best_weighted = this_weight; found = true; } } diff --git a/xr/DispatchAlgorithms/storedip/target.cc b/xr/DispatchAlgorithms/storedip/target.cc @@ -19,71 +19,73 @@ static StoreMap store; unsigned StoredIp::target(struct in_addr clientip, BackendVector const &targetlist) { + PROFILE("StoredIP::target"); + + msg(Mstr("Starting stored-ip dispatcher\n")); + unsigned target; time_t now = time(0); - if (store.count(clientip) > 0) { - // Client already known, maybe timed out. - time_t diff = now - store[clientip].lastaccess; + // Weed out store. Done first, because the store should be up to date + // for some decisions below. + for (StoreMap::iterator iter = store.begin(); iter != store.end(); + iter++) { + if (config.debug()) { + Timestamp tm((*iter).second.lastaccess); + debugmsg (Mstr(inet_ntoa(iter->first)) + Mstr(" visited on ") + + tm.desc() + "\n"); + } + if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) { + debugmsg (" Erasing stale entry\n"); + store.erase(iter); + } + } + // Let's see if we know the client. + if (store.count(clientip) > 0) { if (config.verbose()) { Timestamp tm(store[clientip].lastaccess); msg(Mstr("Client IP ") + Mstr(inet_ntoa(clientip)) + - " last visited on " + tm.desc() + - Mstr(Mstr(", ") + diff) + " sec ago, and went to " + + " last visited on " + tm.desc() + " and went to " + balancer.backend(store[clientip].targetbackend).description() + "\n"); } - - if (diff <= config.ipstoretimeout()) { - // Recent 'nuff - target = store[clientip].targetbackend; - if (! balancer.backend(target).available()) { - // Historical target down - get new one if in lax mode - if (config.dispatchmode() == Dispatchmode::m_strict_stored_ip) - throw Error("Stored-IP algorithm: target back end " + - balancer.backend(target).description() + - "unavailable"); - else { - msg ("Stored IP algorithm: target back end " + - balancer.backend(target).description() + - " unavailable, falling back to least-connections\n"); - Leastconn l; - target = l.target(clientip, targetlist); - } - } - } else { - // Not recent anymore - msg ("Visit too long ago, re-dispatching with least-connections\n"); - Leastconn l; - target = l.target(clientip, targetlist); + target = store[clientip].targetbackend; + if (balancer.backend(target).available()) { + // Historical target is up, go there! + msg(Mstr("Sending ") + Mstr(inet_ntoa(clientip)) + " to " + + balancer.backend(target).description() + "\n"); + ClientData entry = {target, now}; + store[clientip] = entry; + return target; } - } else { - // Historical target unknown, fetch new one - msg ("New visit from " + static_cast<string>(inet_ntoa(clientip)) + - "\n"); - Leastconn l; - target = l.target(clientip, targetlist); + msg (Mstr("Historical target ") + + balancer.backend(target).description() + " unavailable\n"); + if (config.dispatchmode() == Dispatchmode::m_strict_stored_ip) + throw Error("Stored-IP algorithm: target back end " + + balancer.backend(target).description() + + "unavailable"); } - // Update the info. - ClientData entry = {target, now}; - store[clientip] = entry; + // Client is seen for the first time, or after the timout period, or + // their preferred back end is down (and we're in lax mode ofc). + // Treat as new connection. - // Weed out store. + // Preload anticipated connections. for (StoreMap::iterator iter = store.begin(); iter != store.end(); iter++) { - if (config.debug()) { - Timestamp tm((*iter).second.lastaccess); - debugmsg (Mstr(inet_ntoa(iter->first)) + Mstr(" visited on ") + - tm.desc() + "\n"); - } - if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) { - debugmsg (" Erasing stale entry, stale\n"); - store.erase(iter); - } + msg(Mstr("Anticipating connection for back end ") + + balancer.backend((*iter).second.targetbackend).description() + + "\n"); + balancer.backend((*iter).second.targetbackend).anticipate_more(); } + // Now get a target and store the dispatch result. + Leastconn l; + target = l.target(clientip, targetlist); + ClientData entry = {target, now}; + store[clientip] = entry; + // Return target to caller - return (target); + return target; } diff --git a/xr/Dispatchers/tcpdispatcher/dispatch.cc b/xr/Dispatchers/tcpdispatcher/dispatch.cc @@ -8,6 +8,11 @@ void TcpDispatcher::dispatch() { bool connected = false; + // Reset the expectancy of back ends. Dispatchers down the line (stored-ip) + // will update that later. + for (unsigned i = 0; i < balancer.nbackends(); i++) + balancer.backend(i).anticipated(0); + // Build up the target list, if not yet done so. The HTTP dispatcher // might've created it already for host-based matching (in which case // we won't bother here). diff --git a/xr/backend/anticipated.cc b/xr/backend/anticipated.cc @@ -0,0 +1,9 @@ +#include "backend" + +void Backend::anticipated(unsigned a) { + Mutex::lock(&nanticipated); + msg((Mstr("Backend ") + description()) + + (Mstr(" now anticipates ") + a) + " connections\n"); + nanticipated = a; + Mutex::unlock(&nanticipated); +} diff --git a/xr/backend/anticipateless.cc b/xr/backend/anticipateless.cc @@ -0,0 +1,9 @@ +#include "backend" + +void Backend::anticipate_less() { + if (nanticipated) { + Mutex::lock(&nanticipated); + nanticipated--; + Mutex::unlock(&nanticipated); + } +} diff --git a/xr/backend/anticipatemore.cc b/xr/backend/anticipatemore.cc @@ -0,0 +1,7 @@ +#include "backend" + +void Backend::anticipate_more() { + Mutex::lock(&nanticipated); + nanticipated++; + Mutex::unlock(&nanticipated); +} diff --git a/xr/backend/available.cc b/xr/backend/available.cc @@ -8,9 +8,12 @@ bool Backend::available() const { (Mstr(": ") + livestr()) + (Mstr(", ") + upstr()) + (Mstr(", ") + connections()) + - (Mstr(" connections of ") + maxconn()) + + (Mstr(" connections, ") + anticipated()) + + (Mstr(" anticipated, of ") + maxconn()) + " max\n"); if (!maxconn()) return (islive && isup); - return (islive && isup && connections() < maxconn()); + return (islive && + isup && + (connections() + anticipated()) < maxconn()); } diff --git a/xr/backend/backend b/xr/backend/backend @@ -53,6 +53,12 @@ public: unsigned adjustedweight() const { return (bdef.adjustedweight()); } unsigned connections() const { return (nconn); } + + unsigned anticipated() const { return nanticipated; } + void anticipated(unsigned n); + void anticipate_more(); + void anticipate_less(); + double bytesserved() const { return (bytes_served); } unsigned clientsserved() const { return (totconn); } @@ -80,7 +86,7 @@ private: bool islive; bool isup; int clsocket; - unsigned nconn, totconn; + unsigned nconn, totconn, nanticipated; double bytes_served; double loadaverage; DNSEntry dnsentry; diff --git a/xr/backend/backend1.cc b/xr/backend/backend1.cc @@ -2,6 +2,6 @@ Backend::Backend () : islive(true), isup(true), clsocket(-1), - nconn(0), totconn(0), bytes_served(0), + nconn(0), totconn(0), nanticipated(0), bytes_served(0), loadaverage(0.1), dnsentry() { } diff --git a/xr/backend/backend2.cc b/xr/backend/backend2.cc @@ -2,5 +2,6 @@ Backend::Backend (BackendDef const &b) : bdef(b), islive(true), isup(true), clsocket(-1), nconn(0), totconn(0), + nanticipated(0), bytes_served(0), loadaverage(0.1), dnsentry() { } diff --git a/xr/config/setdispatcmode.cc b/xr/config/setdispatcmode.cc @@ -33,7 +33,7 @@ void Config::setdispatchmode (string s) { dmode.mode (Dispatchmode::m_lax_stored_ip); ipstoretimeout(setinteger(s.substr(2))); } else if (s.substr(0, 14) == "lax-stored-ip:") { - dmode.mode (Dispatchmode::m_strict_stored_ip); + dmode.mode (Dispatchmode::m_lax_stored_ip); ipstoretimeout(setinteger(s.substr(14))); } else throw Error("Bad dispatch mode -d" + s); diff --git a/xr/etc/status.xslt b/xr/etc/status.xslt @@ -567,7 +567,12 @@ <tr> <td></td> <td>Connections</td> - <td colspan="2"><xsl:value-of select="connections"/></td> + <td colspan="2"> + <xsl:value-of select="connections"/> + <xsl:if test="anticipated &gt; 0"> + (anticipating <xsl:value-of select="anticipated"/>) + </xsl:if> + </td> </tr> <tr> <td></td> diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc @@ -98,6 +98,7 @@ void Webinterface::answer_status() { " <live>" << balancer.backend(i).livestr() << "</live>\n" " <available>" << balancer.backend(i).availablestr() << "</available>\n" " <connections>" << balancer.backend(i).connections() << "</connections>\n" + " <anticipated>" << balancer.backend(i).anticipated() << "</anticipated>\n" " <bytesserved>" << balancer.backend(i).bytesserved() << "</bytesserved>\n" " <clientsserved>" << balancer.backend(i).clientsserved() << "</clientsserved>\n" " <hostmatch>" << balancer.backend(i).hostmatch() << "</hostmatch>\n"