Skip to content

Commit 5d8e4be

Browse files
author
SamSyntax
committed
Load balancer
0 parents  commit 5d8e4be

File tree

7 files changed

+255
-0
lines changed

7 files changed

+255
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/server
2+
lb
3+
/bin

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build:
2+
go build -o ./lb *.go && ./lb

README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
Go Load Balancer with Dynamic Server Spawning
2+
This Go project implements a simple load balancer that forwards requests to multiple backend servers. The project dynamically spawns HTTP servers and balances the incoming requests using round-robin scheduling.
3+
4+
Overview
5+
The project consists of:
6+
7+
A server spawner that creates multiple backend servers listening on different ports.
8+
A load balancer that receives incoming requests on a designated port and forwards them to one of the spawned servers using a round-robin mechanism.
9+
Each server responds with a message indicating the port it is serving on.
10+
Features
11+
Dynamic server spawning with a given number of servers.
12+
Load balancing using the round-robin technique.
13+
Each server runs independently and responds with a simple message.
14+
Basic reverse proxy functionality for forwarding requests to backend servers.
15+
Project Structure
16+
Files
17+
main.go: The entry point of the application that spawns servers and starts the load balancer.
18+
loadbalancer.go: Contains the logic for the load balancer and the backend server configurations.
19+
Code Explanation
20+
Server Spawner
21+
The Spawner function spawns a specified number of backend HTTP servers. Each server is assigned a unique port and name. Servers are spawned concurrently in goroutines to avoid blocking the main thread.
22+
23+
```go
24+
func Spawner(amt int) []LbServer {
25+
servers := make([]LbServer, 0, amt)
26+
for i := 0; i < amt; i++ {
27+
name := fmt.Sprintf("Server %v", i)
28+
srv := Server(":"+strconv.Itoa(8000+i), name)
29+
servers = append(servers, srv)
30+
}
31+
return servers
32+
}
33+
```
34+
Server Initialization
35+
Each server is initialized using the Server function. It creates an HTTP server that listens on the given port and serves a simple response message.
36+
37+
```go
38+
func Server(port, name string) LbServer {
39+
srv := NewLbServer("http://localhost" + port)
40+
srv.name = name
41+
// Serve HTTP requests in a goroutine
42+
go func() {
43+
http.ListenAndServe(port, mux)
44+
}()
45+
return *srv
46+
}
47+
```
48+
Load Balancer
49+
The LoadBalancer struct holds a list of backend servers and distributes incoming requests among them using a round-robin algorithm.
50+
51+
```go
52+
func (lb *LoadBalancer) GetNextAvailableServer() LbServer {
53+
server := lb.servers[lb.roundRobinCount % len(lb.servers)]
54+
lb.roundRobinCount++
55+
return server
56+
}
57+
```
58+
The load balancer listens on a specified port (e.g., 7000), and forwards requests to the next available backend server.
59+
60+
```go
61+
func (lb *LoadBalancer) ServeProxy(w http.ResponseWriter, r *http.Request) {
62+
targetServer := lb.GetNextAvailableServer()
63+
fmt.Printf("forwarding to %q\n", targetServer.Address())
64+
targetServer.Serve(w, r)
65+
}
66+
```
67+
Running the Code
68+
Clone this repository or copy the source files into your Go workspace.
69+
70+
Run the code using:
71+
72+
```bash
73+
go *.go -o ./lb && ./lb
74+
```
75+
OR
76+
77+
```bash
78+
make build
79+
```
80+
The load balancer will listen on port 7000. Open your browser or use curl to send a request to localhost:7000:
81+
82+
```bash
83+
curl http://localhost:7000
84+
```
85+
The request will be forwarded to one of the backend servers, and you will see a response indicating the server's port.
86+
87+
Example Output
88+
```bash Serving requests at localhost:7000
89+
Spawning server: Server 0 at localhost:8000
90+
Spawning server: Server 1 at localhost:8001
91+
Spawning server: Server 2 at localhost:8002
92+
Spawning server: Server 3 at localhost:8003
93+
Spawning server: Server 4 at localhost:8004
94+
forwarding to "localhost:8001"
95+
forwarding to "localhost:8002"
96+
forwarding to "localhost:8003"
97+
forwarding to "localhost:8004"
98+
forwarding to "localhost:8000"
99+
```
100+
Customization
101+
Number of Servers: You can modify the number of servers spawned by changing the argument passed to the Spawner function in main.go.
102+
Ports: The backend servers listen on ports 8000 and higher. You can modify the port range in the Spawner function.
103+
Dependencies
104+
This project does not require any third-party dependencies. It only uses the Go standard library.
105+
106+
License
107+
This project is licensed under the MIT License.

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/samsyntax/go-lb
2+
3+
go 1.22.7

