GO学习记录——分布式部署(纯 Go 方案无 Docker)
·
分布式部署学习记录,使用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,即可看到返回不同服务器的结果。

更多推荐
所有评论(0)