আপনি যদি এমন পরিষেবা চালাচ্ছেন যা উচ্চ পরিমাণে ট্র্যাফিক পরিচালনা করতে সক্ষম হওয়া উচিত, তবে আপনি আপনার ব্যাকেন্ড সার্ভারগুলির মধ্যে সেই ট্র্যাফিকের অনেকটা ভারসাম্য বহন করতে পারেন. বাজারে অনেক উৎপাদন স্তরের লোড ভারসাম্যকারী রয়েছে (NGINX, HAProxy, ইত্যাদি), কিন্তু তারা কীভাবে কাজ করে তা জানতে ভাল জ্ঞান।
স্ট্যান্ডার্ড লাইব্রেরি ব্যবহার করে একটি সহজ HTTP লোড ভারসামক, এই বাস্তবায়নে, আমরা একটি রং রোবিন অ্যালগরিদম ব্যবহার করব যাতে একটি সংগ্রহের ব্যাকেন্ড সার্ভারগুলির মধ্যে প্রবেশকারী অনুরোধগুলি সমানভাবে বিতরণ করা যায়।
মৌলিক কাঠামো
প্রথমে, আমাদের কোর ডেটা গঠনগুলি সংজ্ঞায়িত করতে হবে. আমাদের লোড ভারসামক একাধিক ব্যাকেন্ড সার্ভার এবং তাদের স্বাস্থ্য অনুসরণ করবে:
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
}
এখন কয়েকটি গুরুত্বপূর্ণ বিষয় এখানে মনে রাখা উচিত:
- ব্যাকেন্ড স্ট্রেকটি তার URL এবং লাইভ স্ট্যাটাসের সাথে একটি একক ব্যাকেন্ড সার্ভার প্রতিনিধিত্ব করে।
- আমরা নিরাপদে আপডেট এবং একটি সমন্বিত পরিবেশে প্রতিটি ব্যাকেন্ডের জীবন্ত অবস্থা চেক করার জন্য একটি মিউটেক্স ব্যবহার করছি।
- LoadBalancer ব্যাকেন্ডের একটি তালিকা ট্র্যাক করে এবং রাউন্ড রোবিন অ্যালগরিদমের জন্য একটি ক্যালকুলেটর বজায় রাখে।
- আমরা একটি সামঞ্জস্যপূর্ণ পরিবেশে নিরাপদে কন্টার বৃদ্ধি করার জন্য পারমাণবিক অপারেশন ব্যবহার করি।
- NextBackend পদ্ধতি round-robin অ্যালগরিদম বাস্তবায়ন করে, অস্বাস্থ্যকর ব্যাকেন্ড বাইপিং।
স্বাস্থ্য পরীক্ষা
ব্যাকেন্ডের অসম্ভবতা সনাক্ত করা যেকোনো লোড ভারসামারারের গুরুত্বপূর্ণ ফাংশনগুলির মধ্যে একটি।
// 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()
}
}
}
এই স্বাস্থ্য পরীক্ষা সহজ:
- আমরা ব্যাকেন্ডের সাথে একটি TCP সংযোগ শুরু করার চেষ্টা করি।
- যদি এই সংযোগ সফল হয়, তাহলে ব্যাকেন্ড জীবিত; অন্যথায় এটি মারা গেছে।
- চেকটি HealthCheckPeriodically ফাংশনে নির্দিষ্ট সময়ের মধ্যে চালানো হয়।
উৎপাদন পরিবেশে, আপনি সম্ভবত একটি আরও উন্নত স্বাস্থ্য চেক করতে চান যা প্রকৃতপক্ষে একটি HTTP অনুরোধ কিছু নির্দিষ্ট চূড়ান্ত পয়েন্টে তৈরি করে, কিন্তু এটি আমাদের শুরু করে।
HTTP হ্যান্ডলার
চলুন এগিয়ে যাই এবং এইচটিটিপি ম্যানেজারটি বাস্তবায়ন করি যা অনুরোধগুলি গ্রহণ করবে এবং তাদের আমাদের ব্যাকেন্ডে রুট করবে:
// 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)
}
আর এখানেই ঘটে সেই জাদু:
- আমাদের রাউন্ড রোবিন অ্যালগরিদম আমাদের পরবর্তী উপলব্ধ ব্যাকেন্ড দেয়।
- যদি কোন ব্যাকেন্ড উপলব্ধ না হয়, তাহলে আমরা একটি 503 পরিষেবা অ্যাক্সেসযোগ্য ত্রুটি ফেরত দেব।
- অন্যথায়, আমরা Go এর অভ্যন্তরীণ বিপরীত প্রক্সি মাধ্যমে নির্বাচিত ব্যাকেন্ডে অনুরোধটি প্রক্সি করি।
দেখে নিন কিভাবেnet/http/httputil
প্যাকেজটি প্রদান করেReverseProxy
টাইপ, যা আমাদের জন্য HTTP অনুরোধগুলি প্রসেসিংয়ের সমস্ত জটিলতা পরিচালনা করে।
সবকিছু একসঙ্গে রাখা
অবশেষে আমরা বাস্তবায়ন করবmain
আমাদের লোড ব্যালেন্সার কনফিগার এবং চালু করার জন্য ফাংশন:
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)
}
}
এখন, প্রধান ফাংশনে কী ঘটেছে:
- আমরা কমান্ড লাইন ফ্ল্যাগের উপর ভিত্তি করে পোর্ট কনফিগার করি।
- আমরা সার্ভারগুলির একটি তালিকা তৈরি করেছি।
- 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
- আমরা প্রাথমিক স্বাস্থ্য পরীক্ষা করেছি।
- আমরা নিয়মিত স্বাস্থ্য পরীক্ষার জন্য একটি goroutine শুরু করি।
- অবশেষে, আমরা HTTP সার্ভারটি আমাদের লোড ভারসামক হিসাবে অপারেটর হিসাবে শুরু করি।
লোড ভারসাম্য পরীক্ষা
সুতরাং আবার আমাদের লোড ভারসামক পরীক্ষা করার জন্য, আমাদের কিছু ব্যাকেন্ড সার্ভার দরকার. একটি ব্যাকেন্ড সার্ভার একটি নিখুঁত বাস্তবায়ন এইভাবে দেখতে পারে:
// 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)
}
}
বিভিন্ন পোর্টে এই ব্যাকেন্ডের একাধিক ইন্সটাফরেক্স চালান:
go build -o backend backend.go
./backend -port 8081 &
./backend -port 8082 &
./backend -port 8083 &
এরপর তৈরি করুন এবং লোড ভারসাম্য বজায় রাখুন:
go build -o load-balancer main.go
./load-balancer
এখন, এটি পরীক্ষা করার জন্য কিছু অনুরোধ করুন:
curl http://localhost:8080/test
একাধিক অনুরোধ করুন এবং আপনি তাদের আপনার ব্যাকেন্ড সার্ভারগুলিতে রাউন্ড রোবিন মোডে বিতরণ করা দেখতে পাবেন।
সম্ভাব্য উন্নতি
এটি একটি ন্যূনতম বাস্তবায়ন, কিন্তু আপনি এটি উন্নত করতে অনেক কিছু করতে পারেন:
-
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.
আমরা একটি সহজ কিন্তু কার্যকরী HTTP লোড ভারসামক তৈরি করেছি, যা Go এর স্ট্যান্ডার্ড লাইব্রেরি ছাড়া কিছুই ব্যবহার করে না. এই উদাহরণটি নেটওয়ার্ক প্রোগ্রামিংয়ের গুরুত্বপূর্ণ ধারণা এবং Go এর ভিত্তিক নেটওয়ার্কিং বৈশিষ্ট্যগুলির ক্ষমতা দেখায়।
যদিও এটি উৎপাদনের জন্য প্রস্তুত নয়, এটি লোড ভারসামকগুলি কীভাবে কাজ করে তা বোঝার জন্য একটি শক্তিশালী ভিত্তি সরবরাহ করে।
উৎপাদন ব্যবহারের জন্য, আপনি আরো বৈশিষ্ট্যগুলি তৈরি করতে চান এবং শক্তিশালী ত্রুটি ব্যবস্থাপনা যোগ করতে চান, কিন্তু কোর কনফিগারেশনগুলি সব একই।
আপনি এখানে উৎস কোড খুঁজে পাবেনhttps://github.com/rezmoss/simple-load-balancer