第5章:负载均衡
为什么需要负载均衡
单机限制
单机系统的问题:
┌────────┐ ┌──────────┐
│ Client │─────→│ Server │
└────────┘ │ 1核2G │
│ Max:1000 QPS│
└──────────┘
问题:
性能瓶颈(单机处理能力有限)
单点故障(服务器宕机=全部不可用)
无法扩展(流量增长无法应对)
负载均衡的作用
多实例 + 负载均衡:
┌────────┐ ┌─────────────┐
│ Client │─────→│Load Balancer│
└────────┘ └──────┬──────┘
┌───┴───┬───────┐
↓ ↓ ↓
┌────────┬────────┬────────┐
│Server 1│Server 2│Server 3│
│1000 QPS│1000 QPS│1000 QPS│
└────────┴────────┴────────┘
总容量:3000 QPS
优势:
提高性能(水平扩展)
高可用(单机故障不影响整体)
可扩展(动态增减实例)
流量分发(合理利用资源)
负载均衡层次
1. DNS负载均衡(第一层)
原理:通过DNS解析返回不同的IP地址
用户请求:www.example.com
DNS解析:
Round 1 → 192.168.1.10
Round 2 → 192.168.1.11
Round 3 → 192.168.1.12
Round 4 → 192.168.1.10 (循环)
配置示例:
; DNS Zone文件
www.example.com. IN A 192.168.1.10
www.example.com. IN A 192.168.1.11
www.example.com. IN A 192.168.1.12
优点:
- 简单易用
- 全局负载均衡(跨地域)
- 无单点故障
缺点:
- DNS缓存问题(无法实时生效)
- 无法感知服务器状态
- 分配不均(客户端缓存)
适用场景:
- 跨地域流量分发
- CDN调度
- 粗粒度负载均衡
2. 四层负载均衡(传输层)
原理:基于IP+端口转发(TCP/UDP)
Client → LVS (Layer 4 Load Balancer)
↓ (修改目标IP/端口)
Server 1/2/3
特点:
- 工作在传输层(OSI第4层)
- 不解析应用层协议
- 转发速度快(性能高)
典型实现:LVS(Linux Virtual Server)
LVS三种模式:
1. NAT模式
Client → LVS (DNAT) → Real Server
↓
Client ← LVS (SNAT) ← Real Server
特点:
- LVS修改源/目标IP
- 请求和响应都经过LVS
- LVS压力大(性能瓶颈)
2. DR模式(Direct Routing)
Client → LVS (修改MAC地址) → Real Server
↑ ↓
Client ←─────────────────────────┘
(直接返回,不经过LVS)
特点:
- 请求经过LVS,响应直接返回
- LVS只修改MAC地址(二层转发)
- 性能高(推荐)
3. TUN模式(IP Tunneling)
Client → LVS (IP封装) → Real Server
↑ ↓
Client ←────────────────────┘
(解封装后直接返回)
特点:
- 支持跨网段
- 性能好
- 配置复杂
优点:
- 性能极高(100万+ QPS)
- 支持所有TCP/UDP协议
- 对应用透明
缺点:
- 无法做应用层路由(如:URL路由)
- 配置复杂
3. 七层负载均衡(应用层)
原理:基于HTTP协议内容进行路由
Client → Nginx (Layer 7 Load Balancer)
↓ (解析HTTP请求)
根据URL/Header/Cookie等路由
↓
Server 1/2/3
特点:
- 工作在应用层(OSI第7层)
- 可以解析HTTP内容
- 灵活的路由策略
典型实现:Nginx、HAProxy
Nginx配置示例:
upstream backend {
# 负载均衡算法
least_conn; # 最少连接数
# 服务器列表
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
# 健康检查
server 192.168.1.13:8080 backup; # 备用服务器
}
server {
listen 80;
server_name www.example.com;
# 基于URL路由
location /api {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 基于Header路由
location /admin {
if ($http_x_admin = "true") {
proxy_pass http://admin_backend;
}
}
# 会话保持(基于Cookie)
location /login {
proxy_pass http://backend;
proxy_cookie_path / "/; Secure; HttpOnly";
}
}
优点:
- 灵活的路由策略(URL、Header、Cookie)
- 支持会话保持
- 可以做内容缓存
- SSL卸载
缺点:
- 性能低于四层(需要解析HTTP)
- 只支持HTTP/HTTPS
负载均衡算法
1. 轮询(Round Robin)
原理:按顺序依次分配请求
Request 1 → Server 1
Request 2 → Server 2
Request 3 → Server 3
Request 4 → Server 1 (循环)
代码实现:
type RoundRobinBalancer struct {
servers []string
index int
mu sync.Mutex
}
func (rb *RoundRobinBalancer) Next() string {
rb.mu.Lock()
defer rb.mu.Unlock()
server := rb.servers[rb.index]
rb.index = (rb.index + 1) % len(rb.servers)
return server
}
// 使用示例
func main() {
balancer := &RoundRobinBalancer{
servers: []string{
"192.168.1.10:8080",
"192.168.1.11:8080",
"192.168.1.12:8080",
},
}
for i := 0; i < 10; i++ {
server := balancer.Next()
fmt.Printf("Request %d → %s\n", i+1, server)
}
}
// 输出:
// Request 1 → 192.168.1.10:8080
// Request 2 → 192.168.1.11:8080
// Request 3 → 192.168.1.12:8080
// Request 4 → 192.168.1.10:8080
// ...
优点:
- 简单公平
- 实现容易
缺点:
- 不考虑服务器性能差异
- 不考虑服务器当前负载
适用场景:
- 服务器性能相同
- 请求处理时间相近
2. 加权轮询(Weighted Round Robin)
原理:根据服务器权重分配请求
Server 1: weight=3
Server 2: weight=2
Server 3: weight=1
分配比例:3:2:1
Server 1 → 3/6 = 50%
Server 2 → 2/6 = 33%
Server 3 → 1/6 = 17%
代码实现:
type WeightedRoundRobinBalancer struct {
servers []Server
index int
mu sync.Mutex
}
type Server struct {
Addr string
Weight int
CurrentWeight int
EffectiveWeight int
}
func (wrb *WeightedRoundRobinBalancer) Next() string {
wrb.mu.Lock()
defer wrb.mu.Unlock()
totalWeight := 0
var selected *Server
// 平滑加权轮询算法(Nginx使用)
for i := range wrb.servers {
server := &wrb.servers[i]
// 当前权重 += 有效权重
server.CurrentWeight += server.EffectiveWeight
totalWeight += server.EffectiveWeight
// 选择当前权重最大的
if selected == nil || server.CurrentWeight > selected.CurrentWeight {
selected = server
}
}
// 选中的服务器权重减去总权重
if selected != nil {
selected.CurrentWeight -= totalWeight
return selected.Addr
}
return ""
}
// 使用示例
func main() {
balancer := &WeightedRoundRobinBalancer{
servers: []Server{
{Addr: "192.168.1.10:8080", Weight: 3, EffectiveWeight: 3},
{Addr: "192.168.1.11:8080", Weight: 2, EffectiveWeight: 2},
{Addr: "192.168.1.12:8080", Weight: 1, EffectiveWeight: 1},
},
}
// 统计分配次数
count := make(map[string]int)
for i := 0; i < 60; i++ {
server := balancer.Next()
count[server]++
}
for addr, cnt := range count {
fmt.Printf("%s: %d (%.1f%%)\n", addr, cnt, float64(cnt)/60*100)
}
}
// 输出:
// 192.168.1.10:8080: 30 (50.0%)
// 192.168.1.11:8080: 20 (33.3%)
// 192.168.1.12:8080: 10 (16.7%)
优点:
- 考虑服务器性能差异
- 平滑分配(Nginx算法)
缺点:
- 权重配置需要人工调整
适用场景:
- 服务器性能不同(高配置服务器weight更大)
- 需要精细化流量控制
3. 最少连接(Least Connections)
原理:选择当前连接数最少的服务器
Server 1: 10 connections
Server 2: 5 connections ← 选择
Server 3: 8 connections
下次请求:
Server 1: 10 connections
Server 2: 6 connections
Server 3: 8 connections ← 如果Server 2的请求完成
代码实现:
type LeastConnectionBalancer struct {
servers []ServerWithConn
mu sync.RWMutex
}
type ServerWithConn struct {
Addr string
Connections int
}
func (lcb *LeastConnectionBalancer) Next() string {
lcb.mu.Lock()
defer lcb.mu.Unlock()
// 找到连接数最少的服务器
minIndex := 0
minConn := lcb.servers[0].Connections
for i := 1; i < len(lcb.servers); i++ {
if lcb.servers[i].Connections < minConn {
minIndex = i
minConn = lcb.servers[i].Connections
}
}
// 增加连接数
lcb.servers[minIndex].Connections++
return lcb.servers[minIndex].Addr
}
func (lcb *LeastConnectionBalancer) Release(addr string) {
lcb.mu.Lock()
defer lcb.mu.Unlock()
for i := range lcb.servers {
if lcb.servers[i].Addr == addr {
lcb.servers[i].Connections--
break
}
}
}
// 使用示例
func main() {
balancer := &LeastConnectionBalancer{
servers: []ServerWithConn{
{Addr: "192.168.1.10:8080", Connections: 0},
{Addr: "192.168.1.11:8080", Connections: 0},
{Addr: "192.168.1.12:8080", Connections: 0},
},
}
// 模拟请求
for i := 0; i < 5; i++ {
server := balancer.Next()
fmt.Printf("Request %d → %s\n", i+1, server)
// 模拟部分请求完成
if i == 2 {
balancer.Release("192.168.1.10:8080")
}
}
}
优点:
- 动态感知服务器负载
- 适合长连接场景
缺点:
- 实现复杂(需要维护连接数)
- 有状态(不适合无状态场景)
适用场景:
- 长连接(WebSocket、数据库连接池)
- 请求处理时间差异大
4. 一致性哈希(Consistent Hashing)
原理:将服务器和请求映射到同一个哈希环上
哈希环(0 ~ 2^32-1):
Server1 (hash=100)
↓
┌─────────●─────────┐
│ │
Server3 ● ● Server2
(hash=300) (hash=200)
│ │
└───────────────────┘
请求路由:
Request(key="user:123") → hash(key) = 150
→ 顺时针找到第一个服务器 → Server2
代码实现:
package main
import (
"hash/crc32"
"sort"
"strconv"
"sync"
)
type ConsistentHash struct {
hashFunc func([]byte) uint32
replicas int // 虚拟节点数
keys []uint32 // 排序的哈希环
hashMap map[uint32]string // 哈希值 -> 真实节点
mu sync.RWMutex
}
func NewConsistentHash(replicas int) *ConsistentHash {
return &ConsistentHash{
replicas: replicas,
hashFunc: crc32.ChecksumIEEE,
hashMap: make(map[uint32]string),
}
}
// 添加节点
func (ch *ConsistentHash) Add(nodes ...string) {
ch.mu.Lock()
defer ch.mu.Unlock()
for _, node := range nodes {
// 为每个真实节点创建多个虚拟节点(提高均衡性)
for i := 0; i < ch.replicas; i++ {
hash := ch.hashFunc([]byte(strconv.Itoa(i) + node))
ch.keys = append(ch.keys, hash)
ch.hashMap[hash] = node
}
}
// 排序哈希环
sort.Slice(ch.keys, func(i, j int) bool {
return ch.keys[i] < ch.keys[j]
})
}
// 移除节点
func (ch *ConsistentHash) Remove(node string) {
ch.mu.Lock()
defer ch.mu.Unlock()
for i := 0; i < ch.replicas; i++ {
hash := ch.hashFunc([]byte(strconv.Itoa(i) + node))
// 从哈希环中删除
idx := sort.Search(len(ch.keys), func(i int) bool {
return ch.keys[i] >= hash
})
if idx < len(ch.keys) && ch.keys[idx] == hash {
ch.keys = append(ch.keys[:idx], ch.keys[idx+1:]...)
}
delete(ch.hashMap, hash)
}
}
// 获取节点
func (ch *ConsistentHash) Get(key string) string {
ch.mu.RLock()
defer ch.mu.RUnlock()
if len(ch.keys) == 0 {
return ""
}
hash := ch.hashFunc([]byte(key))
// 顺时针找到第一个节点
idx := sort.Search(len(ch.keys), func(i int) bool {
return ch.keys[i] >= hash
})
// 环形,如果超出范围,回到第一个
if idx == len(ch.keys) {
idx = 0
}
return ch.hashMap[ch.keys[idx]]
}
// 使用示例
func main() {
ch := NewConsistentHash(150) // 每个节点150个虚拟节点
// 添加服务器
ch.Add("192.168.1.10:8080", "192.168.1.11:8080", "192.168.1.12:8080")
// 测试分布
distribution := make(map[string]int)
for i := 0; i < 10000; i++ {
key := fmt.Sprintf("user:%d", i)
server := ch.Get(key)
distribution[server]++
}
fmt.Println("Distribution:")
for server, count := range distribution {
fmt.Printf("%s: %d (%.1f%%)\n", server, count, float64(count)/10000*100)
}
// 添加新节点(数据迁移少)
fmt.Println("\nAdding new server...")
ch.Add("192.168.1.13:8080")
newDistribution := make(map[string]int)
for i := 0; i < 10000; i++ {
key := fmt.Sprintf("user:%d", i)
server := ch.Get(key)
newDistribution[server]++
}
fmt.Println("New distribution:")
for server, count := range newDistribution {
fmt.Printf("%s: %d (%.1f%%)\n", server, count, float64(count)/10000*100)
}
}
优点:
- 扩缩容时数据迁移少(只影响部分数据)
- 同一个key总是路由到同一台服务器
缺点:
- 实现复杂
- 节点分布可能不均匀(需要虚拟节点)
适用场景:
- 缓存系统(Redis Cluster)
- 分布式存储(Cassandra)
- 会话保持
客户端负载均衡
Spring Cloud Ribbon
原理:客户端集成负载均衡库,直接调用服务实例
┌────────────────┐
│ Service A │
│ (Client) │
│ ┌──────────┐ │
│ │ Ribbon │ │ ①从注册中心获取实例列表
│ │(负载均衡)│←─┼─→ ②负载均衡算法选择实例
│ └──────────┘ │ ③直接调用
└────────┬───────┘
↓ ③
┌────────┐
│Service B│
│Instance │
└────────┘
Go实现示例:
package main
import (
"errors"
"math/rand"
"sync"
"time"
)
// 服务实例
type ServiceInstance struct {
Addr string
Weight int
Health bool
}
// 客户端负载均衡器
type ClientLoadBalancer struct {
discovery ServiceDiscovery
cache map[string][]*ServiceInstance
strategy LoadBalanceStrategy
mu sync.RWMutex
}
type LoadBalanceStrategy interface {
Choose(instances []*ServiceInstance) *ServiceInstance
}
// 随机策略
type RandomStrategy struct{}
func (rs *RandomStrategy) Choose(instances []*ServiceInstance) *ServiceInstance {
if len(instances) == 0 {
return nil
}
return instances[rand.Intn(len(instances))]
}
// 加权随机策略
type WeightedRandomStrategy struct{}
func (wrs *WeightedRandomStrategy) Choose(instances []*ServiceInstance) *ServiceInstance {
if len(instances) == 0 {
return nil
}
// 计算总权重
totalWeight := 0
for _, instance := range instances {
totalWeight += instance.Weight
}
// 随机一个权重
randomWeight := rand.Intn(totalWeight)
// 选择实例
for _, instance := range instances {
randomWeight -= instance.Weight
if randomWeight < 0 {
return instance
}
}
return instances[0]
}
func NewClientLoadBalancer(discovery ServiceDiscovery, strategy LoadBalanceStrategy) *ClientLoadBalancer {
lb := &ClientLoadBalancer{
discovery: discovery,
cache: make(map[string][]*ServiceInstance),
strategy: strategy,
}
// 定期刷新实例列表
go lb.refreshLoop()
return lb
}
func (clb *ClientLoadBalancer) refreshLoop() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
clb.mu.Lock()
for serviceName := range clb.cache {
instances, _ := clb.discovery.GetInstances(serviceName)
clb.cache[serviceName] = instances
}
clb.mu.Unlock()
}
}
// 选择实例
func (clb *ClientLoadBalancer) ChooseInstance(serviceName string) (*ServiceInstance, error) {
clb.mu.RLock()
instances, ok := clb.cache[serviceName]
clb.mu.RUnlock()
if !ok {
// 首次访问,获取实例列表
instances, _ = clb.discovery.GetInstances(serviceName)
clb.mu.Lock()
clb.cache[serviceName] = instances
clb.mu.Unlock()
}
// 过滤健康实例
healthyInstances := make([]*ServiceInstance, 0)
for _, instance := range instances {
if instance.Health {
healthyInstances = append(healthyInstances, instance)
}
}
if len(healthyInstances) == 0 {
return nil, errors.New("no available instances")
}
// 负载均衡选择
return clb.strategy.Choose(healthyInstances), nil
}
// HTTP客户端(集成负载均衡)
type ServiceClient struct {
lb *ClientLoadBalancer
httpClient *http.Client
}
func NewServiceClient(lb *ClientLoadBalancer) *ServiceClient {
return &ServiceClient{
lb: lb,
httpClient: &http.Client{Timeout: 5 * time.Second},
}
}
func (sc *ServiceClient) Call(serviceName, path string) ([]byte, error) {
// 选择实例
instance, err := sc.lb.ChooseInstance(serviceName)
if err != nil {
return nil, err
}
// 发起HTTP请求
url := fmt.Sprintf("http://%s%s", instance.Addr, path)
resp, err := sc.httpClient.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
// 使用示例
func main() {
discovery := NewConsulDiscovery("localhost:8500")
strategy := &WeightedRandomStrategy{}
lb := NewClientLoadBalancer(discovery, strategy)
client := NewServiceClient(lb)
// 调用服务
data, err := client.Call("order-service", "/api/orders")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n", data)
}
优点:
- 无单点故障
- 性能高(直连)
- 灵活(可自定义策略)
缺点:
- 客户端逻辑复杂
- 多语言需要多套实现
服务端负载均衡
Nginx配置实战
完整配置:
# upstream定义后端服务器组
upstream backend {
# 负载均衡算法
least_conn; # 最少连接数
# 服务器列表(加权)
server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.1.13:8080 backup; # 备用服务器(其他都故障时启用)
# 健康检查(需要nginx_upstream_check_module)
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
# 会话保持(IP Hash)
# ip_hash;
}
server {
listen 80;
server_name api.example.com;
# 日志
access_log /var/log/nginx/api.access.log;
error_log /var/log/nginx/api.error.log;
# 限流(每秒10个请求)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req zone=api_limit burst=20 nodelay;
location / {
proxy_pass http://backend;
# 传递真实IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 重试
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
# 健康检查端点
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
实战优化
1. 会话保持(Session Persistence)
问题:用户登录后,Session存储在Server1,下次请求被路由到Server2,Session丢失
解决方案:
方案1: IP Hash
upstream backend {
ip_hash; # 同一个IP总是路由到同一台服务器
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
方案2: Cookie Hash
upstream backend {
hash $cookie_jsessionid; # 根据Cookie哈希
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
方案3: Redis集中存储
// 推荐:Session存储到Redis(共享)
type SessionStore struct {
redis *redis.Client
}
func (ss *SessionStore) Set(sessionID string, data interface{}, ttl time.Duration) error {
jsonData, _ := json.Marshal(data)
return ss.redis.Set(context.Background(), "session:"+sessionID, jsonData, ttl).Err()
}
func (ss *SessionStore) Get(sessionID string) (interface{}, error) {
data, err := ss.redis.Get(context.Background(), "session:"+sessionID).Bytes()
if err != nil {
return nil, err
}
var result interface{}
json.Unmarshal(data, &result)
return result, nil
}
2. 健康检查
主动健康检查(Nginx):
upstream backend {
server 192.168.1.10:8080;
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx;
}
被动健康检查(失败自动摘除):
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
# 30秒内失败3次 → 摘除30秒
3. 动态权重调整
场景:服务器负载变化,动态调整权重
type DynamicWeightBalancer struct {
servers []*Server
monitor *ResourceMonitor
mu sync.RWMutex
}
type Server struct {
Addr string
Weight int
DynamicWeight float64
CPU float64
Memory float64
}
func (dwb *DynamicWeightBalancer) adjustWeights() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
dwb.mu.Lock()
for _, server := range dwb.servers {
// 获取服务器资源使用率
cpu, mem := dwb.monitor.GetResourceUsage(server.Addr)
// 根据负载调整权重
// 负载越高,权重越低
loadFactor := 1.0 - (cpu*0.7 + mem*0.3)
server.DynamicWeight = float64(server.Weight) * loadFactor
log.Printf("Server %s: CPU=%.1f%%, Mem=%.1f%%, Weight=%.2f",
server.Addr, cpu*100, mem*100, server.DynamicWeight)
}
dwb.mu.Unlock()
}
}
面试问答
四层负载均衡和七层负载均衡有什么区别?
答案:
| 对比维度 | 四层负载均衡 | 七层负载均衡 |
|---|---|---|
| 工作层次 | 传输层(TCP/UDP) | 应用层(HTTP) |
| 性能 | 极高(100万+ QPS) | 较高(10万+ QPS) |
| 路由能力 | IP+端口 | URL、Header、Cookie |
| 协议支持 | 所有TCP/UDP | HTTP/HTTPS |
| 典型代表 | LVS、F5 | Nginx、HAProxy |
| 适用场景 | 高性能、透明代理 | 灵活路由、内容缓存 |
选择建议:
高性能要求、非HTTP协议 → 四层(LVS)
需要基于内容路由、HTTP协议 → 七层(Nginx)
一致性哈希如何解决节点变化时的数据迁移问题?
答案:
传统哈希:
节点数从3变为4:
hash(key) % 3 → hash(key) % 4
几乎所有key的映射都会改变
一致性哈希:
哈希环:
Server1(100) → Server2(200) → Server3(300)
添加Server4(150):
只有[100, 150)区间的key需要迁移到Server4
其他key不受影响
数据迁移量 = 1/N(N为节点数)
虚拟节点解决分布不均:
Server1 → 150个虚拟节点
Server2 → 150个虚拟节点
Server3 → 150个虚拟节点
虚拟节点均匀分布在哈希环上
→ 实际节点负载均衡
如何实现会话保持?各种方案的优缺点是什么?
答案:
| 方案 | 实现 | 优点 | 缺点 |
|---|---|---|---|
| IP Hash | 同IP路由到同服务器 | 简单 | 负载不均、NAT问题 |
| Cookie Hash | 根据Cookie哈希 | 准确 | 依赖Cookie |
| Session复制 | 服务器间同步Session | 高可用 | 性能差、复杂 |
| 集中存储 | Redis存储Session | 推荐 | 依赖Redis |
推荐方案:
// Redis集中存储
type SessionManager struct {
redis *redis.Client
}
func (sm *SessionManager) Set(sessionID string, data map[string]interface{}) error {
jsonData, _ := json.Marshal(data)
return sm.redis.Set(
context.Background(),
"session:"+sessionID,
jsonData,
30*time.Minute, // TTL
).Err()
}
func (sm *SessionManager) Get(sessionID string) (map[string]interface{}, error) {
data, err := sm.redis.Get(context.Background(), "session:"+sessionID).Bytes()
if err != nil {
return nil, err
}
var result map[string]interface{}
json.Unmarshal(data, &result)
return result, nil
}
// 优势:
// 所有服务器共享Session
// 支持水平扩展
// 高可用(Redis Cluster)
负载均衡器如何检测后端服务器的健康状态?
答案:
1. 主动健康检查
# Nginx配置
upstream backend {
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx;
}
参数说明:
- interval: 检查间隔(3秒)
- rise: 连续成功2次标记为UP
- fall: 连续失败3次标记为DOWN
- timeout: 超时时间(1秒)
2. 被动健康检查
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
# 实际请求失败时触发:
# 30秒内失败3次 → 摘除30秒
# 30秒后自动恢复,重新检查
3. 健康检查端点
// 服务端实现
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
// 检查数据库
if err := db.Ping(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"status": "DOWN",
"reason": "database unreachable",
})
return
}
// 检查Redis
if err := redis.Ping(context.Background()).Err(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"status": "DOWN",
"reason": "redis unreachable",
})
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "UP",
})
}
如何选择合适的负载均衡算法?
答案:
┌─────────────────────────────────────────┐
│ 选择负载均衡算法 │
└─────────────────────────────────────────┘
↓
服务器性能是否相同?
/ \
是 否
↓ ↓
轮询(RR) 加权轮询(WRR)
↓
请求是否长连接?
/ \
是 否
↓ ↓
最少连接(LC) 加权轮询(WRR)
↓
是否需要会话保持?
/ \
是 否
↓ ↓
一致性哈希 随机/轮询
(Consistent Hash)
具体场景:
| 场景 | 推荐算法 | 原因 |
|---|---|---|
| 短连接HTTP | 轮询/加权轮询 | 简单高效 |
| 长连接(WebSocket) | 最少连接 | 动态负载均衡 |
| 缓存系统 | 一致性哈希 | 减少数据迁移 |
| 需要会话保持 | IP Hash / 一致性哈希 | 同一用户固定服务器 |
| 服务器性能不同 | 加权轮询 | 按性能分配 |