2019-09-13 20:00:06 +02:00
package tcp
import (
2024-02-07 17:14:07 +01:00
"errors"
2019-09-13 20:00:06 +02:00
"sync"
2022-11-21 18:36:05 +01:00
"github.com/rs/zerolog/log"
2019-09-13 20:00:06 +02:00
)
2024-08-12 14:08:05 +02:00
var errNoServersInPool = errors . New ( "no servers in the pool" )
2019-09-13 20:00:06 +02:00
type server struct {
Handler
weight int
}
2020-05-11 12:06:07 +02:00
// WRRLoadBalancer is a naive RoundRobin load balancer for TCP services.
2019-09-13 20:00:06 +02:00
type WRRLoadBalancer struct {
servers [ ] server
2021-11-08 17:58:12 +01:00
lock sync . Mutex
2019-09-13 20:00:06 +02:00
currentWeight int
index int
}
2020-05-11 12:06:07 +02:00
// NewWRRLoadBalancer creates a new WRRLoadBalancer.
2019-09-13 20:00:06 +02:00
func NewWRRLoadBalancer ( ) * WRRLoadBalancer {
return & WRRLoadBalancer {
index : - 1 ,
}
}
2020-05-11 12:06:07 +02:00
// ServeTCP forwards the connection to the right service.
2019-09-13 20:00:06 +02:00
func ( b * WRRLoadBalancer ) ServeTCP ( conn WriteCloser ) {
2021-11-08 17:58:12 +01:00
b . lock . Lock ( )
2019-09-13 20:00:06 +02:00
next , err := b . next ( )
2021-11-08 17:58:12 +01:00
b . lock . Unlock ( )
2019-09-13 20:00:06 +02:00
if err != nil {
2024-08-12 14:08:05 +02:00
if ! errors . Is ( err , errNoServersInPool ) {
2024-08-28 16:11:38 +02:00
log . Error ( ) . Err ( err ) . Msg ( "Error during load balancing" )
2024-08-12 14:08:05 +02:00
}
_ = conn . Close ( )
2021-11-08 17:58:12 +01:00
return
2019-09-13 20:00:06 +02:00
}
2021-11-08 17:58:12 +01:00
2019-09-13 20:00:06 +02:00
next . ServeTCP ( conn )
}
2020-05-11 12:06:07 +02:00
// AddServer appends a server to the existing list.
2019-09-13 20:00:06 +02:00
func ( b * WRRLoadBalancer ) AddServer ( serverHandler Handler ) {
w := 1
b . AddWeightServer ( serverHandler , & w )
}
2020-05-11 12:06:07 +02:00
// AddWeightServer appends a server to the existing list with a weight.
2019-09-13 20:00:06 +02:00
func ( b * WRRLoadBalancer ) AddWeightServer ( serverHandler Handler , weight * int ) {
2021-11-08 17:58:12 +01:00
b . lock . Lock ( )
defer b . lock . Unlock ( )
2019-09-13 20:00:06 +02:00
w := 1
if weight != nil {
w = * weight
}
b . servers = append ( b . servers , server { Handler : serverHandler , weight : w } )
}
func ( b * WRRLoadBalancer ) maxWeight ( ) int {
2024-08-28 15:00:06 +02:00
maximum := - 1
2019-09-13 20:00:06 +02:00
for _ , s := range b . servers {
2024-08-28 15:00:06 +02:00
if s . weight > maximum {
maximum = s . weight
2019-09-13 20:00:06 +02:00
}
}
2024-08-28 15:00:06 +02:00
return maximum
2019-09-13 20:00:06 +02:00
}
func ( b * WRRLoadBalancer ) weightGcd ( ) int {
divisor := - 1
for _ , s := range b . servers {
if divisor == - 1 {
divisor = s . weight
} else {
divisor = gcd ( divisor , s . weight )
}
}
return divisor
}
func gcd ( a , b int ) int {
for b != 0 {
a , b = b , a % b
}
return a
}
func ( b * WRRLoadBalancer ) next ( ) ( Handler , error ) {
if len ( b . servers ) == 0 {
2024-08-12 14:08:05 +02:00
return nil , errNoServersInPool
2019-09-13 20:00:06 +02:00
}
// The algo below may look messy, but is actually very simple
// it calculates the GCD and subtracts it on every iteration, what interleaves servers
// and allows us not to build an iterator every time we readjust weights
// Maximum weight across all enabled servers
2024-08-28 15:00:06 +02:00
maximum := b . maxWeight ( )
if maximum == 0 {
2024-02-07 17:14:07 +01:00
return nil , errors . New ( "all servers have 0 weight" )
2021-11-08 17:58:12 +01:00
}
// GCD across all enabled servers
gcd := b . weightGcd ( )
2019-09-13 20:00:06 +02:00
for {
b . index = ( b . index + 1 ) % len ( b . servers )
if b . index == 0 {
b . currentWeight -= gcd
if b . currentWeight <= 0 {
2024-08-28 15:00:06 +02:00
b . currentWeight = maximum
2019-09-13 20:00:06 +02:00
}
}
srv := b . servers [ b . index ]
if srv . weight >= b . currentWeight {
return srv , nil
}
}
}