From 1391a9f2ba67b19222c59964577f6eb18fe130ec Mon Sep 17 00:00:00 2001 From: ayahav Date: Mon, 20 Mar 2023 18:31:43 +0200 Subject: [PATCH 1/4] added random algo --- README.md | 6 ++++++ api.go | 3 ++- loadbalancer/loadbalancer.go | 1 + loadbalancer/random.go | 20 ++++++++++++++++++++ service/service.go | 2 ++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 loadbalancer/random.go diff --git a/README.md b/README.md index 98d635e..7dd658a 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": { diff --git a/api.go b/api.go index a5d26ae..b885eb1 100644 --- a/api.go +++ b/api.go @@ -171,6 +171,8 @@ 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: if len(s.LoadBalancerPolicy.Options.Weights) != len(s.UpstreamTargets) { @@ -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/loadbalancer.go b/loadbalancer/loadbalancer.go index baaf049..86ab4b7 100644 --- a/loadbalancer/loadbalancer.go +++ b/loadbalancer/loadbalancer.go @@ -8,6 +8,7 @@ const ( RoundRobin string = "round_robin" WeightedRoundRobin string = "weighted_round_robin" LeastConnection string = "least_conn" + Random string = "random" ) type LoadBalancer interface { 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/service/service.go b/service/service.go index 8942176..58aa760 100644 --- a/service/service.go +++ b/service/service.go @@ -134,6 +134,8 @@ 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: From 160f8641802923c4515e9610466674791ef269e5 Mon Sep 17 00:00:00 2001 From: ayahav Date: Mon, 20 Mar 2023 18:31:47 +0200 Subject: [PATCH 2/4] added random algo --- services.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 services.yaml diff --git a/services.yaml b/services.yaml new file mode 100644 index 0000000..418fd35 --- /dev/null +++ b/services.yaml @@ -0,0 +1,14 @@ +- name: test_service + scheme: http + upstreamTargets: + - http://localhost:5000 + - http://localhost:5001 + - http://localhost:5002 + path: /api + domain: "127.0.0.1" + healthCheck: /health + retryAttempts: 3 + timeout: 10ns + maxIdleTime: 0s + loadBalancerPolicy: + type: random From 7b9fe76a3bfbcebce3f774d54e0439fefd5a3cc8 Mon Sep 17 00:00:00 2001 From: Amit Yahav <74831037+amityahav@users.noreply.github.com> Date: Mon, 20 Mar 2023 18:37:48 +0200 Subject: [PATCH 3/4] Delete services.yaml --- services.yaml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 services.yaml diff --git a/services.yaml b/services.yaml deleted file mode 100644 index 418fd35..0000000 --- a/services.yaml +++ /dev/null @@ -1,14 +0,0 @@ -- name: test_service - scheme: http - upstreamTargets: - - http://localhost:5000 - - http://localhost:5001 - - http://localhost:5002 - path: /api - domain: "127.0.0.1" - healthCheck: /health - retryAttempts: 3 - timeout: 10ns - maxIdleTime: 0s - loadBalancerPolicy: - type: random From 5ebd59cc21e1b27fb8fabd4e580fb97cb5c08021 Mon Sep 17 00:00:00 2001 From: Amit Yahav Date: Tue, 21 Mar 2023 19:33:33 +0200 Subject: [PATCH 4/4] Added Weighted Least Conn policy --- README.md | 9 ++++++++ api.go | 2 +- loadbalancer/least_connections.go | 21 ++++++++++++++--- loadbalancer/loadbalancer.go | 9 ++++---- loadbalancer/loadbalancer_test.go | 35 ++++++++++++++++++++++------- loadbalancer/weighted_roundrobin.go | 6 ++--- service/service.go | 4 +++- services.yaml | 14 ------------ 8 files changed, 66 insertions(+), 34 deletions(-) delete mode 100644 services.yaml diff --git a/README.md b/README.md index 1b3e084..a28e04e 100644 --- a/README.md +++ b/README.md @@ -247,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 b885eb1..bc99100 100644 --- a/api.go +++ b/api.go @@ -174,7 +174,7 @@ func validateLoadBalancerPolicy(s *service.BackendService) error { 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") } 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 86ab4b7..18072a7 100644 --- a/loadbalancer/loadbalancer.go +++ b/loadbalancer/loadbalancer.go @@ -5,10 +5,11 @@ import ( ) const ( - RoundRobin string = "round_robin" - WeightedRoundRobin string = "weighted_round_robin" - LeastConnection string = "least_conn" - Random string = "random" + 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/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 ff127f0..f6c303f 100644 --- a/service/service.go +++ b/service/service.go @@ -141,7 +141,9 @@ func (bs *BackendService) setLoadBalancer() { 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() } diff --git a/services.yaml b/services.yaml deleted file mode 100644 index 418fd35..0000000 --- a/services.yaml +++ /dev/null @@ -1,14 +0,0 @@ -- name: test_service - scheme: http - upstreamTargets: - - http://localhost:5000 - - http://localhost:5001 - - http://localhost:5002 - path: /api - domain: "127.0.0.1" - healthCheck: /health - retryAttempts: 3 - timeout: 10ns - maxIdleTime: 0s - loadBalancerPolicy: - type: random