Skip to content

Commit

Permalink
Merge pull request #72 from amityahav/load-balancing-alogrithms
Browse files Browse the repository at this point in the history
  • Loading branch information
amityahav authored Mar 22, 2023
2 parents 6c9f3f5 + 50f05e2 commit c885605
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 20 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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)
}
Expand Down
21 changes: 18 additions & 3 deletions loadbalancer/least_connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
8 changes: 5 additions & 3 deletions loadbalancer/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
35 changes: 27 additions & 8 deletions loadbalancer/loadbalancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ import "testing"

func TestLoadBalancer(t *testing.T) {
var lb LoadBalancer

errFmt := "expected: %s, got: %s"
targets := []string{"google.com", "bing.com"}

// Round Robin
lb = NewRoundRobinLoadBalancer()

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
Expand All @@ -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)
Expand All @@ -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)
}

}
20 changes: 20 additions & 0 deletions loadbalancer/random.go
Original file line number Diff line number Diff line change
@@ -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) {}
6 changes: 3 additions & 3 deletions loadbalancer/weighted_roundrobin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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],
}
}
Expand All @@ -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--
Expand Down
6 changes: 5 additions & 1 deletion service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down

0 comments on commit c885605

Please sign in to comment.