分布式部署学习记录,使用docker设计镜像下载、科学上网问题,之后再尝试docker方案。
个人觉得自己造轮子,手写也挺好的,尤其在学习阶段。

先贴代码:“/”前为文件夹名称
loadbalancer/main.go

package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"net/http/httputil"
	"net/url"
	"sync"
	"time"
)

// 本文件实现了一个简单的 HTTP 反向代理负载均衡器示例。
// 主要功能:
// - 多后端服务器列表(`backends`)
// - 支持多种负载均衡算法的占位类型 `LoadBalancerType`
// - 基于 `httputil.ReverseProxy` 的请求代理与响应修改
// - `/health` 健康检查端点和 `/status` 状态页面

// 后端服务器列表(示例地址)
var backends = []string{
	"http://localhost:8081",
	"http://localhost:8082",
	"http://localhost:8083",
}

// 负载均衡算法类型枚举(可扩展)
type LoadBalancerType int

const (
	// 常见算法占位:轮询、随机、最少连接等
	RoundRobin         LoadBalancerType = iota // 轮询(默认)
	Random                                     // 随机
	LeastConnections                           // 最少连接数
	WeightedRoundRobin                         // 加权轮询(未实现,仅占位)
	WeightedRandom                             // 加权随机(未实现,仅占位)
	IPHash                                     // IP 哈希(未实现,仅占位)
	LeastResponseTime                          // 最短响应时间(未实现,仅占位)
	StickySession                              // 会话保持(未实现,仅占位)
)

var (
	// currentIndex 用于轮询算法的索引
	currentIndex = 0
	// mu 用于保护共享状态(currentIndex 和 connections)
	mu sync.Mutex
	// connections 记录每个后端的并发连接数或请求计数(示例用途)
	connections = make(map[string]int)
)

func main() {
	// 初始化随机种子(用于 Random 算法)
	rand.Seed(time.Now().UnixNano())

	// 构造 ReverseProxy,用于将外部请求转发到选定的后端服务器
	proxy := &httputil.ReverseProxy{
		// Director 在请求转发前修改请求目标地址
		Director: func(req *http.Request) {
			// 选择后端服务器(这里演示使用轮询)
			backend := selectBackend(RoundRobin)

			// 将选中的后端地址解析并替换请求 URL 相关字段
			target, err := url.Parse(backend)
			if err != nil {
				log.Printf("解析URL失败: %v", err)
				return
			}
			req.URL.Scheme = target.Scheme
			req.URL.Host = target.Host
			// 合并后端的路径与原始请求路径
			req.URL.Path = target.Path + req.URL.Path
			req.Host = target.Host

			// 记录(示例)并发连接数或调用次数
			mu.Lock()
			connections[backend]++
			mu.Unlock()

			log.Printf("原始路径=%s,代理请求到: %s%s", req.URL.Path, backend, req.URL.Path)
		},
		// ModifyResponse 可以在响应回到客户端之前修改响应头或内容
		ModifyResponse: func(resp *http.Response) error {
			// 添加自定义头,便于调试和追踪真实提供服务的后端
			resp.Header.Set("X-Load-Balancer", "Go-LB/1.0")
			resp.Header.Set("X-Backend-Server", resp.Request.URL.Host)
			return nil
		},
	}

	// 健康检查端点:返回负载均衡器自身和后端数量等信息
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		// 检查所有后端的健康状态(会向各后端的 /health 发起请求)
		health := checkAllBackends()

		w.Header().Set("Content-Type", "application/json")
		// 返回一个简单的 JSON 状态(字符串拼接仅为示例)
		fmt.Fprintf(w, `{
"status": "healthy",
"loadbalancer": "running",
"timestamp": "%s",
"backends": %d,
"algorithm": "round-robin"
}`, time.Now().Format(time.RFC3339), len(backends))

		log.Printf("负载均衡服务器健康检查: 后端服务器状态: %v", health)
	})

	// 状态页面:提供一个简单的 HTML 界面,展示后端列表和连接统计
	http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
		html := `<html>
<head><title>负载均衡器状态</title></head>
<body style="font-family: Arial, sans-serif; margin: 40px;">
<h1>🎯 Go 负载均衡器</h1>
<p><strong>状态:</strong>运行中</p>
<p><strong>后端服务器:</strong>3 个</p>
<p><strong>算法:</strong>轮询 (Round Robin)</p>
<h3>后端服务器:</h3>
<ul>
`
		// 列出所有后端并生成链接
		for i, backend := range backends {
			html += fmt.Sprintf(`<li><a href="%s" target="_blank">服务器 %d: %s</a></li>`,
				backend, i+1, backend)
		}

		html += `</ul>
<h3>连接统计:</h3>
<ul>`

		// 读取并显示每个后端的连接统计
		mu.Lock()
		for backend, count := range connections {
			html += fmt.Sprintf(`<li>%s: %d 个连接</li>`, backend, count)
		}
		mu.Unlock()

		html += `</ul>
<p><a href="/">测试负载均衡</a> | <a href="/health">健康检查</a></p>
</body>
</html>`

		fmt.Fprint(w, html)
	})

	//测试接口,使用负载均衡端口,代理到 backends中的服务地址
	http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		proxy.ServeHTTP(w, r)

	})

	// 根路径:对于 API 或非主页路径直接走代理,否则重定向到状态页
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 如果请求期望 JSON 或路径不是根,则代理请求到后端
		if r.Header.Get("Accept") == "application/json" || r.URL.Path != "/" {
			proxy.ServeHTTP(w, r)
			return
		}
		//如果注释上方代码,就只会代理指定的接口地址
		// 否则显示状态页面
		http.Redirect(w, r, "/status", http.StatusFound)
	})

	// 启动信息打印
	port := ":8080"
	log.Printf("⚖️ 负载均衡器启动在端口 %s", port)
	log.Printf("访问地址:http://localhost%s", port)
	log.Printf("状态页面:http://localhost%s/status", port)
	log.Printf("后端服务器:")
	for i, backend := range backends {
		log.Printf("  %d. %s", i+1, backend)
	}
	log.Println("按 Ctrl+C 停止负载均衡器")

	// 启动 HTTP 服务
	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatal("负载均衡器启动失败: ", err)
	}
}

