mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-07 11:28:48 +03:00
fee4890e94
* Balance round robin evenly when some hosts are down Before, when load balancing across multiple hosts, if a host went down then the next host in line would be sent a double share of requests. This is because the round robin counter was only incremented once per request, regardless of the health of the selection. If current selection was unhealthy then the policy would advance to the next host, but this would not be reflected in the policy counter. To fix this, the counter is now incremented for every attempted host. This commit adds a test case that identifies the issue, and a fix. * Make robin counter private * Use a mutex to sync round robin selection
103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
package proxy
|
|
|
|
import (
|
|
"math/rand"
|
|
"sync"
|
|
)
|
|
|
|
// HostPool is a collection of UpstreamHosts.
|
|
type HostPool []*UpstreamHost
|
|
|
|
// Policy decides how a host will be selected from a pool.
|
|
type Policy interface {
|
|
Select(pool HostPool) *UpstreamHost
|
|
}
|
|
|
|
func init() {
|
|
RegisterPolicy("random", func() Policy { return &Random{} })
|
|
RegisterPolicy("least_conn", func() Policy { return &LeastConn{} })
|
|
RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} })
|
|
}
|
|
|
|
// Random is a policy that selects up hosts from a pool at random.
|
|
type Random struct{}
|
|
|
|
// Select selects an up host at random from the specified pool.
|
|
func (r *Random) Select(pool HostPool) *UpstreamHost {
|
|
// instead of just generating a random index
|
|
// this is done to prevent selecting a unavailable host
|
|
var randHost *UpstreamHost
|
|
count := 0
|
|
for _, host := range pool {
|
|
if !host.Available() {
|
|
continue
|
|
}
|
|
count++
|
|
if count == 1 {
|
|
randHost = host
|
|
} else {
|
|
r := rand.Int() % count
|
|
if r == (count - 1) {
|
|
randHost = host
|
|
}
|
|
}
|
|
}
|
|
return randHost
|
|
}
|
|
|
|
// LeastConn is a policy that selects the host with the least connections.
|
|
type LeastConn struct{}
|
|
|
|
// Select selects the up host with the least number of connections in the
|
|
// pool. If more than one host has the same least number of connections,
|
|
// one of the hosts is chosen at random.
|
|
func (r *LeastConn) Select(pool HostPool) *UpstreamHost {
|
|
var bestHost *UpstreamHost
|
|
count := 0
|
|
leastConn := int64(1<<63 - 1)
|
|
for _, host := range pool {
|
|
if !host.Available() {
|
|
continue
|
|
}
|
|
hostConns := host.Conns
|
|
if hostConns < leastConn {
|
|
bestHost = host
|
|
leastConn = hostConns
|
|
count = 1
|
|
} else if hostConns == leastConn {
|
|
// randomly select host among hosts with least connections
|
|
count++
|
|
if count == 1 {
|
|
bestHost = host
|
|
} else {
|
|
r := rand.Int() % count
|
|
if r == (count - 1) {
|
|
bestHost = host
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bestHost
|
|
}
|
|
|
|
// RoundRobin is a policy that selects hosts based on round robin ordering.
|
|
type RoundRobin struct {
|
|
robin uint32
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
// Select selects an up host from the pool using a round robin ordering scheme.
|
|
func (r *RoundRobin) Select(pool HostPool) *UpstreamHost {
|
|
poolLen := uint32(len(pool))
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
// Return next available host
|
|
for i := uint32(0); i < poolLen; i++ {
|
|
r.robin++
|
|
host := pool[r.robin%poolLen]
|
|
if host.Available() {
|
|
return host
|
|
}
|
|
}
|
|
return nil
|
|
}
|