ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

grpc-go 客户端负载均衡实战:pick_first 与 round_robin 策略配置与运行原理

grpc-go 客户端负载均衡实战:pick_first 与 round_robin 策略配置与运行原理 grpc-go 客户端负载均衡实战pick_first 与 round_robin 策略配置与运行原理【免费下载链接】grpc-goThe Go language implementation of gRPC. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-go导读在 gRPC 中负载均衡的核心决策发生在客户端一个ClientConn通过 name resolver 拿到后端地址列表后由**负载均衡策略load balancing policy**决定每个 RPC 究竟发往哪个后端。本文以 grpc-go 官方示例 examples/features/load_balancing 为主线完整演示如何用grpc.WithDefaultServiceConfig为不同的ClientConn选择pick_first或round_robin两种内置策略并结合仓库源码balancer/pickfirst/pickfirst.go、balancer/roundrobin/roundrobin.go讲清它们的选路行为与底层原理。读完本文你将掌握如何在客户端配置负载均衡策略、两种内置策略的行为差异、为什么round_robin也可能连续命中同一后端以及如何编写自定义策略。示例概览双后端 Echo 服务与双客户端本示例的运行结构非常清晰用于直观验证负载均衡的效果两个 Echo 服务器分别监听:50051与:50052并且会把自身监听地址拼进响应消息。因此:50051上的服务器会回复this is examples/load_balancing (from :50051)两个客户端连接同一个服务名由示例自带的 name resolver 解析出上述两个后端地址分别使用不同的负载均衡策略pick_first与round_robin通过观察连续多次 RPC 的响应来自哪个端口即可眼见为实地看到两种策略的选路差异。示例代码位于 examples/features/load_balancing 目录下examples/features/load_balancing/ ├── client/ │ └── main.go ├── server/ │ └── main.go └── README.md运行方式在仓库根目录依次打开两个终端# 终端一启动两个 Echo 后端 go run examples/features/load_balancing/server/main.go# 终端二启动客户端依次展示两种策略下的 10 次 RPC go run examples/features/load_balancing/client/main.go服务端 server/main.go 通过sync.WaitGroup并发启动两个grpc.NewServer()各自注册Echo服务并调用s.Serve(lis)阻塞服务var addrs []string{:50051, :50052} func startServer(addr string) { lis, err : net.Listen(tcp, addr) // ... s : grpc.NewServer() pb.RegisterEchoServer(s, ecServer{addr: addr}) log.Printf(serving on %s\n, addr) if err : s.Serve(lis); err ! nil { log.Fatalf(failed to serve: %v, err) } } func main() { var wg sync.WaitGroup for _, addr : range addrs { wg.Add(1) go func(addr string) { defer wg.Done() startServer(addr) }(addr) } wg.Wait() }ecServer.UnaryEcho是应答逻辑的落点它把请求消息原样返回并追加来源地址见 server/main.gofunc (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { return pb.EchoResponse{Message: fmt.Sprintf(%s (from %s), req.Message, s.addr)}, nil }Echo服务的 proto 定义在 examples/features/proto/echo/echo.proto其中UnaryEcho是本次示例用到的单元 RPCservice Echo { rpc UnaryEcho(EchoRequest) returns (EchoResponse) {} rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {} rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {} rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {} }前置阅读示例 name resolver需要特别说明示例中两个后端地址并非硬编码在客户端而是由一个注册为examplescheme 的自定义 name resolver 提供client/main.goconst ( exampleScheme example exampleServiceName lb.example.grpc.io ) var addrs []string{localhost:50051, localhost:50052}resolver 的Build方法把lb.example.grpc.io映射到addrs并通过r.cc.UpdateState(resolver.State{Addresses: addrs})把地址列表推送给ClientConn随后由负载均衡策略接管func (r *exampleResolver) start() { addrStrs : r.addrsStore[r.target.Endpoint()] addrs : make([]resolver.Address, len(addrStrs)) for i, s : range addrStrs { addrs[i] resolver.Address{Addr: s} } r.cc.UpdateState(resolver.State{Addresses: addrs}) } func init() { resolver.Register(exampleResolverBuilder{}) }阅读 examples/features/name_resolving/README.md 可以更系统地理解 name resolver 的工作方式它本质上是map[service-name][]backend-ip最常用的实现是 DNS。用 WithDefaultServiceConfig 配置负载均衡策略客户端在创建ClientConn时通过grpc.WithDefaultServiceConfig传入一段 JSON 形式的 service config其中loadBalancingConfig字段用于指定初始负载均衡策略。示例客户端 client/main.go 创建了两个连接// pick_first is the default, so theres no need to set the load balancing policy. pickfirstConn, err : grpc.NewClient( fmt.Sprintf(%s:///%s, exampleScheme, exampleServiceName), grpc.WithTransportCredentials(insecure.NewCredentials()), ) // ... // Make another ClientConn with round_robin policy. roundrobinConn, err : grpc.NewClient( fmt.Sprintf(%s:///%s, exampleScheme, exampleServiceName), grpc.WithDefaultServiceConfig({loadBalancingConfig: [{round_robin:{}}]}), // This sets the initial balancing policy. grpc.WithTransportCredentials(insecure.NewCredentials()), )有两个要点值得注意pick_first是默认策略。第一个ClientConn没有传任何 service configgRPC 会默认使用pick_first因此代码注释明确写道 theres no need to set the load balancing policy。round_robin需要显式声明。通过grpc.WithDefaultServiceConfig传入{loadBalancingConfig: [{round_robin:{}}]}。该选项的完整定义位于 dialoptions.go它的作用是为ClientConn配置一份默认服务配置当 name resolver 没有推送服务配置时生效。loadBalancingConfig是一个有序策略数组gRPC 会按顺序挑选第一个当前可用即已注册的策略。这是 gRPC 负载均衡策略扩展机制的基石也意味着策略优先级可以在服务端下发。pick_first固定命中一个后端行为说明pick_first的行为非常直接依次尝试连接地址列表中的第一个地址连接成功后所有 RPC 都发送到该后端若第一个地址连接失败则尝试下一个地址直到某一次连接成功为止。因此在本示例中两个后端都在线pick_first客户端的所有 10 次 RPC 都会落在:50051响应输出如下this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051)源码实现印证pick_first策略注册于 balancer/pickfirst/pickfirst.go其包注释称它是 the universal leaf policy——即所有复合策略最终都会落到pick_first之上后文round_robin的实现正好印证了这一点。核心注册逻辑如下func init() { balancer.Register(pickfirstBuilder{}) } // Name is the name of the pick_first balancer. const Name pick_first从实现看pickfirstBalancer用addressList顺序迭代地址pickfirst.go在requestConnectionLocked中依次对地址创建 SubConn 并发起连接一旦某个 SubConn 进入Ready状态shutdownRemainingLocked会关闭其余所有 SubConn仅保留这一个用于全部 RPCpickfirst.go。这正是只挑第一个可用的、成功后所有 RPC 都走它的行为来源。值得一提的细节pick_first还实现了 Happy EyeballsRFC 8305式的并发探测——在等待当前地址连接的同时以connectionDelayInterval 250 * time.Millisecondpickfirst.go的间隔推进下一个地址的尝试以缩短失败切换的等待时间。此外它的可配置项shuffleAddressList可以在连接前打乱地址列表顺序见pfConfig定义pickfirst.go这为后续更公平的地址选择留出了空间。round_robin轮流分发到所有可用后端行为说明round_robin的策略则完全不同会尝试连接所有解析出的地址将 RPC 按顺序轮流round-robin发送给每个后端第一个 RPC 发往 backend-1第二个发往 backend-2第三个又回到 backend-1依此类推。本示例中的响应输出大致如下两个端口交替出现this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50052) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50052) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50052) this is examples/load_balancing (from :50051) this is examples/load_balancing (from :50052) this is examples/load_balancing (from :50051)为什么可能连续命中同一后端原文档特别提醒你有可能看到连续两次 RPC 都发往了同一个后端。原因在于round_robin只在当前对 RPC 可用Ready的连接中进行选择。如果两个连接中有一个由于某种原因尚未就绪那么所有 RPC 都会落到那个已就绪的连接上直到两个连接都 Ready 为止。换言之round_robin的轮询是在就绪连接子集上进行的而非盲目地在地址列表上轮转。源码实现印证round_robin策略位于 balancer/roundrobin/roundrobin.go同样在init()中自注册无需用户显式安装// Name is the name of round_robin balancer. const Name round_robin func init() { balancer.Register(builder{}) }从当前实现看rrBalancer实际是基于endpointsharding组合器构建的它通过endpointsharding.NewBalancer为每个端点endpoint创建一个子pick_first策略由子策略负责与具体地址建立连接再由round_robin在多个已就绪的子连接之间做轮转func (bb builder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer { childBuilder : balancer.Get(pickfirst.Name).Build bal : rrBalancer{ cc: cc, Balancer: endpointsharding.NewBalancer(cc, opts, childBuilder, endpointsharding.Options{}), } // ... return bal }注意UpdateClientConnState中通过pickfirst.EnableHealthListener开启了子pick_first策略的健康监听用于客户端侧健康检查与异常点检测这也与只在 Ready 连接间轮询的行为相呼应roundrobin.go。策略的另一种配置来源服务端下发 service config原文档强调了一个容易被忽视的点负载均衡策略也可以由服务端通过 service config 下发从而让服务所有者而非客户端所有者决定使用哪种策略。这与grpc.WithDefaultServiceConfig的客户端默认值形成互补客户端默认值仅在 resolver 未推送服务配置时生效若 name resolver 返回的服务配置中带有loadBalancingConfig则以服务端/控制面下发的配置为准。这正是 gRPC 中客户端负责尽力而为、服务端拥有最终决定权的配置协商模型。仓库中 service config 的解析与loadBalancingConfig的处理逻辑可参考 service_config.go。扩展实现自定义负载均衡策略pick_first与round_robin是 gRPC 默认内置的两类策略。若需要自定义策略例如按权重、按延迟、按请求内容路由需要实现 balancer/balancer.go 中定义的一组接口Builder负责创建 balancer 实例Name()返回策略名用于在 service config 中被引用Build(cc ClientConn, opts BuildOptions) Balancer创建实例balancer.goBalancer通过UpdateClientConnState接收 resolver 推送的地址与配置管理 SubConn 生命周期并通过UpdateState向ClientConn上报连通状态与新的 Pickerbalancer.goPicker真正的每次 RPC 选路决策者Pick(info PickInfo) (PickResult, error)决定本次 RPC 使用哪个 SubConnbalancer.go可选实现ConfigParser通过ParseConfig解析策略专属的 JSON 配置balancer.go。注册方式与内置策略一致在init()中调用balancer.Register(builder{})之后即可在loadBalancingConfig中以策略名引用。balancer.Register的说明balancer.go要求注册必须发生在初始化阶段且线程不安全若同名重复注册后注册者生效。小结通过本示例可以完整掌握 grpc-go 客户端负载均衡的实践路径地址来源与选路解耦name resolver 负责有哪些后端负载均衡策略负责这次 RPC 发给谁两者通过ClientConn衔接配置方式grpc.WithDefaultServiceConfig中loadBalancingConfig数组声明策略pick_first为默认值可省略round_robin需显式声明且可被服务端下发的 service config 覆盖行为差异pick_first固定一个可用后端适合连接复用、会话粘性场景round_robin在全部就绪连接间轮流分发适合请求均衡场景实现基础两个内置策略均可在 balancer/pickfirst/pickfirst.go 与 balancer/roundrobin/roundrobin.go 中查看完整实现自定义策略则从 balancer/balancer.go 的Builder/Balancer/Picker/ConfigParser接口起步。建议动手运行示例观察两个终端输出中来源端口的差异并尝试停掉:50051后端直观感受pick_first的故障切换与round_robin在单就绪连接下的被迫集中行为。【免费下载链接】grpc-goThe Go language implementation of gRPC. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-go创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表