// selectBackend 根据指定算法选择一个后端地址。
// 当前实现:Random、LeastConnections、RoundRobin(默认)
func selectBackend(algo LoadBalancerType) string {
	mu.Lock()
	defer mu.Unlock()

	switch algo {
	case Random:
		// 随机选择一个后端
		return backends[rand.Intn(len(backends))]

	case LeastConnections:
		// 选择当前连接数最少的后端
		minConn := -1
		selected := backends[0]
		for _, backend := range backends {
			conn := connections[backend]
			if minConn == -1 || conn < minConn {
				minConn = conn
				selected = backend
			}
		}
		return selected

	default: // RoundRobin
		// 轮询选择并推进索引
		log.Printf("选择服务器索引===================currentIndex=%s", currentIndex)
		backend := backends[currentIndex]
		currentIndex = (currentIndex + 1) % len(backends)
		return backend
	}
}

// checkAllBackends 对每个后端的 /health 端点进行简单探测,返回布尔结果映射
func checkAllBackends() map[string]bool {
	results := make(map[string]bool)
	client := &http.Client{Timeout: 2 * time.Second}

	for _, backend := range backends {
		resp, err := client.Get(backend + "/health")
		if err != nil || resp.StatusCode != 200 {
			results[backend] = false
		} else {
			results[backend] = true
		}
		if resp != nil {
			resp.Body.Close()
		}
	}

	return results
}


server1/main.go

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

