在Golang的分布式服务架构中,RPC作为服务间通信的核心方式,其负载均衡能力决定了请求能否合理分配到多个服务节点,避免单节点过载。实现RPC负载均衡需要结合服务发现获取可用节点列表,再通过对应的分配策略完成请求路由。

核心依赖组件
实现Golang RPC负载均衡需要两个基础组件:
- 服务发现模块:负责从注册中心获取当前可用的RPC服务节点列表,监听节点上下线变化,实时更新可用节点集合。
- 负载均衡选择器:根据预设的策略,从可用节点列表中挑选一个节点处理当前RPC请求。
常见负载均衡策略实现
1. 随机策略
随机策略从可用节点中随机选择一个节点处理请求,实现简单,适合节点性能差异不大的场景。
package balance
import (
"math/rand"
"sync"
)
// RandomBalance 随机负载均衡器
type RandomBalance struct {
mu sync.RWMutex
nodes []string // 可用节点地址列表
}
// NewRandomBalance 创建随机负载均衡器
func NewRandomBalance() *RandomBalance {
return &RandomBalance{}
}
// UpdateNodes 更新可用节点列表
func (r *RandomBalance) UpdateNodes(nodes []string) {
r.mu.Lock()
defer r.mu.Unlock()
r.nodes = make([]string, len(nodes))
copy(r.nodes, nodes)
}
// Select 选择节点
func (r *RandomBalance) Select() (string, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if len(r.nodes) == 0 {
return "", errors.New("no available nodes")
}
// 随机选择一个节点
idx := rand.Intn(len(r.nodes))
return r.nodes[idx], nil
}
2. 轮询策略
轮询策略按顺序依次分配请求到每个节点,保证每个节点处理的请求数量相对均衡。
package balance
import (
"sync"
)
// RoundRobinBalance 轮询负载均衡器
type RoundRobinBalance struct {
mu sync.RWMutex
nodes []string
current int // 当前轮询索引
}
// NewRoundRobinBalance 创建轮询负载均衡器
func NewRoundRobinBalance() *RoundRobinBalance {
return &RoundRobinBalance{
current: 0,
}
}
// UpdateNodes 更新可用节点列表
func (r *RoundRobinBalance) UpdateNodes(nodes []string) {
r.mu.Lock()
defer r.mu.Unlock()
r.nodes = make([]string, len(nodes))
copy(r.nodes, nodes)
r.current = 0 // 重置索引
}
// Select 选择节点
func (r *RoundRobinBalance) Select() (string, error) {
r.mu.Lock()
defer r.mu.Unlock()
if len(r.nodes) == 0 {
return "", errors.New("no available nodes")
}
node := r.nodes[r.current]
r.current = (r.current + 1) % len(r.nodes)
return node, nil
}
3. 加权轮询策略
加权轮询策略根据节点的性能配置不同的权重,性能越高的节点分配到的请求越多,适合节点配置差异较大的场景。
package balance
import (
"sync"
)
// WeightNode 带权重的节点
type WeightNode struct {
addr string // 节点地址
weight int // 节点权重
currentWeight int // 当前权重,用于计算
}
// WeightRoundRobinBalance 加权轮询负载均衡器
type WeightRoundRobinBalance struct {
mu sync.RWMutex
nodes []*WeightNode
}
// NewWeightRoundRobinBalance 创建加权轮询负载均衡器
func NewWeightRoundRobinBalance() *WeightRoundRobinBalance {
return &WeightRoundRobinBalance{}
}
// UpdateNodes 更新可用节点列表,传入节点地址和对应权重
func (w *WeightRoundRobinBalance) UpdateNodes(nodes map[string]int) {
w.mu.Lock()
defer w.mu.Unlock()
w.nodes = make([]*WeightNode, 0, len(nodes))
for addr, weight := range nodes {
w.nodes = append(w.nodes, &WeightNode{
addr: addr,
weight: weight,
currentWeight: 0,
})
}
}
// Select 选择节点,使用平滑加权轮询算法
func (w *WeightRoundRobinBalance) Select() (string, error) {
w.mu.Lock()
defer w.mu.Unlock()
if len(w.nodes) == 0 {
return "", errors.New("no available nodes")
}
var selected *WeightNode
totalWeight := 0
// 遍历所有节点,更新当前权重,选择当前权重最大的节点
for _, node := range w.nodes {
node.currentWeight += node.weight
totalWeight += node.weight
if selected == nil || node.currentWeight > selected.currentWeight {
selected = node
}
}
// 选中节点的当前权重减去总权重
if selected != nil {
selected.currentWeight -= totalWeight
return selected.addr, nil
}
return "", errors.New("select node failed")
}
结合RPC调用的完整示例
以下是结合net/rpc包和轮询负载均衡器的完整调用示例:
package main
import (
"fmt"
"log"
"net/rpc"
"balance"
)
func main() {
// 初始化轮询负载均衡器,添加可用节点
balancer := balance.NewRoundRobinBalance()
balancer.UpdateNodes([]string{
"127.0.0.1:8080",
"127.0.0.1:8081",
"127.0.0.1:8082",
})
// 模拟发起10次RPC请求
for i := 0; i < 10; i++ {
// 选择节点
addr, err := balancer.Select()
if err != nil {
log.Printf("select node failed: %v", err)
continue
}
// 建立RPC连接
client, err := rpc.Dial("tcp", addr)
if err != nil {
log.Printf("connect to %s failed: %v", addr, err)
continue
}
// 调用RPC方法
var reply string
err = client.Call("MathService.Add", &Args{A: 1, B: 2}, &reply)
if err != nil {
log.Printf("rpc call failed: %v", err)
} else {
fmt.Printf("call %s success, reply: %sn", addr, reply)
}
client.Close()
}
}
优化实践建议
- 增加节点健康检查机制,负载均衡器定期探测节点可用性,自动剔除不可用节点。
- 实现请求失败重试机制,当所选节点调用失败时,自动切换到其他可用节点重试。
- 结合服务发现的监听能力,当节点列表变化时实时更新负载均衡器的节点集合,避免请求到已下线节点。
- 对于延迟敏感的RPC调用,可以增加最小活跃数策略,优先选择当前处理请求最少的节点。