grpc client端创建连接时可以用WithBalancer来指定负载均衡组件,这里研究下grpc自带的RoundRobin(轮询调度)的实现。源码在google.golang.org/grpc/balancer.go中。
roundRobin结构体定义如下:
type roundRobin struct {
r naming.Resolver
w naming.Watcher
addrs []*addrInfo // all the addresses the client should potentially connect
mu sync.Mutex
addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to.
next int // index of the next address to return for Get()
waitCh chan struct{} // the channel to block when there is no connected address available
done bool // The Balancer is closed.
}
- r是命名解析器,可以定义自己的命名解析器,如etcd命名解析器。如果r为nil,那么Dial中参数target将直接作为可请求地址添加到addrs中。
- w是命名解析器Resolve方法返回的watcher,该watcher可以监听命名解析器发来的地址信息变化,通知roundRobin对addrs中的地址进行动态的增删。
- addrs是从命名解析器获取地址信息数组,数组中每个地址不仅有地址信息,还有grpc与该地址是否已经创建了ready状态的连接。
- addrCh是地址数组的channel,该channel会在每次命名解析器发来地址信息变化后,将所有addrs通知到grpc内部的lbWatcher,lbWatcher是统一管理地址连接状态的协程,负责新地址的连接与被删除地址的关闭操作。
- next是roundRobin的Index,即轮询调度遍历到addrs数组中的哪个位置了。
- waitCh是当addrs中地址为空时,grpc调用Get()方法希望获取到一个到target的连接,如果设置了grpc的failfast为false,那么Get()方法会阻塞在此channel上,直到有ready的连接。
roundRobin启动:
func (rr *roundRobin) Start(target string, config BalancerConfig) error {
rr.mu.Lock()
defer rr.mu.Unlock()
if rr.done {
return ErrClientConnClosing
}
if rr.r == nil {
// 如果没有解析器,那么直接将target加入addrs地址数组
rr.addrs = append(rr.addrs, &addrInfo{addr: Address{Addr: target}})
return nil
}
// Resolve接口会返回一个watcher,watcher可以监听解析器的地址变化
w, err := rr.r.Resolve(target)
if err != nil {
return err
}
rr.w = w
// 创建一个channel,当watcher监听到地址变化时,通知grpc内部lbWatcher去连接该地址