reverseproxy: Use xxhash instead of fnv32 for LB (#6203)

* Added Faster Non-cryptographic Hash Function for Load Balancing

* Ran golangci-lint

* Updated hash version and hash return type
This commit is contained in:
Hayder 2024-03-29 12:56:18 -04:00 committed by GitHub
parent ddb1d2c2b1
commit 74949fb091
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 83 deletions

View file

@ -20,7 +20,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"hash/fnv"
weakrand "math/rand"
"net"
"net/http"
@ -28,6 +27,8 @@ import (
"strings"
"sync/atomic"
"github.com/cespare/xxhash/v2"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@ -807,7 +808,7 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
// see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0,
// https://randorithms.com/2020/12/26/rendezvous-hashing.html,
// and https://en.wikipedia.org/wiki/Rendezvous_hashing.
var highestHash uint32
var highestHash uint64
var upstream *Upstream
for _, up := range pool {
if !up.Available() {
@ -823,10 +824,10 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
}
// hash calculates a fast hash based on s.
func hash(s string) uint32 {
h := fnv.New32a()
func hash(s string) uint64 {
h := xxhash.New()
_, _ = h.Write([]byte(s))
return h.Sum32()
return h.Sum64()
}
func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {

View file

@ -157,8 +157,8 @@ func TestIPHashPolicy(t *testing.T) {
// We should be able to predict where every request is routed.
req.RemoteAddr = "172.0.0.1:80"
h := ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
req.RemoteAddr = "172.0.0.2:80"
h = ipHash.Select(pool, req, nil)
@ -167,8 +167,8 @@ func TestIPHashPolicy(t *testing.T) {
}
req.RemoteAddr = "172.0.0.3:80"
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
req.RemoteAddr = "172.0.0.4:80"
h = ipHash.Select(pool, req, nil)
@ -179,8 +179,8 @@ func TestIPHashPolicy(t *testing.T) {
// we should get the same results without a port
req.RemoteAddr = "172.0.0.1"
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
req.RemoteAddr = "172.0.0.2"
h = ipHash.Select(pool, req, nil)
@ -189,8 +189,8 @@ func TestIPHashPolicy(t *testing.T) {
}
req.RemoteAddr = "172.0.0.3"
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
req.RemoteAddr = "172.0.0.4"
h = ipHash.Select(pool, req, nil)
@ -203,8 +203,8 @@ func TestIPHashPolicy(t *testing.T) {
req.RemoteAddr = "172.0.0.4"
pool[1].setHealthy(false)
h = ipHash.Select(pool, req, nil)
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
if h != pool[2] {
t.Error("Expected ip hash policy host to be the third host.")
}
req.RemoteAddr = "172.0.0.2"
@ -217,8 +217,8 @@ func TestIPHashPolicy(t *testing.T) {
req.RemoteAddr = "172.0.0.3"
pool[2].setHealthy(false)
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
req.RemoteAddr = "172.0.0.4"
h = ipHash.Select(pool, req, nil)
@ -300,8 +300,8 @@ func TestClientIPHashPolicy(t *testing.T) {
// We should be able to predict where every request is routed.
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80")
h := ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80")
h = ipHash.Select(pool, req, nil)
@ -310,8 +310,8 @@ func TestClientIPHashPolicy(t *testing.T) {
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80")
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80")
h = ipHash.Select(pool, req, nil)
@ -322,8 +322,8 @@ func TestClientIPHashPolicy(t *testing.T) {
// we should get the same results without a port
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1")
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
h = ipHash.Select(pool, req, nil)
@ -332,8 +332,8 @@ func TestClientIPHashPolicy(t *testing.T) {
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
h = ipHash.Select(pool, req, nil)
@ -346,8 +346,8 @@ func TestClientIPHashPolicy(t *testing.T) {
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
pool[1].setHealthy(false)
h = ipHash.Select(pool, req, nil)
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
if h != pool[2] {
t.Error("Expected ip hash policy host to be the third host.")
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
@ -360,8 +360,8 @@ func TestClientIPHashPolicy(t *testing.T) {
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
pool[2].setHealthy(false)
h = ipHash.Select(pool, req, nil)
if h != pool[1] {
t.Error("Expected ip hash policy host to be the second host.")
if h != pool[0] {
t.Error("Expected ip hash policy host to be the first host.")
}
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
h = ipHash.Select(pool, req, nil)
@ -470,21 +470,21 @@ func TestQueryHashPolicy(t *testing.T) {
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
h = queryPolicy.Select(pool, request, nil)
if h != pool[0] {
t.Error("Expected query policy host to be the first host.")
if h != pool[1] {
t.Error("Expected query policy host to be the second host.")
}
request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil)
pool[0].setHealthy(false)
h = queryPolicy.Select(pool, request, nil)
if h != pool[1] {
t.Error("Expected query policy host to be the second host.")
if h != pool[2] {
t.Error("Expected query policy host to be the third host.")
}
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
h = queryPolicy.Select(pool, request, nil)
if h != pool[2] {
t.Error("Expected query policy host to be the third host.")
if h != pool[1] {
t.Error("Expected query policy host to be the second host.")
}
// We should be able to resize the host pool and still be able to predict
@ -533,14 +533,14 @@ func TestURIHashPolicy(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "/test", nil)
h := uriPolicy.Select(pool, request, nil)
if h != pool[1] {
t.Error("Expected uri policy host to be the second host.")
if h != pool[2] {
t.Error("Expected uri policy host to be the third host.")
}
pool[2].setHealthy(false)
h = uriPolicy.Select(pool, request, nil)
if h != pool[1] {
t.Error("Expected uri policy host to be the second host.")
if h != pool[0] {
t.Error("Expected uri policy host to be the first host.")
}
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)