mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 14:13:48 +03:00
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:
parent
ddb1d2c2b1
commit
74949fb091
2 changed files with 84 additions and 83 deletions
|
@ -20,7 +20,6 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
|
||||||
weakrand "math/rand"
|
weakrand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -28,6 +27,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"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,
|
// see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0,
|
||||||
// https://randorithms.com/2020/12/26/rendezvous-hashing.html,
|
// https://randorithms.com/2020/12/26/rendezvous-hashing.html,
|
||||||
// and https://en.wikipedia.org/wiki/Rendezvous_hashing.
|
// and https://en.wikipedia.org/wiki/Rendezvous_hashing.
|
||||||
var highestHash uint32
|
var highestHash uint64
|
||||||
var upstream *Upstream
|
var upstream *Upstream
|
||||||
for _, up := range pool {
|
for _, up := range pool {
|
||||||
if !up.Available() {
|
if !up.Available() {
|
||||||
|
@ -823,10 +824,10 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
|
||||||
}
|
}
|
||||||
|
|
||||||
// hash calculates a fast hash based on s.
|
// hash calculates a fast hash based on s.
|
||||||
func hash(s string) uint32 {
|
func hash(s string) uint64 {
|
||||||
h := fnv.New32a()
|
h := xxhash.New()
|
||||||
_, _ = h.Write([]byte(s))
|
_, _ = h.Write([]byte(s))
|
||||||
return h.Sum32()
|
return h.Sum64()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {
|
func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {
|
||||||
|
|
|
@ -157,8 +157,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
// We should be able to predict where every request is routed.
|
// We should be able to predict where every request is routed.
|
||||||
req.RemoteAddr = "172.0.0.1:80"
|
req.RemoteAddr = "172.0.0.1:80"
|
||||||
h := ipHash.Select(pool, req, nil)
|
h := ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.2:80"
|
req.RemoteAddr = "172.0.0.2:80"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -167,8 +167,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.3:80"
|
req.RemoteAddr = "172.0.0.3:80"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.4:80"
|
req.RemoteAddr = "172.0.0.4:80"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -179,8 +179,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
// we should get the same results without a port
|
// we should get the same results without a port
|
||||||
req.RemoteAddr = "172.0.0.1"
|
req.RemoteAddr = "172.0.0.1"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.2"
|
req.RemoteAddr = "172.0.0.2"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -189,8 +189,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.3"
|
req.RemoteAddr = "172.0.0.3"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.4"
|
req.RemoteAddr = "172.0.0.4"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -203,8 +203,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
req.RemoteAddr = "172.0.0.4"
|
req.RemoteAddr = "172.0.0.4"
|
||||||
pool[1].setHealthy(false)
|
pool[1].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[0] {
|
if h != pool[2] {
|
||||||
t.Error("Expected ip hash policy host to be the first host.")
|
t.Error("Expected ip hash policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.RemoteAddr = "172.0.0.2"
|
req.RemoteAddr = "172.0.0.2"
|
||||||
|
@ -217,8 +217,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
req.RemoteAddr = "172.0.0.3"
|
req.RemoteAddr = "172.0.0.3"
|
||||||
pool[2].setHealthy(false)
|
pool[2].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.4"
|
req.RemoteAddr = "172.0.0.4"
|
||||||
h = ipHash.Select(pool, req, nil)
|
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.
|
// We should be able to predict where every request is routed.
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80")
|
||||||
h := ipHash.Select(pool, req, nil)
|
h := ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80")
|
||||||
h = ipHash.Select(pool, req, nil)
|
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")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -322,8 +322,8 @@ func TestClientIPHashPolicy(t *testing.T) {
|
||||||
// we should get the same results without a port
|
// we should get the same results without a port
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
|
||||||
h = ipHash.Select(pool, req, nil)
|
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")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
||||||
h = ipHash.Select(pool, req, nil)
|
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")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
||||||
pool[1].setHealthy(false)
|
pool[1].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[0] {
|
if h != pool[2] {
|
||||||
t.Error("Expected ip hash policy host to be the first host.")
|
t.Error("Expected ip hash policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
|
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")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
|
||||||
pool[2].setHealthy(false)
|
pool[2].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -470,21 +470,21 @@ func TestQueryHashPolicy(t *testing.T) {
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
||||||
h = queryPolicy.Select(pool, request, nil)
|
h = queryPolicy.Select(pool, request, nil)
|
||||||
if h != pool[0] {
|
if h != pool[1] {
|
||||||
t.Error("Expected query policy host to be the first host.")
|
t.Error("Expected query policy host to be the second host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil)
|
request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil)
|
||||||
pool[0].setHealthy(false)
|
pool[0].setHealthy(false)
|
||||||
h = queryPolicy.Select(pool, request, nil)
|
h = queryPolicy.Select(pool, request, nil)
|
||||||
if h != pool[1] {
|
if h != pool[2] {
|
||||||
t.Error("Expected query policy host to be the second host.")
|
t.Error("Expected query policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
||||||
h = queryPolicy.Select(pool, request, nil)
|
h = queryPolicy.Select(pool, request, nil)
|
||||||
if h != pool[2] {
|
if h != pool[1] {
|
||||||
t.Error("Expected query policy host to be the third host.")
|
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
|
// 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)
|
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||||
h := uriPolicy.Select(pool, request, nil)
|
h := uriPolicy.Select(pool, request, nil)
|
||||||
if h != pool[1] {
|
if h != pool[2] {
|
||||||
t.Error("Expected uri policy host to be the second host.")
|
t.Error("Expected uri policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
pool[2].setHealthy(false)
|
pool[2].setHealthy(false)
|
||||||
h = uriPolicy.Select(pool, request, nil)
|
h = uriPolicy.Select(pool, request, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected uri policy host to be the second host.")
|
t.Error("Expected uri policy host to be the first host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
|
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
|
||||||
|
|
Loading…
Reference in a new issue