func main() {
	// 定义处理函数
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 模拟处理时间
		time.Sleep(100 * time.Millisecond)

		response := fmt.Sprintf(`=== Server 1 Response ===
Time: %s
Client: %s
Request Path: %s
Server ID: server1
Status: OK
`,
			time.Now().Format("2006-01-02 15:04:05.000"),
			r.RemoteAddr,
			r.URL.Path)

		fmt.Fprint(w, response)

		// 在控制台也打印日志
		log.Printf("请求来自: %s, 路径: %s", r.RemoteAddr, r.URL.Path)
	})

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintf(w, `{"status": "healthy", "server": "server1", "timestamp": "%s"}`,
			time.Now().Format(time.RFC3339))
	})

	http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
		info := `服务器信息:
名称:Server 1
端口:8081
状态:运行中
启动时间:` + time.Now().Format("2006-01-02 15:04:05") + `
请求计数:持续服务中...
`
		fmt.Fprint(w, info)
	})

	http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		info := `test,测试接口,server1`
		fmt.Fprint(w, info)
	})

	// 启动服务器
	port := ":8081"
	log.Printf("🚀 Server 1 启动在端口 %s", port)
	log.Printf("访问地址:http://localhost%s", port)
	log.Printf("健康检查:http://localhost%s/health", port)
	log.Println("按 Ctrl+C 停止服务器")

	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatal("服务器启动失败: ", err)
	}
}

server2/main.go

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 模拟不同的处理时间
		time.Sleep(150 * time.Millisecond)

		response := fmt.Sprintf(`=== Server 2 Response ===
Time: %s
Client: %s
Request Path: %s
Server ID: server2
Status: OK
CPU Usage: 15%%
Memory: 128MB/512MB
`,
			time.Now().Format("2006-01-02 15:04:05.000"),
			r.RemoteAddr,
			r.URL.Path)

		fmt.Fprint(w, response)
		log.Printf("[Server2] 处理请求: %s", r.URL.Path)
	})

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintf(w, `{"status": "healthy", "server": "server2", "load": "medium", "timestamp": "%s"}`,
			time.Now().Format(time.RFC3339))
	})

	http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		info := `test,测试接口,server2`
		fmt.Fprint(w, info)
	})

	port := ":8082"
	log.Printf("🚀 Server 2 启动在端口 %s", port)
	log.Printf("访问地址:http://localhost%s", port)
	log.Println("按 Ctrl+C 停止服务器")

	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatal("服务器启动失败: ", err)
	}
}

server3/main.go

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"
)

// 请求统计
var requestCount int

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		requestCount++
		time.Sleep(200 * time.Millisecond) // 最慢的服务器

		response := fmt.Sprintf(`=== Server 3 Response ===
Time: %s
Client: %s
Request Path: %s
Server ID: server3
Status: OK
Request #: %d
Uptime: %s
`,
			time.Now().Format("2006-01-02 15:04:05.000"),
			r.RemoteAddr,
			r.URL.Path,
			requestCount,
			time.Since(startTime).String())

		fmt.Fprint(w, response)
		log.Printf("[Server3] 请求 #%d: %s", requestCount, r.URL.Path)
	})

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		health := map[string]interface{}{
			"status":    "healthy",
			"server":    "server3",
			"timestamp": time.Now().Format(time.RFC3339),
			"requests":  requestCount,
			"uptime":    time.Since(startTime).String(),
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(health)
	})

	http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
		stats := fmt.Sprintf(`服务器统计:
总请求数:%d
启动时间:%s
运行时长:%s
平均响应时间:200ms
当前连接:1
`,
			requestCount,
			startTime.Format("2006-01-02 15:04:05"),
			time.Since(startTime).String())

		fmt.Fprint(w, stats)
	})
	http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		info := `test,测试接口,server3`
		fmt.Fprint(w, info)
	})
	port := ":8083"
	log.Printf("🚀 Server 3 启动在端口 %s", port)
	log.Printf("访问地址:http://localhost%s", port)
	log.Println("按 Ctrl+C 停止服务器")

	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatal("服务器启动失败: ", err)
	}
}

var startTime = time.Now()

/start.bat


@echo off
chcp 65001 > nul
echo 启动分布式系统...

start cmd /k "cd server1 && go run main.go"
timeout /t 2 /nobreak > nul

start cmd /k "cd server2 && go run main.go"
timeout /t 2 /nobreak > nul

start cmd /k "cd server3 && go run main.go"
timeout /t 2 /nobreak > nul

start cmd /k "cd loadbalancer && go run main.go"

echo.
echo 所有服务已启动!
echo 访问 http://localhost:8080 测试负载均衡
echo.
pause

控制台,运行 .\start.bat 即可启动负载均衡服务器和server1、2、3。
直接在浏览器里访问http://localhost:8080/test,即可看到返回不同服务器的结果。

在这里插入图片描述

更多推荐