From: Charles-Henri Bruyand Date: Tue, 5 Jun 2018 12:06:55 +0000 (+0200) Subject: dnsdist: add consistent hash builtin policy X-Git-Tag: dnsdist-1.3.3~148^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1720247eee24adbfdb6af3ce1cc7f1dccd3778a3;p=thirdparty%2Fpdns.git dnsdist: add consistent hash builtin policy --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index f8134fff48..dc111ce5bd 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -461,6 +461,7 @@ const std::vector g_consoleKeywords{ { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" }, { "webserver", true, "address:port, password [, apiKey [, customHeaders ]])", "launch a webserver with stats on that address with that password" }, { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" }, + { "chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter" }, { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" }, }; diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc index f5229184b2..5eb25e1ad2 100644 --- a/pdns/dnsdist-lua-bindings.cc +++ b/pdns/dnsdist-lua-bindings.cc @@ -70,6 +70,7 @@ void setupLuaBindings(bool client) g_lua.writeVariable("roundrobin", ServerPolicy{"roundrobin", roundrobin, false}); g_lua.writeVariable("wrandom", ServerPolicy{"wrandom", wrandom, false}); g_lua.writeVariable("whashed", ServerPolicy{"whashed", whashed, false}); + g_lua.writeVariable("chashed", ServerPolicy{"chashed", chashed, false}); g_lua.writeVariable("leastOutstanding", ServerPolicy{"leastOutstanding", leastOutstanding, false}); /* ServerPool */ diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index e35f0c0f3e..2355d503cc 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -44,6 +44,7 @@ #include "sodcrypto.hh" #include +#include #ifdef HAVE_SYSTEMD #include @@ -276,6 +277,10 @@ void setupLuaConfig(bool client) ret->name=boost::get(vars["name"]); } + if (vars.count("id")) { + ret->id = boost::lexical_cast(boost::get(vars["id"])); + } + if(vars.count("checkName")) { ret->checkName=DNSName(boost::get(vars["checkName"])); } diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index eb2873c624..d4bffecf98 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -627,6 +627,7 @@ bool DownstreamState::reconnect() DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf_, size_t numberOfSockets): remote(remote_), sourceAddr(sourceAddr_), sourceItf(sourceItf_) { + id = t_uuidGenerator(); threadStarted.clear(); mplexer = std::unique_ptr(FDMultiplexer::getMultiplexerSilent()); @@ -642,6 +643,7 @@ DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress sw.start(); infolog("Added downstream server %s", remote.toStringWithPort()); } + } std::mutex g_luamutex; @@ -721,6 +723,48 @@ shared_ptr whashed(const NumberedServerVector& servers, const D return valrandom(dq->qname->hash(g_hashperturb), servers, dq); } +/* + * @todo + * - test/benchmark other hashing methods + * - test/benchmark adding an avalanche algorithm on hashes + * @see https://github.com/haproxy/haproxy/blob/master/doc/internals/hashing.txt + */ + +shared_ptr chashed(const NumberedServerVector& servers, const DNSQuestion* dq) +{ + std::map> circle = {}; + unsigned int qhash = dq->qname->hash(g_hashperturb); + + for (const auto& d: servers) { + if (d.second->isUp()) { + if (d.second->hashes.empty()) { + // computes server's points + // @todo check if server's weight can change over time + auto w = d.second->weight; + while (w > 0) { + std::string uuid = boost::str(boost::format("%s-%d") % d.second->id % w); + unsigned int wshash = burtleCI((const unsigned char*)uuid.c_str(), uuid.size(), g_hashperturb); + d.second->hashes.insert(wshash); + --w; + } + } + for (const auto& h: d.second->hashes) { + // put server's hashes on the circle + circle.insert(std::make_pair(h, d.second)); + } + } + } + if (circle.empty()) { + return shared_ptr(); + } + + auto p = circle.upper_bound(qhash); + if(p == circle.end()) { + return circle.begin()->second; + } + return p->second; +} + shared_ptr roundrobin(const NumberedServerVector& servers, const DNSQuestion* dq) { diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index cc30c09fd0..0a035b6634 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -518,7 +518,7 @@ extern std::shared_ptr g_tcpclientthreads; struct DownstreamState { - typedef std::function(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t; + typedef std::function(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t; DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf, size_t numberOfSockets); DownstreamState(const ComboAddress& remote_): DownstreamState(remote_, ComboAddress(), 0, 1) {} @@ -531,7 +531,8 @@ struct DownstreamState } } } - + boost::uuids::uuid id; + std::set hashes; std::vector sockets; std::mutex socketsLock; std::mutex connectLock; @@ -846,6 +847,7 @@ std::shared_ptr firstAvailable(const NumberedServerVector& serv std::shared_ptr leastOutstanding(const NumberedServerVector& servers, const DNSQuestion* dq); std::shared_ptr wrandom(const NumberedServerVector& servers, const DNSQuestion* dq); std::shared_ptr whashed(const NumberedServerVector& servers, const DNSQuestion* dq); +std::shared_ptr chashed(const NumberedServerVector& servers, const DNSQuestion* dq); std::shared_ptr roundrobin(const NumberedServerVector& servers, const DNSQuestion* dq); int getEDNSZ(const char* packet, unsigned int len); uint16_t getEDNSOptionCode(const char * packet, size_t len); diff --git a/pdns/dnsdistdist/docs/guides/serverselection.rst b/pdns/dnsdistdist/docs/guides/serverselection.rst index de6316edab..0668705317 100644 --- a/pdns/dnsdistdist/docs/guides/serverselection.rst +++ b/pdns/dnsdistdist/docs/guides/serverselection.rst @@ -43,6 +43,13 @@ The current hash algorithm is based on the qname of the query. Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instances. +``chashed`` +~~~~~~~~~~~ + +``chashed`` is a consistent hashing distribution policy. Identical questions with identical hashes will be distributed to the same servers. But unlike the ``whashed`` policy, this distribution will keep consistent over time. Adding or removing servers will only remap a small part of the queries. + +You can also set the hash perturbation value, see :func:`setWHashedPertubation`. + ``roundrobin`` ~~~~~~~~~~~~~~ diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 55a816911b..fbd84502e6 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -296,7 +296,7 @@ Servers address="IP:PORT", -- IP and PORT of the backend server (mandatory) qps=NUM, -- Limit the number of queries per second to NUM, when using the `firstAvailable` policy order=NUM, -- The order of this server, used by the `leastOustanding` and `firstAvailable` policies - weight=NUM, -- The weight of this server, used by the `wrandom` and `whashed` policies, default: 1 + weight=NUM, -- The weight of this server, used by the `wrandom`, `whashed` and `chashed` policies, default: 1 -- Supported values are a minimum of 1, and a maximum of 2147483647. pool=STRING|{STRING}, -- The pools this server belongs to (unset or empty string means default pool) as a string or table of strings retries=NUM, -- The number of TCP connection attempts to the backend, for a given query