14,587 okumalar
14,587 okumalar

Bu 150-Line Go Script Gerçekten Bir Tam Yük Dayanıklı

ile Rez Moss7m2025/04/23
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

Bu makalede yalnızca standart kitaplık kullanılarak Go'da basit bir HTTP yük dengesi nasıl oluşturacağınızı göstereceğiz. Backend sunucularında yuvarlak robin dağıtımını, başarısızlıkları tespit etmek için sağlık kontrolünü ve talep projesini gerçekleştirir - her şey yaklaşık 150 kod satırında. Go'da ağ kuramını öğrenmek için mükemmel
featured image - Bu 150-Line Go Script Gerçekten Bir Tam Yük Dayanıklı
Rez Moss HackerNoon profile picture

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:

  1. Backend Struct, URL ve canlı statüsüne sahip tek bir backend sunucusunu temsil eder.
  2. 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.
  3. LoadBalancer, arka uçların bir listesini izler ve yuvarlak robin algoritması için bir sayacı korur.
  4. Aynı ortamda sayıyı güvenli bir şekilde artırmak için atomik işlemleri kullanırız.
  5. 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:

  1. Backend ile bir TCP bağlantısı başlatmaya çalışıyoruz.
  2. Bu bağlantı başarılı olursa, arka uç canlıdır; aksi takdirde öldü.
  3. 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:

  1. Round-robin algoritmamız bize mevcut bir sonraki arka planı verir.
  2. Eğer herhangi bir arka kapak mevcut değilse, 503 Service Unavailable hatasını döndürüyoruz.
  3. 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/httputilpaketi sağlarReverseProxyBizim için HTTP talebinin tüm karmaşıklığını ele alan bir tür.

Bütün bunları bir araya getirmek

Son olarak, uygulayalımmainYü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:

  1. Parsel ettiğimiz komut satırı bayraklarına göre port yapılandırıyoruz.
  2. Backend sunucularının bir listesini oluşturduk.
  3. 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
  4. İlk sağlık kontrolünü yapıyoruz.
  5. Düzenli sağlık kontrolleri için bir goroutine başlatıyoruz.
  6. 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:


  1. Different balancing algorithms: Implement weighted round-robin, least connections, or IP hash-based selection.

  2. Better health checking: Make full HTTP requests to a health endpoint instead of just checking TCP connectivity.

  3. Metrics collection: Track request counts, response times, and error rates for each backend.

  4. Sticky sessions: Ensure requests from the same client always go to the same backend.

  5. TLS support: Add HTTPS for secure connections.

  6. 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

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks