gRPC负载均衡(客户端负载均衡)
gRPC负载均衡客户端负载均衡前言上篇介绍了如何使用etcd实现服务发现本篇将基于etcd的服务发现前提下介绍如何实现gRPC客户端负载均衡。gRPC负载均衡gRPC官方文档提供了关于gRPC负载均衡方案Load Balancing in gRPC此方案是为gRPC设计的下面我们对此进行分析。1、对每次调用进行负载均衡gRPC中的负载平衡是以每次调用为基础而不是以每个连接为基础。换句话说即使所有的请求都来自一个客户端我们仍希望它们在所有的服务器上实现负载平衡。2、负载均衡的方法集中式Proxy Model[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akL52HNn-1613979911428)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518153536494-684598725.png)]在服务消费者和服务提供者之间有一个独立的负载均衡LB通常是专门的硬件设备如 F5或者基于软件如 LVSHAproxy等实现。LB上有所有服务的地址映射表通常由运维配置注册当服务消费方调用某个目标服务时它向LB发起请求由LB以某种策略比如轮询Round-Robin做负载均衡后将请求转发到目标服务。LB一般具备健康检查能力能自动摘除不健康的服务实例。该方案主要问题服务消费方、提供方之间增加了一级有一定性能开销请求量大时效率较低。可能有读者会认为集中式负载均衡存在这样的问题一旦负载均衡服务挂掉那整个系统将不能使用。解决方案可以对负载均衡服务进行DNS负载均衡通过对一个域名设置多个IP地址每次DNS解析时轮询返回负载均衡服务地址从而实现简单的DNS负载均衡。客户端负载Balancing-aware Client[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z422EtFU-1613979911430)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518155900462-1370526164.png)]针对第一个方案的不足此方案将LB的功能集成到服务消费方进程里也被称为软负载或者客户端负载方案。服务提供方启动时首先将服务地址注册到服务注册表同时定期报心跳到服务注册表以表明服务的存活状态相当于健康检查服务消费方要访问某个服务时它通过内置的LB组件向服务注册表查询同时缓存并定期刷新目标服务地址列表然后以某种负载均衡策略选择一个目标服务地址最后向目标服务发起请求。LB和服务发现能力被分散到每一个服务消费者的进程内部同时服务消费方和服务提供方之间是直接调用没有额外开销性能比较好。该方案主要问题要用多种语言、多个版本的客户端编写和维护负载均衡策略使客户端的代码大大复杂化。独立LB服务External Load Balancing Service[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W9r95C1t-1613979911431)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518170636421-1833253282.png)]该方案是针对第二种方案的不足而提出的一种折中方案原理和第二种方案基本类似。不同之处是将LB和服务发现功能从进程内移出来变成主机上的一个独立进程。主机上的一个或者多个服务要访问目标服务时他们都通过同一主机上的独立LB进程做服务发现和负载均衡。该方案也是一种分布式方案没有单点问题服务调用方和LB之间是进程内调用性能好同时该方案还简化了服务调用方不需要为不同语言开发客户库。本篇将介绍第二种负载均衡方法客户端负载均衡。实现gRPC客户端负载均衡gRPC已提供了简单的负载均衡策略如Round Robin我们只需实现它提供的Builder和Resolver接口就能完成gRPC客户端负载均衡。typeBuilderinterface{Build(target Target,cc ClientConn,opts BuildOption)(Resolver,error)Scheme()string}Builder接口创建一个resolver本文称之服务发现用于监视名称解析更新。Build方法为给定目标创建一个新的resolver当调用grpc.Dial()时执行。Scheme方法返回此resolver支持的方案Scheme定义可参考https://github.com/grpc/grpc/blob/master/doc/naming.mdtypeResolverinterface{ResolveNow(ResolveNowOption)Close()}Resolver接口监视指定目标的更新包括地址更新和服务配置更新。ResolveNow方法被 gRPC 调用以尝试再次解析目标名称。只用于提示可忽略该方法。Close方法关闭resolver根据以上两个接口我们把服务发现的功能写在Build方法中把获取到的负载均衡服务地址返回到客户端并监视服务更新情况以修改客户端连接。修改服务发现代码discovery.gopackageetcdv3import(contextlogsynctimegithub.com/coreos/etcd/mvcc/mvccpbgo.etcd.io/etcd/clientv3google.golang.org/grpc/resolver)constschemagrpclb//ServiceDiscovery 服务发现typeServiceDiscoverystruct{cli*clientv3.Client//etcd clientcc resolver.ClientConn serverListmap[string]resolver.Address//服务列表lock sync.Mutex}//NewServiceDiscovery 新建发现服务funcNewServiceDiscovery(endpoints[]string)resolver.Builder{cli,err:clientv3.New(clientv3.Config{Endpoints:endpoints,DialTimeout:5*time.Second,})iferr!nil{log.Fatal(err)}returnServiceDiscovery{cli:cli,}}//Build 为给定目标创建一个新的resolver当调用grpc.Dial()时执行func(s*ServiceDiscovery)Build(target resolver.Target,cc resolver.ClientConn,opts resolver.BuildOption)(resolver.Resolver,error){log.Println(Build)s.cccc s.serverListmake(map[string]resolver.Address)prefix:/target.Scheme/target.Endpoint///根据前缀获取现有的keyresp,err:s.cli.Get(context.Background(),prefix,clientv3.WithPrefix())iferr!nil{returnnil,err}for_,ev:rangeresp.Kvs{s.SetServiceList(string(ev.Key),string(ev.Value))}s.cc.NewAddress(s.getServices())//监视前缀修改变更的servergos.watcher(prefix)returns,nil}// ResolveNow 监视目标更新func(s*ServiceDiscovery)ResolveNow(rn resolver.ResolveNowOption){log.Println(ResolveNow)}//Scheme return schemafunc(s*ServiceDiscovery)Scheme()string{returnschema}//Close 关闭func(s*ServiceDiscovery)Close(){log.Println(Close)s.cli.Close()}//watcher 监听前缀func(s*ServiceDiscovery)watcher(prefixstring){rch:s.cli.Watch(context.Background(),prefix,clientv3.WithPrefix())log.Printf(watching prefix:%s now...,prefix)forwresp:rangerch{for_,ev:rangewresp.Events{switchev.Type{casemvccpb.PUT://新增或修改s.SetServiceList(string(ev.Kv.Key),string(ev.Kv.Value))casemvccpb.DELETE://删除s.DelServiceList(string(ev.Kv.Key))}}}}//SetServiceList 新增服务地址func(s*ServiceDiscovery)SetServiceList(key,valstring){s.lock.Lock()defers.lock.Unlock()s.serverList[key]resolver.Address{Addr:val}s.cc.NewAddress(s.getServices())log.Println(put key :,key,val:,val)}//DelServiceList 删除服务地址func(s*ServiceDiscovery)DelServiceList(keystring){s.lock.Lock()defers.lock.Unlock()delete(s.serverList,key)s.cc.NewAddress(s.getServices())log.Println(del key:,key)}//GetServices 获取服务地址func(s*ServiceDiscovery)getServices()[]resolver.Address{addrs:make([]resolver.Address,0,len(s.serverList))for_,v:ranges.serverList{addrsappend(addrs,v)}returnaddrs}代码主要修改以下地方把获取的服务地址转成resolver.Address供gRPC客户端连接。根据schema的定义规则修改key格式。服务注册主要修改key存储格式register.gopackageetcdv3import(contextlogtimego.etcd.io/etcd/clientv3)//ServiceRegister 创建租约注册服务typeServiceRegisterstruct{cli*clientv3.Client//etcd clientleaseID clientv3.LeaseID//租约ID//租约keepalieve相应chankeepAliveChan-chan*clientv3.LeaseKeepAliveResponse keystring//keyvalstring//value}//NewServiceRegister 新建注册服务funcNewServiceRegister(endpoints[]string,serName,addrstring,leaseint64)(*ServiceRegister,error){cli,err:clientv3.New(clientv3.Config{Endpoints:endpoints,DialTimeout:5*time.Second,})iferr!nil{log.Fatal(err)}ser:ServiceRegister{cli:cli,key:/schema/serName/addr,val:addr,}//申请租约设置时间keepaliveiferr:ser.putKeyWithLease(lease);err!nil{returnnil,err}returnser,nil}//设置租约func(s*ServiceRegister)putKeyWithLease(leaseint64)error{//设置租约时间resp,err:s.cli.Grant(context.Background(),lease)iferr!nil{returnerr}//注册服务并绑定租约_,errs.cli.Put(context.Background(),s.key,s.val,clientv3.WithLease(resp.ID))iferr!nil{returnerr}//设置续租 定期发送需求请求leaseRespChan,err:s.cli.KeepAlive(context.Background(),resp.ID)iferr!nil{returnerr}s.leaseIDresp.ID s.keepAliveChanleaseRespChan log.Printf(Put key:%s val:%s success!,s.key,s.val)returnnil}//ListenLeaseRespChan 监听 续租情况func(s*ServiceRegister)ListenLeaseRespChan(){forleaseKeepResp:ranges.keepAliveChan{log.Println(续约成功,leaseKeepResp)}log.Println(关闭续租)}// Close 注销服务func(s*ServiceRegister)Close()error{//撤销租约if_,err:s.cli.Revoke(context.Background(),s.leaseID);err!nil{returnerr}log.Println(撤销租约)returns.cli.Close()}客户端修改gRPC连接服务的部分代码即可funcmain(){r:etcdv3.NewServiceDiscovery(EtcdEndpoints)resolver.Register(r)// 连接服务器conn,err:grpc.Dial(r.Scheme()://8.8.8.8/simple_grpc,grpc.WithBalancerName(round_robin),grpc.WithInsecure())iferr!nil{log.Fatalf(net.Connect err: %v,err)}deferconn.Close()// 建立gRPC连接grpcClientpb.NewSimpleClient(conn)gRPC内置了简单的负载均衡策略round_robin根据负载均衡地址以轮询的方式进行调用服务。服务端启动时把服务地址注册到etcd中即可funcmain(){// 监听本地端口listener,err:net.Listen(Network,Address)iferr!nil{log.Fatalf(net.Listen err: %v,err)}log.Println(Address net.Listing...)// 新建gRPC服务器实例grpcServer:grpc.NewServer()// 在gRPC服务器注册我们的服务pb.RegisterSimpleServer(grpcServer,SimpleService{})//把服务注册到etcdser,err:etcdv3.NewServiceRegister(EtcdEndpoints,SerName,Address,5)iferr!nil{log.Fatalf(register service err: %v,err)}deferser.Close()//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待直到进程被杀死或者 Stop() 被调用errgrpcServer.Serve(listener)iferr!nil{log.Fatalf(grpcServer.Serve err: %v,err)}}运行效果我们先启动并注册三个服务[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CcBjY60C-1613979911433)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518201520301-2141314089.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEDYzKtW-1613979911434)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518201526062-1105611810.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sPyNZUfz-1613979911435)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518201529806-1864982377.png)]然后客户端进行调用[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uf2751dH-1613979911436)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518201645385-8940133.png)]看服务端接收到的请求[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0oh1VkkQ-1613979911437)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518201925163-1429636105.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yNdAKdPX-1613979911438)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518201929077-822092499.png)]关闭localhost:8000服务剩余localhost:8001和localhost:8002服务接收请求[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IsnUNpt2-1613979911438)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518202359155-2143850614.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5NgMmsA6-1613979911439)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518202405272-1990664274.png)]重新打开localhost:8000服务[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DxIslk4E-1613979911440)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518202655967-135791051.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WY5uMrJI-1613979911440)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518202700598-298101288.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oLUQPK6o-1613979911441)(https://img2020.cnblogs.com/blog/1508611/202005/1508611-20200518202703530-602882933.png)]可以看到gRPC客户端负载均衡运行良好。总结本文介绍了gRPC客户端负载均衡的实现它简单实现了gRPC负载均衡的功能。但在对接其他语言时候比较麻烦需要每种语言都实现一套服务发现和负载策略且如果要较为复杂的负载策略需要修改客户端代码才能完成。下篇将介绍如何实现官方推荐的负载均衡策略External Load Balancing Service。源码地址https://github.com/Bingjian-Zhu/etcd-example参考https://segmentfault.com/a/1190000008672912https://github.com/wothing/wonaming

相关新闻

Git仓库完整迁移指南:镜像克隆、裸仓库与远程地址修改三种方法详解

Git仓库完整迁移指南:镜像克隆、裸仓库与远程地址修改三种方法详解

1. 项目缘起:为什么我们需要“完整”迁移Git仓库?最近在帮团队做基础设施的国产化适配,其中一个绕不开的环节就是代码仓库的迁移。这听起来简单,不就是把代码从一个地方复制到另一个地方吗?但实际操作起来,…

2026/8/16 14:10:40 阅读更多 →
2026百元档随身WiFi避坑指南:100元以内,便宜和靠谱怎么兼得?

2026百元档随身WiFi避坑指南:100元以内,便宜和靠谱怎么兼得?

开学季的宿舍、刚搬的出租屋、临时出差的高铁上,很多人第一次考虑随身WiFi时预算都不高,核心问题就一句话:100元以内有没有靠谱的选择?先把结论放前面:预算100元以内,飞猫M1是目前公开参数较完整的机型之一…

2026/8/16 14:53:29 阅读更多 →
IDEA Git集成实战:从基础配置到高效团队协作全指南

IDEA Git集成实战:从基础配置到高效团队协作全指南

1. 项目概述:为什么IDEA里的Git值得你花时间? 如果你是一名Java开发者,或者正在使用IntelliJ IDEA进行任何语言的开发,那么“在IDEA中使用Git”这个标题对你来说,可能意味着一个从“能用”到“高效用”的质变过程。很多…

2026/8/16 14:47:37 阅读更多 →

最新新闻

Windows看B站不流畅?试试开箱即用的BiliBili-UWP第三方客户端

Windows看B站不流畅?试试开箱即用的BiliBili-UWP第三方客户端

Windows看B站不流畅?试试开箱即用的BiliBili-UWP第三方客户端 【免费下载链接】BiliBili-UWP BiliBili的UWP客户端,当然,是第三方的了 项目地址: https://gitcode.com/gh_mirrors/bi/BiliBili-UWP 某个周末,我想在大屏显示…

2026/8/16 18:11:40 阅读更多 →
打造专属你的配色:palenight.vim 颜色覆盖(palenight_color_overrides)完全教程

打造专属你的配色:palenight.vim 颜色覆盖(palenight_color_overrides)完全教程

打造专属你的配色:palenight.vim 颜色覆盖(palenight_color_overrides)完全教程 【免费下载链接】palenight.vim Soothing color scheme for your favorite [best] text editor 项目地址: https://gitcode.com/gh_mirrors/pa/palenight.vim…

2026/8/16 18:11:40 阅读更多 →
PowerToys 汉化包 3 分钟装好:告别英文界面的省心指南

PowerToys 汉化包 3 分钟装好:告别英文界面的省心指南

PowerToys 汉化包 3 分钟装好:告别英文界面的省心指南 【免费下载链接】PowerToys-CN PowerToys Simplified Chinese Translation 微软增强工具箱 自制汉化 项目地址: https://gitcode.com/gh_mirrors/po/PowerToys-CN 正在被 PowerToys 满屏英文劝退的朋友&…

