基于v1.64.0;
注:详细的传递等交叉部分的逻辑 及部分负载均衡处理的细节,请见 resolver;
负载均衡
负载均衡的粒度是请求级别,流量管理更灵活;
基础实现流程:
- 构建注册中心,提供服务发现(如 etcd、ZK 等);
- grpc 服务端注册后,服务与命名服务建立连接,并注册自身相关元数据(ip+port…),并开启 goroutine 续约;
- 将命名服务注册给 resolver(如 etcd 客户端),获得本地解析器,注册 grpc 客户端时,将服务发现的地址和解析器和负载均衡策略等 opts 传入(局部有效),客户端和所有在线服务建立 HTTP2.0 长连接;
- 发起 RPC 调用时,根据负载均衡器的策略(默认为pick_first,即选第一个能用的),选择其中一个 HTTP2.0 长连接与该服务端通信;
balancer.Balancer
(负载均衡器) 的职责:
- 接收 Resolver 传递过来的服务节点更新信息(通过 ClientConn),维护连接和对应连接池的状态;
- 内含选择器用于实现真正的负载均衡逻辑,选择器的 warpper 会通过 updatePicker 方法更新 picker的状态;
连接为 HTTP2.0 的长连接,每次 RPC 调用都直接复用该连接;
当一个服务挂掉后,且客户端可通过重试连接另一个服务节点;
负载均衡器的实现负责维护和管理连接池。决定了客户端使用哪些 SubConn 与服务实例通信,以及如何分配请求到这些 SubConn 上;
自定义实现的思路
详细请见 implement
如果仅自定义简单的负载均衡策略,只需实现 PickerBuilder
和 Picker
接口即可:
PickerBuilder
接口用于创建 Picker,每次 balancer 收到服务节点的变化通知时,最后都需要重新 build 出一个新 picker,或者说以此更新选择器的负载均衡策略;
Picker
接口则负责提供服务发现功能,就单纯实现负载均衡相关逻辑即可,比如最简单的就是把获取到的第一个节点返回给 ClientConn 以发起 RPC 请求;
如果涉及较为复杂的负载均衡策略,或者说核心是想要自己管理连接、连接池并管理相关 UpdateState 的逻辑时,就需要再向上将 balancer.Balancer 和 balancer.Builder 接口自定义实现出来,涉及维护的元数据较多,暂不展开讨论;
连接相关
type acBalancerWrapper struct {
ac *addrConn // read-only
ccb *ccBalancerWrapper // read-only
stateListener func(balancer.SubConnState)
...
}
type SubConnState struct {
ConnectivityState connectivity.State
ConnectionError error
}
ClientConn
的状态转换:
- Idle -> Connecting -> Ready:客户端启动后,在第一次发起 RPC 请求前处于 Idle,然后尝试连接服务端进入 Connecting 状态,连接成功后进入 Ready 状态;
- Ready -> TransientFailure -> Connecting → Ready:连接中途断开时,客户端会进入 TransientFailure 状态,接着自动重试连接进入 Connecting,最终重新连接成功进入 Ready;
- Ready -> Shutdown:当客户端调用 Close() 时,连接被关闭,状态变为 Shutdown;
- idle -> connecting -> TransientFailure -> shutdown:正常连接尝试重试(发现服务终止);
当 SubConn 状态发生变化时,addrConn 会调用 updateConnectivityState 方法更新状态,并通过 acBalancerWrapper 的回调函数 stateListener,传递给 baseBalancer(会更新 picker),首先会尝试重连,如果服务挂了,则最后会转变为 shutdown 状态;
balancer 相关核心接口
// SubConn 表示到gRPC后端服务的单个连接
//
// 所有 SubConn 都从 IDLE(空闲状态)开始,不会主动连接后端服务,
// 只有当负载均衡器调用 Connect 时,才会尝试连接;
//
// 如果 SubConn 断开连接,会进入IDLE,需要负载均衡器再次 Connect;
type SubConn interface {
// 弃用,直接创建新的 SubConn 实例,而不是更新现有连接的地址;
UpdateAddresses([]resolver.Address)
Connect()
// 生产者指客户端发送消息的行为?
GetOrBuildProducer(ProducerBuilder) (p Producer, close func())
// 不接受新请求,完成当前rpc后关闭
Shutdown()
}
// 创建负载均衡器
type Builder interface {
// 通过客户端连接创建新负载均衡器
Build(cc ClientConn, opts BuildOptions) Balancer
// 返回负载均衡器的名字,用于客户端选择LB策略
Name() string
}
type Picker interface {
// 为请求选择一个连接,及负载均衡策略实现的地方
// 不可阻塞
// 返回错误的情况:
// ErrNoSubConnAvailable:grpc阻塞直到获得新连接处理请求;
//
// 如果返回 grpc/status 包的状态错误,则以此终止该rpc
//
// 其他错误,等待就绪的rpc会阻塞等待,非等待则会返回错误的string和Unavailable
// 注:WithWaitForReady(true)即等待就绪
Pick(info PickInfo) (PickResult, error)
}
// 接收从 Resolver 发送的服务端列表,建立并维护(长)连接(SubConn)状态
// 当 client 发起 RPC 调用时,按照算法从连接池取出一个连接用于 RPC 调用
type Balancer interface {
// 动态感知服务地址或配置(负载均衡策略切换)的变化,可触发服务发现器的重试;
UpdateClientConnState(ClientConnState) error
// 回调通知,grpc解析服务地址时发生了错误
ResolverError(error)
UpdateSubConnState(SubConn, SubConnState)
// 考虑关闭所有子连接,目前不强制要求全关闭
Close()
}
type ClientConn interface {
// 在负载均衡器的实现中使用,创建新连接
// 未来一个SubConn只对应一个address
NewSubConn([]resolver.Address, NewSubConnOptions) (SubConn, error)
RemoveSubConn(SubConn) // 弃用,应该用SubConn.Shutdown关闭连接
UpdateAddresses(SubConn, []resolver.Address) //弃用,应该创建而不是更新
// 负载均衡器的状态被更新了,grpc会更新 ClientConn 的连接状态
// 并调用新 Picker 的 Pick 方法获取新 SubConns
UpdateState(State)
// 负载均衡器发现状态变化后,通知grpc进行服务发现
ResolveNow(resolver.ResolveNowOptions)
Target() string // 弃用
// 使用 BuildOptions 中的 Target 字段获取后端服务(逻辑标识或者说服务名)位置
}
serviceconfig:
// 用于表示负载均衡 配置 的数据结构;
// 为不同类型的负载均衡 配置 提供一个通用的基类;
// 不透明
type LoadBalancingConfig interface {
isLoadBalancingConfig()
}