diff --git a/README.md b/README.md index ef36fcd..a28e04e 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,12 @@ Frontman uses various storage systems to manage backend service configurations. } ``` Supported Load Balancer types: +- Random + ```json + "loadBalancerPolicy": { + "type": "random" + } + ``` - Round-robin ```json "loadBalancerPolicy": { @@ -241,6 +247,15 @@ Supported Load Balancer types: } ``` +- Weighted Least Connection + ```json + "loadBalancerPolicy": { + "type": "weightd_least_conn" + "options": { + "weights": [1, 2, 3] + } + ``` + You can add, update, and remove backend services using the following REST endpoints: - GET /services - Retrieves a list of all backend services diff --git a/api.go b/api.go index a5d26ae..bc99100 100644 --- a/api.go +++ b/api.go @@ -171,8 +171,10 @@ func validateService(service *service.BackendService) error { func validateLoadBalancerPolicy(s *service.BackendService) error { switch s.LoadBalancerPolicy.Type { + case loadbalancer.Random: + case loadbalancer.LeastConnection: case loadbalancer.RoundRobin: - case loadbalancer.WeightedRoundRobin: + case loadbalancer.WeightedRoundRobin, loadbalancer.WeightedLeastConnection: if len(s.LoadBalancerPolicy.Options.Weights) != len(s.UpstreamTargets) { return fmt.Errorf("mismatched lengts of weights and targets") } @@ -182,7 +184,6 @@ func validateLoadBalancerPolicy(s *service.BackendService) error { return fmt.Errorf("weightes must be greater than zero") } } - case loadbalancer.LeastConnection: default: return fmt.Errorf("unknown load-balancer policy: %s", s.LoadBalancerPolicy.Type) } diff --git a/loadbalancer/least_connections.go b/loadbalancer/least_connections.go index 16c066a..22350c0 100644 --- a/loadbalancer/least_connections.go +++ b/loadbalancer/least_connections.go @@ -6,16 +6,26 @@ type targetInfo struct { target string index int count int + weight int insertionTime uint64 } type targetsHeap struct { - time uint64 - heap []*targetInfo + time uint64 + heap []*targetInfo + weighted bool } func (th targetsHeap) Less(i, j int) bool { if th.heap[i].count == th.heap[j].count { + if th.weighted { + if th.heap[i].weight == th.heap[j].weight { + return th.heap[i].insertionTime < th.heap[j].insertionTime + } + + return th.heap[i].weight > th.heap[j].weight + } + return th.heap[i].insertionTime < th.heap[j].insertionTime } @@ -53,7 +63,7 @@ type LeastConnPolicy struct { targetsMap map[string]*targetInfo } -func NewLeastConnLoadBalancer(targets []string) *LeastConnPolicy { +func NewLeastConnLoadBalancer(targets []string, weights []int) *LeastConnPolicy { tm := make(map[string]*targetInfo, len(targets)) h := targetsHeap{ time: 0, @@ -68,8 +78,13 @@ func NewLeastConnLoadBalancer(targets []string) *LeastConnPolicy { insertionTime: uint64(i), } + if weights != nil { + ti.weight = weights[i] + } + tm[t] = &ti h.heap[i] = &ti + h.weighted = weights != nil } currTime := h.heap[len(h.heap)-1].insertionTime diff --git a/loadbalancer/loadbalancer.go b/loadbalancer/loadbalancer.go index baaf049..18072a7 100644 --- a/loadbalancer/loadbalancer.go +++ b/loadbalancer/loadbalancer.go @@ -5,9 +5,11 @@ import ( ) const ( - RoundRobin string = "round_robin" - WeightedRoundRobin string = "weighted_round_robin" - LeastConnection string = "least_conn" + RoundRobin string = "round_robin" + WeightedRoundRobin string = "weighted_round_robin" + LeastConnection string = "least_conn" + WeightedLeastConnection string = "weighted_least_conn" + Random string = "random" ) type LoadBalancer interface { diff --git a/loadbalancer/loadbalancer_test.go b/loadbalancer/loadbalancer_test.go index 6499444..05fe400 100644 --- a/loadbalancer/loadbalancer_test.go +++ b/loadbalancer/loadbalancer_test.go @@ -4,6 +4,8 @@ import "testing" func TestLoadBalancer(t *testing.T) { var lb LoadBalancer + + errFmt := "expected: %s, got: %s" targets := []string{"google.com", "bing.com"} // Round Robin @@ -11,17 +13,17 @@ func TestLoadBalancer(t *testing.T) { target := lb.ChooseTarget(targets) if target != targets[0] { - t.Errorf("expected: %s, got: %s", targets[0], target) + t.Errorf(errFmt, targets[0], target) } target = lb.ChooseTarget(targets) if target != targets[1] { - t.Errorf("expected: %s, got: %s", targets[1], target) + t.Errorf(errFmt, targets[1], target) } target = lb.ChooseTarget(targets) if target != targets[0] { - t.Errorf("expected: %s, got: %s", targets[0], target) + t.Errorf(errFmt, targets[0], target) } // Weighted Round Robin @@ -31,19 +33,19 @@ func TestLoadBalancer(t *testing.T) { for i := 0; i < weights[0]; i++ { target = lb.ChooseTarget(targets) if target != targets[0] { - t.Errorf("expected: %s, got: %s", targets[0], target) + t.Errorf(errFmt, targets[0], target) } } for i := 0; i < weights[1]; i++ { target = lb.ChooseTarget(targets) if target != targets[1] { - t.Errorf("expected: %s, got: %s", targets[1], target) + t.Errorf(errFmt, targets[1], target) } } // Least Connections - lb = NewLeastConnLoadBalancer(targets) + lb = NewLeastConnLoadBalancer(targets, nil) target = lb.ChooseTarget(targets) target2 := lb.ChooseTarget(targets) @@ -53,16 +55,33 @@ func TestLoadBalancer(t *testing.T) { target3 := lb.ChooseTarget(targets) if target2 != target3 { - t.Errorf("expected: %s, got: %s", target2, target3) + t.Errorf(errFmt, target2, target3) } lb.Done(target) target4 := lb.ChooseTarget(targets) if target != target4 { - t.Errorf("expected: %s, got: %s", target, target4) + t.Errorf(errFmt, target, target4) } lb.Done(target4) lb.Done(target3) + + // Weighted Least Connections + weights = []int{2, 3} + lb = NewLeastConnLoadBalancer(targets, weights) + + target = lb.ChooseTarget(targets) + + if target != targets[1] { + t.Errorf(errFmt, targets[1], target) + } + + target2 = lb.ChooseTarget(targets) + + if target2 != targets[0] { + t.Errorf(errFmt, targets[0], target2) + } + } diff --git a/loadbalancer/random.go b/loadbalancer/random.go new file mode 100644 index 0000000..bd89cc6 --- /dev/null +++ b/loadbalancer/random.go @@ -0,0 +1,20 @@ +package loadbalancer + +import ( + "math/rand" + "time" +) + +type RandomPolicy struct{} + +func NewRandomLoadBalancer() *RandomPolicy { + return &RandomPolicy{} +} + +func (p *RandomPolicy) ChooseTarget(targets []string) string { + rand.Seed(time.Now().UnixNano()) + + return targets[rand.Intn(len(targets))] +} + +func (p *RandomPolicy) Done(_ string) {} diff --git a/loadbalancer/weighted_roundrobin.go b/loadbalancer/weighted_roundrobin.go index 014110e..56ec968 100644 --- a/loadbalancer/weighted_roundrobin.go +++ b/loadbalancer/weighted_roundrobin.go @@ -2,13 +2,13 @@ package loadbalancer type WeightedRoundRobinPolicy struct { basePolicy - baseWeights []int + weights []int currentWeight int } func NewWRoundRobinLoadBalancer(weights []int) *WeightedRoundRobinPolicy { return &WeightedRoundRobinPolicy{ - baseWeights: weights, + weights: weights, currentWeight: weights[0], } } @@ -20,7 +20,7 @@ func (p *WeightedRoundRobinPolicy) ChooseTarget(targets []string) string { curr := p.currentIndex if p.currentWeight == 0 { - p.currentWeight = p.baseWeights[p.currentIndex] + p.currentWeight = p.weights[p.currentIndex] } p.currentWeight-- diff --git a/service/service.go b/service/service.go index 919e5b2..f6c303f 100644 --- a/service/service.go +++ b/service/service.go @@ -134,12 +134,16 @@ func (bs *BackendService) GetLoadBalancer() loadbalancer.LoadBalancer { func (bs *BackendService) setLoadBalancer() { switch bs.LoadBalancerPolicy.Type { + case loadbalancer.Random: + bs.loadBalancer = loadbalancer.NewRandomLoadBalancer() case loadbalancer.RoundRobin: bs.loadBalancer = loadbalancer.NewRoundRobinLoadBalancer() case loadbalancer.WeightedRoundRobin: bs.loadBalancer = loadbalancer.NewWRoundRobinLoadBalancer(bs.LoadBalancerPolicy.Options.Weights) case loadbalancer.LeastConnection: - bs.loadBalancer = loadbalancer.NewLeastConnLoadBalancer(bs.UpstreamTargets) + bs.loadBalancer = loadbalancer.NewLeastConnLoadBalancer(bs.UpstreamTargets, nil) + case loadbalancer.WeightedLeastConnection: + bs.loadBalancer = loadbalancer.NewLeastConnLoadBalancer(bs.UpstreamTargets, bs.LoadBalancerPolicy.Options.Weights) default: bs.loadBalancer = loadbalancer.NewRoundRobinLoadBalancer() }