Eğer yüksek miktarda trafiği halledebilecek hizmetler çalıştırıyorsanız, arka uç sunucularınız arasında bu trafiğin büyük bir kısmını dengeleyebilirsiniz. piyasada pek çok üretim sınıfı yük dengeli var (NGINX, HAProxy, vb.), ancak bunların nasıl çalıştığını bilmek sahne arkasında iyi bir bilgi.
Standart bir kütüphane kullanarak basit bir HTTP yük dengesi, Bu uygulamada, gelen talepleri bir koleksiyon arka uç sunucuları arasında eşit bir şekilde dağıtmak için bir yuvarlak robin algoritması kullanacağız.
Temel yapısı
İlk olarak, çekirdek veri yapılarımızı tanımlamanız gerekir. yüklenme dengelerimiz birden fazla arka uç sunucularını ve onların sağlığını izleyecektir:
package main
import (
"flag"
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"sync/atomic"
"time"
)
// Backend represents a backend server
type Backend struct {
URL *url.URL
Alive bool
mux sync.RWMutex
ReverseProxy *httputil.ReverseProxy
}
// SetAlive updates the alive status of backend
func (b *Backend) SetAlive(alive bool) {
b.mux.Lock()
b.Alive = alive
b.mux.Unlock()
}
// IsAlive returns true when backend is alive
func (b *Backend) IsAlive() (alive bool) {
b.mux.RLock()
alive = b.Alive
b.mux.RUnlock()
return
}
// LoadBalancer represents a load balancer
type LoadBalancer struct {
backends []*Backend
current uint64
}
// NextBackend returns the next available backend to handle the request
func (lb *LoadBalancer) NextBackend() *Backend {
// Simple round-robin
next := atomic.AddUint64(&lb.current, uint64(1)) % uint64(len(lb.backends))
// Find the next available backend
for i := 0; i < len(lb.backends); i++ {
idx := (int(next) + i) % len(lb.backends)
if lb.backends[idx].IsAlive() {
return lb.backends[idx]
}
}
return nil
}
Şimdi göz önünde bulundurulması gereken birkaç önemli nokta var:
- Backend Struct, URL ve canlı statüsüne sahip tek bir backend sunucusunu temsil eder.
- Güvenli bir şekilde güncelleştirmek ve eşzamanlı bir ortamda her backend'in canlı durumunu kontrol etmek için bir mutex kullanıyoruz.
- LoadBalancer, arka uçların bir listesini izler ve yuvarlak robin algoritması için bir sayacı korur.
- Aynı ortamda sayıyı güvenli bir şekilde artırmak için atomik işlemleri kullanırız.
- NextBackend yöntemi, sağlıklı olmayan arka uçları kaçırarak yuvarlak robin algoritmasını uygular.
Sağlık Kontrolü
Backend'in kullanılamazlığını tespit etmek, herhangi bir yük dengeleme cihazının kritik işlevlerinden biridir. Çok basit bir sağlık kontrol mekanizması uygulanır:
// isBackendAlive checks whether a backend is alive by establishing a TCP connection
func isBackendAlive(u *url.URL) bool {
timeout := 2 * time.Second
conn, err := net.DialTimeout("tcp", u.Host, timeout)
if err != nil {
log.Printf("Site unreachable: %s", err)
return false
}
defer conn.Close()
return true
}
// HealthCheck pings the backends and updates their status
func (lb *LoadBalancer) HealthCheck() {
for _, b := range lb.backends {
status := isBackendAlive(b.URL)
b.SetAlive(status)
if status {
log.Printf("Backend %s is alive", b.URL)
} else {
log.Printf("Backend %s is dead", b.URL)
}
}
}
// HealthCheckPeriodically runs a routine health check every interval
func (lb *LoadBalancer) HealthCheckPeriodically(interval time.Duration) {
t := time.NewTicker(interval)
for {
select {
case <-t.C:
lb.HealthCheck()
}
}
}
Bu sağlık kontrolü basittir:
- Backend ile bir TCP bağlantısı başlatmaya çalışıyoruz.
- Bu bağlantı başarılı olursa, arka uç canlıdır; aksi takdirde öldü.
- Kontrol, HealthCheckPeriodically işlevinde belirtilen bir aralıkta gerçekleştirilir.
Üretim ortamında, aslında belirli bir son noktaya bir HTTP isteği yapan daha gelişmiş bir sağlık kontrolünü istersiniz, ancak bu bizi başlatır.
HTTP Handler Hakkında
Lütfen ilerlemeye devam edelim ve isteği alacak HTTP işlemciyi uygulayalım ve onları arka uçlarımıza yönlendirelim:
// ServeHTTP implements the http.Handler interface for the LoadBalancer
func (lb *LoadBalancer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
backend := lb.NextBackend()
if backend == nil {
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
return
}
// Forward the request to the backend
backend.ReverseProxy.ServeHTTP(w, r)
}
İşte büyünün gerçekleştiği yer:
- Round-robin algoritmamız bize mevcut bir sonraki arka planı verir.
- Eğer herhangi bir arka kapak mevcut değilse, 503 Service Unavailable hatasını döndürüyoruz.
- Aksi takdirde, isteği Go'nun içi ters proxy aracılığıyla seçilen arka ucuna proxy ederiz.
Nasıl görünüyor dikkatnet/http/httputil
paketi sağlarReverseProxy
Bizim için HTTP talebinin tüm karmaşıklığını ele alan bir tür.
Bütün bunları bir araya getirmek
Son olarak, uygulayalımmain
Yük dengelememizi yapılandırmak ve başlatmak için:
func main() {
// Parse command line flags
port := flag.Int("port", 8080, "Port to serve on")
flag.Parse()
// Configure backends
serverList := []string{
"http://localhost:8081",
"http://localhost:8082",
"http://localhost:8083",
}
// Create load balancer
lb := LoadBalancer{}
// Initialize backends
for _, serverURL := range serverList {
url, err := url.Parse(serverURL)
if err != nil {
log.Fatal(err)
}
proxy := httputil.NewSingleHostReverseProxy(url)
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Error: %v", err)
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
}
lb.backends = append(lb.backends, &Backend{
URL: url,
Alive: true,
ReverseProxy: proxy,
})
log.Printf("Configured backend: %s", url)
}
// Initial health check
lb.HealthCheck()
// Start periodic health check
go lb.HealthCheckPeriodically(time.Minute)
// Start server
server := http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: &lb,
}
log.Printf("Load Balancer started at :%d\n", *port)
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
İşte ana fonksiyonda neler oluyor:
- Parsel ettiğimiz komut satırı bayraklarına göre port yapılandırıyoruz.
- Backend sunucularının bir listesini oluşturduk.
- For each backend server, we:
- Parse the URL
- Create a reverse proxy for that backend
- Add error handling for when a backend fails
- Add the backend to our load balancer
- İlk sağlık kontrolünü yapıyoruz.
- Düzenli sağlık kontrolleri için bir goroutine başlatıyoruz.
- Son olarak, HTTP sunucusunu işlemci olarak yük dengelerimizle başlatıyoruz.
Load Balancer Kullanımı
Bu yüzden tekrar yük dengelememizi test etmek için bazı backend sunucularına ihtiyacımız var. bir backend sunucunun nazik bir uygulanması şu şekilde görünebilir:
// Save this to backend.go
package main
import (
"flag"
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := flag.Int("port", 8081, "Port to serve on")
flag.Parse()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
hostname, _ := os.Hostname()
fmt.Fprintf(w, "Backend server on port %d, host: %s, Request path: %s\n", *port, hostname, r.URL.Path)
})
log.Printf("Backend started at :%d\n", *port)
if err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil); err != nil {
log.Fatal(err)
}
}
Farklı portlarda bu backend'in birkaç örneğini çalıştırın:
go build -o backend backend.go
./backend -port 8081 &
./backend -port 8082 &
./backend -port 8083 &
Ardından yük dengesini oluşturun ve çalıştırın:
go build -o load-balancer main.go
./load-balancer
Şimdi, test etmek için birkaç isteği yapın:
curl http://localhost:8080/test
Birden fazla isteği yapın ve bunları arka taraf sunucularınızda yuvarlak bir şekilde dağıtıldığını göreceksiniz.
Potansiyel iyileştirmeler
Bu minimum bir uygulama, ancak bunu geliştirmek için yapabileceğiniz birçok şey var:
-
Different balancing algorithms: Implement weighted round-robin, least connections, or IP hash-based selection.
-
Better health checking: Make full HTTP requests to a health endpoint instead of just checking TCP connectivity.
-
Metrics collection: Track request counts, response times, and error rates for each backend.
-
Sticky sessions: Ensure requests from the same client always go to the same backend.
-
TLS support: Add HTTPS for secure connections.
-
Dynamic configuration: Allow updating the backend list without restarting.
Go’nun standart kütüphanesinden başka bir şey kullanarak basit ama etkili bir HTTP yük dengeli oluşturduk.Bu örnek, ağ programlamasındaki önemli kavramları ve Go’nun entegre ağ özelliklerinin gücünü gösteriyor.
Bu üretime hazır değilken, yük dengeleme cihazlarının nasıl çalıştığını anlamak için sağlam bir temel sağlar. Tam çözümü yaklaşık 150 kod satırı - Go'nun ifade gücüne ve standart kütüphanesinin gücüne dair kanıt.
Üretim kullanımında, daha fazla özellik geliştirmek ve güçlü hata yönetimi eklemek istiyorsunuz, ancak temel kavramlar hepsi aynıdır.Bu temel ilkelerin anlayışı, yol boyunca kullanacağınız herhangi bir yük dengeleme cihazını daha iyi yapılandırmanıza ve düzeltmenize hazırlayacaktır.
Buradan kaynak kodunu bulabilirsinizhttps://github.com/rezmoss/simple-load-balancer