Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ type Backend struct {
healthy atomic.Bool
}

func NewBackend(url string) *Backend {
return &Backend{url: url}
type BackendOptions struct {
Url string
}

func NewBackend(opts BackendOptions) *Backend {
return &Backend{url: opts.Url}
}

func (b *Backend) GetUrl() string {
Expand All @@ -22,6 +26,10 @@ func (b *Backend) IsHealthy() bool {
return b.healthy.Load()
}

func (b *Backend) SetHealth(isHealthy bool) {
b.healthy.Store(isHealthy)
}

type BackendPool struct {
bs []*Backend
mu sync.RWMutex
Expand Down
9 changes: 9 additions & 0 deletions config/config_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
port: 8080
backends:
- {url: "localhost:80", weight: 10}
- {url: "localhost:8081", weight: 10}
algorithm: "round-robin"
timeouts:
dial: 10s
connect: 20s
shutdown: 30s
50 changes: 50 additions & 0 deletions config/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package config

import (
"fmt"
"go.yaml.in/yaml/v4"
"os"
"time"
)

type BackendCfg struct {
Url string `yaml:"url"`
Weight int `yaml:"weight"`
}

type TimeoutsCfg struct {
Dial time.Duration `yaml:"dial"`
Connect time.Duration `yaml:"connect"`
Shutdown time.Duration `yaml:"shutdown"`
}

type Config struct {
Port int `yaml:"port"`
Algorithm string `yaml:"algorithm"`
Backends []BackendCfg `yaml:"backends"`
Timeouts TimeoutsCfg `yaml:"timeouts"`
}

func Load(filePath string) Config {
var conf Config
data, err := os.ReadFile(filePath)
if err != nil {
panic(err)
}
if err = yaml.Unmarshal(data, &conf); err != nil {
panic(err)
}
if err = conf.validate(); err != nil {
panic(err)
}
return conf
}

func (c *Config) validate() error {
for i, b := range c.Backends {
if b.Url == "" {
return fmt.Errorf("backend %d: url required", i)
}
}
return nil
}
11 changes: 11 additions & 0 deletions config/loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package config

import (
"fmt"
"testing"
)

func TestLoad(t *testing.T) {
config := Load("config_test.yaml")
fmt.Println(config)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module load-balancer

go 1.24.0

require go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
39 changes: 28 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"load-balancer/backend"
"load-balancer/config"
"load-balancer/listener"
"load-balancer/proxy"
"load-balancer/router"
Expand All @@ -13,22 +14,38 @@ import (
"os"
"os/signal"
"syscall"
"time"
)

func main() {
port := 8080
conf := config.Load("config/config_test.yaml")

host := "[::1]"
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", conf.Port))
if err != nil {
log.Fatalf("failed to listen on port %d: %v", port, err)
log.Fatalf("failed to listen on port %d: %v", conf.Port, err)
}

backends := make([]*backend.Backend, 0, len(conf.Backends))
for i, b := range conf.Backends {
backends = append(backends, backend.NewBackend(backend.BackendOptions{
Url: b.Url}))
backends[i].SetHealth(true)
}
b := backend.NewBackend("localhost:80")
b1 := backend.NewBackend("localhost:8081")
bp := backend.NewBackendPool([]*backend.Backend{b, b1})
algo := roundrobin.NewRoundRobin(bp)
r := router.NewRouter(host+fmt.Sprintf(":%d", port), algo)
p := proxy.NewProxy(r)
bp := backend.NewBackendPool(backends)

var algo router.AlgoIO
switch conf.Algorithm {
case "round-robin":
algo = roundrobin.NewRoundRobin(bp)
default:
panic("unknown algorithm")
}

r := router.NewRouter(host+fmt.Sprintf(":%d", conf.Port), algo)
p := proxy.NewProxy(r, proxy.ProxyOptions{
DialTimeout: conf.Timeouts.Dial,
ConnTimeout: conf.Timeouts.Connect,
})
l := listener.NewListener(p)

go l.Listen(ln)
Expand All @@ -37,7 +54,7 @@ func main() {
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
<-sigCh

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), conf.Timeouts.Shutdown)
defer cancel()

l.GracefulShutdown(ctx)
Expand Down
11 changes: 8 additions & 3 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ type Proxy struct {
connTimeout time.Duration
}

func NewProxy(rt RouterIO) *Proxy {
type ProxyOptions struct {
DialTimeout time.Duration
ConnTimeout time.Duration
}

func NewProxy(rt RouterIO, opts ProxyOptions) *Proxy {
if rt == nil {
panic("router cannot be nil")
}
return &Proxy{
router: rt,
dialTimeout: 10 * time.Second,
connTimeout: 20 * time.Second,
dialTimeout: opts.DialTimeout,
connTimeout: opts.ConnTimeout,
}
}

Expand Down
6 changes: 3 additions & 3 deletions router/roundrobin/round_robin.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ type RoundRobin struct {
mu sync.Mutex
}

func NewRoundRobin(bs BackendPoolIO) *RoundRobin {
if bs == nil {
func NewRoundRobin(bp BackendPoolIO) *RoundRobin {
if bp == nil {
panic("backend pool cannot be nil")
}
return &RoundRobin{bp: bs}
return &RoundRobin{bp: bp}
}

func (rr *RoundRobin) GetBackend() (*backend.Backend, error) {
Expand Down
Loading