loadbalancer.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httputil"
7+
"net/url"
8+
"os"
9+
)
10+
11+
type ServerInterface interface {
12+
Address() string
13+
IsAlive() bool
14+
Serve(w http.ResponseWriter, r *http.Request)
15+
}
16+
17+
type LbServer struct {
18+
addr string
19+
proxy *httputil.ReverseProxy
20+
name string
21+
}
22+
23+
func (s LbServer) Address() string {
24+
return s.addr
25+
}
26+
27+
func (s LbServer) IsAlive() bool {
28+
return true
29+
}
30+
31+
func (s LbServer) Serve(w http.ResponseWriter, r *http.Request) {
32+
s.proxy.ServeHTTP(w, r)
33+
}
34+
35+
func NewLbServer(addr string) *LbServer {
36+
serverUrl, err := url.Parse(addr)
37+
if err != nil {
38+
fmt.Printf("Failed to parse url: %v\n", err)
39+
os.Exit(1)
40+
}
41+
return &LbServer{
42+
addr: addr,
43+
proxy: httputil.NewSingleHostReverseProxy(serverUrl),
44+
}
45+
}
46+
47+
type LoadBalancer struct {
48+
port string
49+
roundRobinCount int
50+
servers []LbServer
51+
}
52+
53+
func NewLoadBalancer(port string, servers []LbServer) *LoadBalancer {
54+
return &LoadBalancer{
55+
port: port,
56+
roundRobinCount: 0,
57+
servers: servers,
58+
}
59+
}
60+
61+
func (lb *LoadBalancer) GetNextAvailableServer() LbServer {
62+
server := lb.servers[lb.roundRobinCount%len(lb.servers)]
63+
for !server.IsAlive() {
64+
lb.roundRobinCount++
65+
server = lb.servers[lb.roundRobinCount%len(lb.servers)]
66+
}
67+
68+
lb.roundRobinCount++
69+
return server
70+
}
71+
72+
func (lb *LoadBalancer) ServeProxy(w http.ResponseWriter, r *http.Request) {
73+
targetServer := lb.GetNextAvailableServer()
74+
fmt.Printf("forwarding to %q\n", targetServer.Address())
75+
targetServer.Serve(w, r)
76+
}

main.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
)
7+
8+
func main() {
9+
servers := Spawner(5)
10+
lb := NewLoadBalancer("7000", servers)
11+
handleRedirect := func(w http.ResponseWriter, r *http.Request) {
12+
lb.ServeProxy(w, r)
13+
}
14+
http.HandleFunc("/", handleRedirect)
15+
16+
fmt.Printf("serving requests at localhost:%s\n", lb.port)
17+
18+
http.ListenAndServe(":"+lb.port, nil)
19+
select {}
20+
}

spawner.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"strconv"
8+
)
9+
10+
func Server(port, name string) LbServer {
11+
srv := NewLbServer("http://localhost" + port)
12+
srv.name = name
13+
14+
mux := http.NewServeMux()
15+
16+
handler := func(w http.ResponseWriter, r *http.Request) {
17+
inf := fmt.Sprintf("Serving on port %s", port)
18+
fmt.Println(inf)
19+
io.WriteString(w, inf)
20+
}
21+
22+
mux.HandleFunc("/", handler)
23+
fmt.Printf("Spawning server: %s at %s\n", srv.name, srv.addr)
24+
25+
go func() {
26+
err := http.ListenAndServe(port, mux)
27+
if err != nil {
28+
fmt.Printf("Error starting server on port %s: %v\n", port, err)
29+
}
30+
}()
31+
32+
return *srv
33+
34+
}
35+
func Spawner(amt int) []LbServer {
36+
servers := make([]LbServer, 0, amt)
37+
for i := 0; i < amt; i++ {
38+
name := fmt.Sprintf("Server %v", i)
39+
port := ":" + strconv.Itoa(8000+i)
40+
srv := Server(port, name)
41+
servers = append(servers, srv)
42+
}
43+
return servers
44+
}

0 commit comments

Comments
 (0)