2026/8/16 18:11:40 阅读更多 →
BIMP批量图像处理插件使用指南:从入门到精通,一次配置永久复用

BIMP批量图像处理插件使用指南:从入门到精通,一次配置永久复用

BIMP批量图像处理插件使用指南:从入门到精通,一次配置永久复用 【免费下载链接】gimp-plugin-bimp BIMP. Batch Image Manipulation Plugin for GIMP. 项目地址: https://gitcode.com/gh_mirrors/gi/gimp-plugin-bimp BIMP(Batch Imag…

2026/8/16 18:11:40 阅读更多 →
百度网盘Mac版下载提速指南:3步装好开源插件,把速度拉回正常水平

百度网盘Mac版下载提速指南:3步装好开源插件,把速度拉回正常水平

百度网盘Mac版下载提速指南:3步装好开源插件,把速度拉回正常水平 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 百度网盘Mac版…

2026/8/16 18:11:39 阅读更多 →
百度网盘Mac版下载慢到崩溃?这份提速插件完整攻略,一键解锁SVIP高速下载

百度网盘Mac版下载慢到崩溃?这份提速插件完整攻略,一键解锁SVIP高速下载

百度网盘Mac版下载慢到崩溃?这份提速插件完整攻略,一键解锁SVIP高速下载 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 深夜下…

2026/8/16 18:10:39 阅读更多 →

日新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/16 0:00:54 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/16 0:00:55 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/16 0:03:55 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/16 0:00:54 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/16 0:00:55 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/16 0:03:55 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/16 6:00:23 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/16 6:00:24 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/16 6:00:27 阅读更多 →