mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-25 17:56:34 +08:00
proxy: Add new URI hashing load balancing policy (#1679)
* Add uri policy test cases * Add function definition * Add uri hashing policy * Refactor and extract hostByHashing and use in IP and URI policy * Rename to URIHash Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>
This commit is contained in:
parent
b0cf3f0d2d
commit
ebf4279e98
|
@ -23,6 +23,7 @@ func init() {
|
|||
RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} })
|
||||
RegisterPolicy("ip_hash", func() Policy { return &IPHash{} })
|
||||
RegisterPolicy("first", func() Policy { return &First{} })
|
||||
RegisterPolicy("uri_hash", func() Policy { return &URIHash{} })
|
||||
}
|
||||
|
||||
// Random is a policy that selects up hosts from a pool at random.
|
||||
|
@ -106,23 +107,10 @@ func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost
|
|||
return nil
|
||||
}
|
||||
|
||||
// IPHash is a policy that selects hosts based on hashing the request IP
|
||||
type IPHash struct{}
|
||||
|
||||
func hash(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(s))
|
||||
return h.Sum32()
|
||||
}
|
||||
|
||||
// Select selects an up host from the pool based on hashing the request IP
|
||||
func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||
// hostByHashing returns an available host from pool based on a hashable string
|
||||
func hostByHashing(pool HostPool, s string) *UpstreamHost {
|
||||
poolLen := uint32(len(pool))
|
||||
clientIP, _, err := net.SplitHostPort(request.RemoteAddr)
|
||||
if err != nil {
|
||||
clientIP = request.RemoteAddr
|
||||
}
|
||||
index := hash(clientIP) % poolLen
|
||||
index := hash(s) % poolLen
|
||||
for i := uint32(0); i < poolLen; i++ {
|
||||
index += i
|
||||
host := pool[index%poolLen]
|
||||
|
@ -133,6 +121,33 @@ func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
|||
return nil
|
||||
}
|
||||
|
||||
// hash calculates a hash based on string s
|
||||
func hash(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(s))
|
||||
return h.Sum32()
|
||||
}
|
||||
|
||||
// IPHash is a policy that selects hosts based on hashing the request IP
|
||||
type IPHash struct{}
|
||||
|
||||
// Select selects an up host from the pool based on hashing the request IP
|
||||
func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||
clientIP, _, err := net.SplitHostPort(request.RemoteAddr)
|
||||
if err != nil {
|
||||
clientIP = request.RemoteAddr
|
||||
}
|
||||
return hostByHashing(pool, clientIP)
|
||||
}
|
||||
|
||||
// URIHash is a policy that selects the host based on hashing the request URI
|
||||
type URIHash struct{}
|
||||
|
||||
// Select selects the host based on hashing the URI
|
||||
func (r *URIHash) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||
return hostByHashing(pool, request.RequestURI)
|
||||
}
|
||||
|
||||
// First is a policy that selects the first available host
|
||||
type First struct{}
|
||||
|
||||
|
|
|
@ -243,3 +243,62 @@ func TestFirstPolicy(t *testing.T) {
|
|||
t.Error("Expected first policy host to be the second host.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUriPolicy(t *testing.T) {
|
||||
pool := testPool()
|
||||
uriPolicy := &URIHash{}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
h := uriPolicy.Select(pool, request)
|
||||
if h != pool[0] {
|
||||
t.Error("Expected uri policy host to be the first host.")
|
||||
}
|
||||
|
||||
pool[0].Unhealthy = 1
|
||||
h = uriPolicy.Select(pool, request)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected uri policy host to be the first host.")
|
||||
}
|
||||
|
||||
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
|
||||
h = uriPolicy.Select(pool, request)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected uri policy host to be the second host.")
|
||||
}
|
||||
|
||||
// We should be able to resize the host pool and still be able to predict
|
||||
// where a request will be routed with the same URI's used above
|
||||
pool = []*UpstreamHost{
|
||||
{
|
||||
Name: workableServer.URL, // this should resolve (healthcheck test)
|
||||
},
|
||||
{
|
||||
Name: "http://localhost:99998", // this shouldn't
|
||||
},
|
||||
}
|
||||
|
||||
request = httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
h = uriPolicy.Select(pool, request)
|
||||
if h != pool[0] {
|
||||
t.Error("Expected uri policy host to be the first host.")
|
||||
}
|
||||
|
||||
pool[0].Unhealthy = 1
|
||||
h = uriPolicy.Select(pool, request)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected uri policy host to be the first host.")
|
||||
}
|
||||
|
||||
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
|
||||
h = uriPolicy.Select(pool, request)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected uri policy host to be the second host.")
|
||||
}
|
||||
|
||||
pool[0].Unhealthy = 1
|
||||
pool[1].Unhealthy = 1
|
||||
h = uriPolicy.Select(pool, request)
|
||||
if h != nil {
|
||||
t.Error("Expected uri policy policy host to be nil.")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user