]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add consistent hash builtin policy
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Tue, 5 Jun 2018 12:06:55 +0000 (14:06 +0200)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Thu, 14 Jun 2018 13:22:44 +0000 (15:22 +0200)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist-lua.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/guides/serverselection.rst
pdns/dnsdistdist/docs/reference/config.rst

index f8134fff48df0db5256ba917a7cb5ddcce2d8e5f..dc111ce5bd950792e457822d8df8b204d01abad4 100644 (file)
@@ -461,6 +461,7 @@ const std::vector<ConsoleKeyword> 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" },
 };
 
index f5229184b227dfd76e81b8a07789d2d7dcf891ca..5eb25e1ad2b0b2d05e99226e443a2c58c7fd5475 100644 (file)
@@ -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 */
index e35f0c0f3e2b0f9dc8ce7bcf922e4a148cfabee1..2355d503ccf571a057e3c92477a46bc96f8a0ea7 100644 (file)
@@ -44,6 +44,7 @@
 #include "sodcrypto.hh"
 
 #include <boost/logic/tribool.hpp>
+#include <boost/lexical_cast.hpp>
 
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
@@ -276,6 +277,10 @@ void setupLuaConfig(bool client)
                          ret->name=boost::get<string>(vars["name"]);
                        }
 
+                        if (vars.count("id")) {
+                          ret->id = boost::lexical_cast<boost::uuids::uuid>(boost::get<string>(vars["id"]));
+                        }
+
                        if(vars.count("checkName")) {
                          ret->checkName=DNSName(boost::get<string>(vars["checkName"]));
                        }
index eb2873c624d6343825be380da2bc3aeb54f86a7c..d4bffecf98c4b4d93fe6382d7d984c773ea71a85 100644 (file)
@@ -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>(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<DownstreamState> 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<DownstreamState> chashed(const NumberedServerVector& servers, const DNSQuestion* dq)
+{
+  std::map<unsigned int, shared_ptr<DownstreamState>> 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<DownstreamState>();
+  }
+
+  auto p = circle.upper_bound(qhash);
+  if(p == circle.end()) {
+    return circle.begin()->second;
+  }
+  return p->second;
+}
+
 
 shared_ptr<DownstreamState> roundrobin(const NumberedServerVector& servers, const DNSQuestion* dq)
 {
index cc30c09fd0173b8b5473f9e03debdca3b7f73f58..0a035b6634079d22aac6b10d69a3913723057cdc 100644 (file)
@@ -518,7 +518,7 @@ extern std::shared_ptr<TCPClientCollection> g_tcpclientthreads;
 
 struct DownstreamState
 {
-  typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t;
+   typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(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<unsigned int> hashes;
   std::vector<int> sockets;
   std::mutex socketsLock;
   std::mutex connectLock;
@@ -846,6 +847,7 @@ std::shared_ptr<DownstreamState> firstAvailable(const NumberedServerVector& serv
 std::shared_ptr<DownstreamState> leastOutstanding(const NumberedServerVector& servers, const DNSQuestion* dq);
 std::shared_ptr<DownstreamState> wrandom(const NumberedServerVector& servers, const DNSQuestion* dq);
 std::shared_ptr<DownstreamState> whashed(const NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> chashed(const NumberedServerVector& servers, const DNSQuestion* dq);
 std::shared_ptr<DownstreamState> roundrobin(const NumberedServerVector& servers, const DNSQuestion* dq);
 int getEDNSZ(const char* packet, unsigned int len);
 uint16_t getEDNSOptionCode(const char * packet, size_t len);
index de6316edab5683a2832691d70debaedc10088d25..06687053170cbb3a6021da0f0c49b709ef60df77 100644 (file)
@@ -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``
 ~~~~~~~~~~~~~~
 
index 55a816911bc609f140c7b8ec08ecdc516f57cc83..fbd84502e606b28521828cd877f80b689165d9cd 100644 (file)
@@ -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