gRPC之gRPC负载均衡(客户端负载均衡)(etcd)
1、gRPC负载均衡(客户端负载均衡)(etcd)
本篇将基于etcd的服务发现前提下,介绍如何实现gRPC客户端负载均衡。
1.1 gRPC负载均衡
gRPC官方文档提供了关于gRPC负载均衡方案Load Balancing in gRPC
https://github.com/grpc/grpc/blob/master/doc/load-balancing.md
此方案是为gRPC设计的,下面我们对此进行分析。
1.1.1 对每次调用进行负载均衡
gRPC中的负载平衡是以每次调用为基础,而不是以每个连接为基础。换句话说,即使所有的请求都来自一个客户
端,我们仍希望它们在所有的服务器上实现负载平衡。
1.1.2 负载均衡的方法
集中式(Proxy Model)

在服务消费者和服务提供者之间有一个独立的负载均衡(LB),通常是专门的硬件设备如 F5,或者基于软件如
LVS,HAproxy等实现。LB上有所有服务的地址映射表,通常由运维配置注册,当服务消费方调用某个目标服务
时,它向LB发起请求,由LB以某种策略,比如轮询(Round-Robin)做负载均衡后将请求转发到目标服务。LB一
般具备健康检查能力,能自动摘除不健康的服务实例。
该方案主要问题:服务消费方、提供方之间增加了一级,有一定性能开销,请求量大时,效率较低。
可能有读者会认为集中式负载均衡存在这样的问题,一旦负载均衡服务挂掉,那整个系统将不能使用。
解决方案:可以对负载均衡服务进行DNS负载均衡,通过对一个域名设置多个IP地址,每次DNS解析时轮询
返回负载均衡服务地址,从而实现简单的DNS负载均衡。
客户端负载(Balancing-aware Client)

针对第一个方案的不足,此方案将LB的功能集成到服务消费方进程里,也被称为软负载或者客户端负载方案。服
务提供方启动时,首先将服务地址注册到服务注册表,同时定期报心跳到服务注册表以表明服务的存活状态,相当
于健康检查,服务消费方要访问某个服务时,它通过内置的LB组件向服务注册表查询,同时缓存并定期刷新目标
服务地址列表,然后以某种负载均衡策略选择一个目标服务地址,最后向目标服务发起请求。LB和服务发现能力
被分散到每一个服务消费者的进程内部,同时服务消费方和服务提供方之间是直接调用,没有额外开销,性能比较
好。
该方案主要问题:要用多种语言、多个版本的客户端编写和维护负载均衡策略,使客户端的代码大大复杂化。
独立LB服务(External Load Balancing Service)

该方案是针对第二种方案的不足而提出的一种折中方案,原理和第二种方案基本类似。
不同之处是将LB和服务发现功能从进程内移出来,变成主机上的一个独立进程。主机上的一个或者多个服务要访
问目标服务时,他们都通过同一主机上的独立LB进程做服务发现和负载均衡。该方案也是一种分布式方案没有单
点问题,服务调用方和LB之间是进程内调用性能好,同时该方案还简化了服务调用方,不需要为不同语言开发客
户库。
本篇将介绍第二种负载均衡方法,客户端负载均衡。
1.2 实现gRPC客户端负载均衡
gRPC已提供了简单的负载均衡策略(如:Round Robin),我们只需实现它提供的Builder和Resolver接口,
就能完成gRPC客户端负载均衡。
type Builder interface {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.md
type Resolver interface {ResolveNow(ResolveNowOption)Close()
}
Resolver接口:监视指定目标的更新,包括地址更新和服务配置更新。
ResolveNow方法:被 gRPC 调用,以尝试再次解析目标名称。只用于提示,可忽略该方法。
Close方法:关闭resolver。
根据以上两个接口,我们把服务发现的功能写在Build方法中,把获取到的负载均衡服务地址返回到客户端,并
监视服务更新情况,以修改客户端连接。
1.3 服务发现代码修改
服务发现代码./etcdv3/discovery.go的内容:
package etcdv3import ("context""github.com/coreos/etcd/mvcc/mvccpb""go.etcd.io/etcd/clientv3""google.golang.org/grpc/resolver""log""sync""time"
)const schema = "grpclb"// ServiceDiscovery 服务发现
type ServiceDiscovery struct {// etcd clientcli *clientv3.Clientcc resolver.ClientConn// 服务列表serverList sync.Map
}// NewServiceDiscovery新建发现服务
func NewServiceDiscovery(endpoints []string) resolver.Builder {cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints,DialTimeout: 5 * time.Second,})if err != nil {log.Fatal(err)}return &ServiceDiscovery{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.cc = cc// /grpclb/simple_grpc/prefix := "/" + target.Scheme + "/" + target.Endpoint + "/"// 根据前缀获取现有的keyresp, err := s.cli.Get(context.Background(), prefix, clientv3.WithPrefix())if err != nil {return nil, err}for _, ev := range resp.Kvs {s.SetServiceList(string(ev.Key), string(ev.Value))}s.cc.UpdateState(resolver.State{Addresses: s.getServices()})// 监视前缀,修改变更的servergo s.watcher(prefix)return s, nil
}// ResolveNow监视目标更新
func (s *ServiceDiscovery) ResolveNow(rn resolver.ResolveNowOption) {log.Println("ResolveNow")
}// Scheme return schema
func (s *ServiceDiscovery) Scheme() string {return schema
}// Close关闭
func (s *ServiceDiscovery) Close() {log.Println("Close")s.cli.Close()
}// watcher监听前缀
func (s *ServiceDiscovery) watcher(prefix string) {rch := s.cli.Watch(context.Background(), prefix, clientv3.WithPrefix())log.Printf("watching prefix:%s now...", prefix)for wresp := range rch {for _, ev := range wresp.Events {switch ev.Type {// 新增或修改case mvccpb.PUT:s.SetServiceList(string(ev.Kv.Key), string(ev.Kv.Value))// 删除case mvccpb.DELETE:s.DelServiceList(string(ev.Kv.Key))}}}
}// SetServiceList新增服务地址
func (s *ServiceDiscovery) SetServiceList(key, val string) {s.serverList.Store(key, resolver.Address{Addr: val})s.cc.UpdateState(resolver.State{Addresses: s.getServices()})log.Println("put key :", key, "val:", val)
}// DelServiceList删除服务地址
func (s *ServiceDiscovery) DelServiceList(key string) {s.serverList.Delete(key)s.cc.UpdateState(resolver.State{Addresses: s.getServices()})log.Println("del key:", key)
}// GetServices获取服务地址
func (s *ServiceDiscovery) getServices() []resolver.Address {addrs := make([]resolver.Address, 0, 10)s.serverList.Range(func(k, v interface{}) bool {addrs = append(addrs, v.(resolver.Address))return true})// [{localhost:8000 <nil> 0 <nil>}]return addrs
}
代码主要修改以下地方:
1、把获取的服务地址转成resolver.Address,供gRPC客户端连接。
2、根据schema的定义规则,修改key格式。
1.4 服务注册代码修改
服务发现代码./etcdv3/register.go的内容:
package etcdv3import ("context""go.etcd.io/etcd/clientv3""log""time"
)// ServiceRegister创建租约注册服务
type ServiceRegister struct {// etcd clientcli *clientv3.Client// 租约IDleaseID clientv3.LeaseID// 租约keepalieve相应chankeepAliveChan <-chan *clientv3.LeaseKeepAliveResponse// keykey string// valueval string
}// NewServiceRegister 新建注册服务
func NewServiceRegister(endpoints []string, serName, addr string, lease int64) (*ServiceRegister, error) {cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints,DialTimeout: 5 * time.Second,})if err != nil {log.Fatal(err)}ser := &ServiceRegister{cli: cli,// /grpclb/simple_grpc/localhost:8000key: "/" + schema + "/" + serName + "/" + addr,val: addr,}// 申请租约设置时间keepaliveif err := ser.putKeyWithLease(lease); err != nil {return nil, err}return ser, nil
}// 设置租约
func (s *ServiceRegister) putKeyWithLease(lease int64) error {// 设置租约时间resp, err := s.cli.Grant(context.Background(), lease)if err != nil {return err}// 注册服务并绑定租约_, err = s.cli.Put(context.Background(), s.key, s.val, clientv3.WithLease(resp.ID))if err != nil {return err}// 设置续租 定期发送需求请求leaseRespChan, err := s.cli.KeepAlive(context.Background(), resp.ID)if err != nil {return err}s.leaseID = resp.IDs.keepAliveChan = leaseRespChanlog.Printf("Put key:%s val:%s success!", s.key, s.val)return nil
}// ListenLeaseRespChan监听续租情况
func (s *ServiceRegister) ListenLeaseRespChan() {for leaseKeepResp := range s.keepAliveChan {log.Println("续约成功", leaseKeepResp)}log.Println("关闭续租")
}// Close注销服务
func (s *ServiceRegister) Close() error {// 撤销租约if _, err := s.cli.Revoke(context.Background(), s.leaseID); err != nil {return err}log.Println("撤销租约")return s.cli.Close()
}
服务注册主要修改key存储格式。
1.5 proto编写和编译
simple.proto文件的内容:
// 协议为proto3
syntax = "proto3";
package proto;
option go_package = "./proto;proto";// 定义发送请求信息
message SimpleRequest{// 定义发送的参数,采用驼峰命名方式,小写加下划线,如:student_name// 参数类型 参数名 标识号(不可重复)string data = 1;
}// 定义响应信息
message SimpleResponse{// 定义接收的参数// 参数类型 参数名 标识号(不可重复)int32 code = 1;string value = 2;
}// 定义我们的服务(可定义多个服务,每个服务可定义多个接口)
service Simple{rpc Route (SimpleRequest) returns (SimpleResponse){};
}
编译simple.proto文件:
$ protoc --go_out=plugins=grpc:. simple.proto
1.6 gRPC客户端
gRPC内置了简单的负载均衡策略round_robin,根据负载均衡地址,以轮询的方式进行调用服务。
客户端修改gRPC连接服务的部分代码,client.go内容如下:
package mainimport ("context""etcd2/etcdv3"pb "etcd2/proto""fmt""google.golang.org/grpc""google.golang.org/grpc/resolver""log""strconv""time"
)var (// EtcdEndpoints etcd集群地址EtcdEndpoints = []string{"localhost:2379"}// SerName服务名称SerName = "simple_grpc"grpcClient pb.SimpleClient
)func main() {r := etcdv3.NewServiceDiscovery(EtcdEndpoints)resolver.Register(r)// 连接服务器conn, err := grpc.Dial(// grpclb:///simple_grpcfmt.Sprintf("%s:///%s", r.Scheme(), SerName),grpc.WithBalancerName("round_robin"),grpc.WithInsecure(),)if err != nil {log.Fatalf("net.Connect err: %v", err)}defer conn.Close()// 建立gRPC连接grpcClient = pb.NewSimpleClient(conn)for i := 0; i < 100; i++ {route(i)time.Sleep(1 * time.Second)}
}// route调用服务端Route方法
func route(i int) {// 创建发送结构体req := pb.SimpleRequest{Data: "grpc " + strconv.Itoa(i),}// 调用我们的服务(Route方法)// 同时传入了一个 context.Context ,在有需要时可以让我们改变RPC的行为,比如超时/取消一个正在运行的RPCres, err := grpcClient.Route(context.Background(), &req)if err != nil {log.Fatalf("Call Route err: %v", err)}// 打印返回值log.Println(res)
}
1.7 gRPC服务端
服务端启动时,把服务地址注册到etcd中即可,server.go内容如下:
package mainimport ("context""etcd2/etcdv3"pb "etcd2/proto""google.golang.org/grpc""log""net"
)// SimpleService定义我们的服务
type SimpleService struct{}const (// Address监听地址,服务端地址Address string = "localhost:8000"// Network网络通信协议Network string = "tcp"// SerName服务名称SerName string = "simple_grpc"
)// EtcdEndpoints etcd集群地址
var EtcdEndpoints = []string{"localhost:2379"}func main() {// 监听本地端口listener, err := net.Listen(Network, Address)if err != 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)if err != nil {log.Fatalf("register service err: %v", err)}defer ser.Close()// 用服务器Serve()方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者Stop()被调用err = grpcServer.Serve(listener)if err != nil {log.Fatalf("grpcServer.Serve err: %v", err)}
}// Route实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {log.Println("receive: " + req.Data)res := pb.SimpleResponse{Code: 200,Value: "hello " + req.Data,}return &res, nil
}
1.8 运行效果
我们先启动并注册三个服务:
[root@zsx etcd2]# go run server.go
2023-02-13 20:51:54.173418 I | localhost:8000 net.Listing...
2023-02-13 20:51:54.207555 I | Put key:/grpclb/simple_grpc/localhost:8000 val:localhost:8000 success!
[root@zsx etcd2]# go run server1.go
2023-02-13 20:52:02.041409 I | localhost:8001 net.Listing...
2023-02-13 20:52:02.046411 I | Put key:/grpclb/simple_grpc/localhost:8001 val:localhost:8001 success!
[root@zsx etcd2]# go run server2.go
2023-02-13 20:52:06.644427 I | localhost:8002 net.Listing...
2023-02-13 20:52:06.647214 I | Put key:/grpclb/simple_grpc/localhost:8002 val:localhost:8002 success!
然后客户端进行调用:
[root@zsx etcd2]# go run client.go
2023-02-13 20:53:43.841502 I | Build
2023-02-13 20:53:43.844638 I | put key : /grpclb/simple_grpc/localhost:8000 val: localhost:8000
2023-02-13 20:53:43.844659 I | put key : /grpclb/simple_grpc/localhost:8001 val: localhost:8001
2023-02-13 20:53:43.844668 I | put key : /grpclb/simple_grpc/localhost:8002 val: localhost:8002
2023-02-13 20:53:43.849315 I | watching prefix:/grpclb/simple_grpc/ now...
2023-02-13 20:53:43.849949 I | code:200 value:"hello grpc 0"
2023-02-13 20:53:44.851034 I | code:200 value:"hello grpc 1"
2023-02-13 20:53:45.852734 I | code:200 value:"hello grpc 2"
2023-02-13 20:53:46.854096 I | code:200 value:"hello grpc 3"
2023-02-13 20:53:47.855628 I | code:200 value:"hello grpc 4"
2023-02-13 20:53:48.857095 I | code:200 value:"hello grpc 5"
2023-02-13 20:53:49.858414 I | code:200 value:"hello grpc 6"
2023-02-13 20:53:50.859152 I | code:200 value:"hello grpc 7"
2023-02-13 20:53:51.860268 I | code:200 value:"hello grpc 8"
2023-02-13 20:53:52.860825 I | code:200 value:"hello grpc 9"
2023-02-13 20:53:53.861698 I | code:200 value:"hello grpc 10"
2023-02-13 20:53:54.864353 I | code:200 value:"hello grpc 11"
2023-02-13 20:53:55.865019 I | code:200 value:"hello grpc 12"
2023-02-13 20:53:56.865771 I | code:200 value:"hello grpc 13"
2023-02-13 20:53:57.866400 I | code:200 value:"hello grpc 14"
2023-02-13 20:53:58.867066 I | code:200 value:"hello grpc 15"
2023-02-13 20:53:59.868412 I | code:200 value:"hello grpc 16"
2023-02-13 20:54:00.869399 I | code:200 value:"hello grpc 17"
2023-02-13 20:54:01.870803 I | code:200 value:"hello grpc 18"
2023-02-13 20:54:02.872201 I | code:200 value:"hello grpc 19"
2023-02-13 20:54:03.872696 I | code:200 value:"hello grpc 20"
2023-02-13 20:54:04.874159 I | code:200 value:"hello grpc 21"
2023-02-13 20:54:05.875990 I | code:200 value:"hello grpc 22"
2023-02-13 20:54:06.877799 I | code:200 value:"hello grpc 23"
2023-02-13 20:54:07.878814 I | code:200 value:"hello grpc 24"
2023-02-13 20:54:08.881218 I | code:200 value:"hello grpc 25"
......
看服务端接收到的请求:
[root@zsx etcd2]# go run server.go
2023-02-13 20:51:54.173418 I | localhost:8000 net.Listing...
2023-02-13 20:51:54.207555 I | Put key:/grpclb/simple_grpc/localhost:8000 val:localhost:8000 success!
2023-02-13 20:53:43.849692 I | receive: grpc 0
2023-02-13 20:53:45.852147 I | receive: grpc 2
2023-02-13 20:53:48.856715 I | receive: grpc 5
2023-02-13 20:53:51.860086 I | receive: grpc 8
2023-02-13 20:53:54.863486 I | receive: grpc 11
2023-02-13 20:53:57.866286 I | receive: grpc 14
2023-02-13 20:54:00.869018 I | receive: grpc 17
2023-02-13 20:54:03.872545 I | receive: grpc 20
2023-02-13 20:54:06.877412 I | receive: grpc 23
......
[root@zsx etcd2]# go run server1.go
2023-02-13 20:52:02.041409 I | localhost:8001 net.Listing...
2023-02-13 20:52:02.046411 I | Put key:/grpclb/simple_grpc/localhost:8001 val:localhost:8001 success!
2023-02-13 20:53:46.853933 I | receive: grpc 3
2023-02-13 20:53:49.858075 I | receive: grpc 6
2023-02-13 20:53:52.860683 I | receive: grpc 9
2023-02-13 20:53:55.864896 I | receive: grpc 12
2023-02-13 20:53:58.866946 I | receive: grpc 15
2023-02-13 20:54:01.870692 I | receive: grpc 18
2023-02-13 20:54:04.874044 I | receive: grpc 21
......
[root@zsx etcd2]# go run server2.go
2023-02-13 20:52:06.644427 I | localhost:8002 net.Listing...
2023-02-13 20:52:06.647214 I | Put key:/grpclb/simple_grpc/localhost:8002 val:localhost:8002 success!
2023-02-13 20:53:44.850843 I | receive: grpc 1
2023-02-13 20:53:47.855201 I | receive: grpc 4
2023-02-13 20:53:50.859003 I | receive: grpc 7
2023-02-13 20:53:53.861487 I | receive: grpc 10
2023-02-13 20:53:56.865643 I | receive: grpc 13
2023-02-13 20:53:59.868263 I | receive: grpc 16
2023-02-13 20:54:02.872082 I | receive: grpc 19
2023-02-13 20:54:05.875695 I | receive: grpc 22
2023-02-13 20:54:08.880472 I | receive: grpc 25
......
关闭localhost:8000服务,剩余localhost:8001和localhost:8002服务接收请求:
[root@zsx etcd2]# go run server1.go
2023-02-13 20:56:48.915356 I | localhost:8001 net.Listing...
2023-02-13 20:56:48.917716 I | Put key:/grpclb/simple_grpc/localhost:8001 val:localhost:8001 success!
2023-02-13 20:56:57.599656 I | receive: grpc 1
2023-02-13 20:57:00.603955 I | receive: grpc 4
2023-02-13 20:57:03.607802 I | receive: grpc 7
2023-02-13 20:57:06.612719 I | receive: grpc 10
2023-02-13 20:57:09.615636 I | receive: grpc 13
2023-02-13 20:57:12.621450 I | receive: grpc 16
2023-02-13 20:57:15.624067 I | receive: grpc 19
2023-02-13 20:57:18.627407 I | receive: grpc 22
2023-02-13 20:57:21.630674 I | receive: grpc 25
2023-02-13 20:57:24.635254 I | receive: grpc 28
2023-02-13 20:57:27.638432 I | receive: grpc 31
2023-02-13 20:57:30.642211 I | receive: grpc 34
2023-02-13 20:57:32.644497 I | receive: grpc 36
2023-02-13 20:57:34.646649 I | receive: grpc 38
2023-02-13 20:57:36.650191 I | receive: grpc 40
2023-02-13 20:57:38.653029 I | receive: grpc 42
2023-02-13 20:57:40.654724 I | receive: grpc 44
2023-02-13 20:57:42.656665 I | receive: grpc 46
2023-02-13 20:57:44.658922 I | receive: grpc 48
2023-02-13 20:57:46.661156 I | receive: grpc 50
2023-02-13 20:57:48.663613 I | receive: grpc 52
2023-02-13 20:57:50.666520 I | receive: grpc 54
2023-02-13 20:57:52.668219 I | receive: grpc 56
2023-02-13 20:57:54.670777 I | receive: grpc 58
2023-02-13 20:57:56.673138 I | receive: grpc 60
2023-02-13 20:57:58.674908 I | receive: grpc 62
2023-02-13 20:58:00.677698 I | receive: grpc 64
2023-02-13 20:58:02.680816 I | receive: grpc 66
2023-02-13 20:58:04.686075 I | receive: grpc 68
......
[root@zsx etcd2]# go run server2.go
2023-02-13 20:56:51.462814 I | localhost:8002 net.Listing...
2023-02-13 20:56:51.467130 I | Put key:/grpclb/simple_grpc/localhost:8002 val:localhost:8002 success!
2023-02-13 20:56:58.600367 I | receive: grpc 2
2023-02-13 20:57:01.605054 I | receive: grpc 5
2023-02-13 20:57:04.608898 I | receive: grpc 8
2023-02-13 20:57:07.613784 I | receive: grpc 11
2023-02-13 20:57:10.617774 I | receive: grpc 14
2023-02-13 20:57:13.622261 I | receive: grpc 17
2023-02-13 20:57:16.624596 I | receive: grpc 20
2023-02-13 20:57:19.628076 I | receive: grpc 23
2023-02-13 20:57:22.632461 I | receive: grpc 26
2023-02-13 20:57:25.636681 I | receive: grpc 29
2023-02-13 20:57:28.639889 I | receive: grpc 32
2023-02-13 20:57:31.643062 I | receive: grpc 35
2023-02-13 20:57:33.645208 I | receive: grpc 37
2023-02-13 20:57:35.648217 I | receive: grpc 39
2023-02-13 20:57:37.652005 I | receive: grpc 41
2023-02-13 20:57:39.653990 I | receive: grpc 43
2023-02-13 20:57:41.656116 I | receive: grpc 45
2023-02-13 20:57:43.657680 I | receive: grpc 47
2023-02-13 20:57:45.659936 I | receive: grpc 49
2023-02-13 20:57:47.662667 I | receive: grpc 51
2023-02-13 20:57:49.665158 I | receive: grpc 53
2023-02-13 20:57:51.667787 I | receive: grpc 55
2023-02-13 20:57:53.669130 I | receive: grpc 57
2023-02-13 20:57:55.672291 I | receive: grpc 59
2023-02-13 20:57:57.673711 I | receive: grpc 61
2023-02-13 20:57:59.676281 I | receive: grpc 63
2023-02-13 20:58:01.679348 I | receive: grpc 65
2023-02-13 20:58:03.682384 I | receive: grpc 67
2023-02-13 20:58:05.687477 I | receive: grpc 69
......
重新打开localhost:8000服务
[root@zsx etcd2]# go run server.go
2023-02-13 20:58:05.315863 I | localhost:8000 net.Listing...
2023-02-13 20:58:05.322321 I | Put key:/grpclb/simple_grpc/localhost:8000 val:localhost:8000 success!
2023-02-13 20:58:06.688247 I | receive: grpc 70
2023-02-13 20:58:09.691796 I | receive: grpc 73
2023-02-13 20:58:12.697446 I | receive: grpc 76
2023-02-13 20:58:15.700649 I | receive: grpc 79
2023-02-13 20:58:18.704804 I | receive: grpc 82
2023-02-13 20:58:21.711890 I | receive: grpc 85
2023-02-13 20:58:24.721398 I | receive: grpc 88
2023-02-13 20:58:27.726881 I | receive: grpc 91
2023-02-13 20:58:30.733417 I | receive: grpc 94
2023-02-13 20:58:33.739445 I | receive: grpc 97
[root@zsx etcd2]# go run server1.go
2023-02-13 20:56:48.915356 I | localhost:8001 net.Listing...
2023-02-13 20:56:48.917716 I | Put key:/grpclb/simple_grpc/localhost:8001 val:localhost:8001 success!
2023-02-13 20:56:57.599656 I | receive: grpc 1
2023-02-13 20:57:00.603955 I | receive: grpc 4
2023-02-13 20:57:03.607802 I | receive: grpc 7
2023-02-13 20:57:06.612719 I | receive: grpc 10
2023-02-13 20:57:09.615636 I | receive: grpc 13
2023-02-13 20:57:12.621450 I | receive: grpc 16
2023-02-13 20:57:15.624067 I | receive: grpc 19
2023-02-13 20:57:18.627407 I | receive: grpc 22
2023-02-13 20:57:21.630674 I | receive: grpc 25
2023-02-13 20:57:24.635254 I | receive: grpc 28
2023-02-13 20:57:27.638432 I | receive: grpc 31
2023-02-13 20:57:30.642211 I | receive: grpc 34
2023-02-13 20:57:32.644497 I | receive: grpc 36
2023-02-13 20:57:34.646649 I | receive: grpc 38
2023-02-13 20:57:36.650191 I | receive: grpc 40
2023-02-13 20:57:38.653029 I | receive: grpc 42
2023-02-13 20:57:40.654724 I | receive: grpc 44
2023-02-13 20:57:42.656665 I | receive: grpc 46
2023-02-13 20:57:44.658922 I | receive: grpc 48
2023-02-13 20:57:46.661156 I | receive: grpc 50
2023-02-13 20:57:48.663613 I | receive: grpc 52
2023-02-13 20:57:50.666520 I | receive: grpc 54
2023-02-13 20:57:52.668219 I | receive: grpc 56
2023-02-13 20:57:54.670777 I | receive: grpc 58
2023-02-13 20:57:56.673138 I | receive: grpc 60
2023-02-13 20:57:58.674908 I | receive: grpc 62
2023-02-13 20:58:00.677698 I | receive: grpc 64
2023-02-13 20:58:02.680816 I | receive: grpc 66
2023-02-13 20:58:04.686075 I | receive: grpc 68
2023-02-13 20:58:07.689105 I | receive: grpc 71
2023-02-13 20:58:10.693713 I | receive: grpc 74
2023-02-13 20:58:13.698055 I | receive: grpc 77
2023-02-13 20:58:16.701562 I | receive: grpc 80
2023-02-13 20:58:19.706860 I | receive: grpc 83
2023-02-13 20:58:22.715951 I | receive: grpc 86
2023-02-13 20:58:25.723692 I | receive: grpc 89
2023-02-13 20:58:28.728865 I | receive: grpc 92
2023-02-13 20:58:31.735971 I | receive: grpc 95
2023-02-13 20:58:34.740998 I | receive: grpc 98
[root@zsx etcd2]# go run server2.go
2023-02-13 20:56:51.462814 I | localhost:8002 net.Listing...
2023-02-13 20:56:51.467130 I | Put key:/grpclb/simple_grpc/localhost:8002 val:localhost:8002 success!
2023-02-13 20:56:58.600367 I | receive: grpc 2
2023-02-13 20:57:01.605054 I | receive: grpc 5
2023-02-13 20:57:04.608898 I | receive: grpc 8
2023-02-13 20:57:07.613784 I | receive: grpc 11
2023-02-13 20:57:10.617774 I | receive: grpc 14
2023-02-13 20:57:13.622261 I | receive: grpc 17
2023-02-13 20:57:16.624596 I | receive: grpc 20
2023-02-13 20:57:19.628076 I | receive: grpc 23
2023-02-13 20:57:22.632461 I | receive: grpc 26
2023-02-13 20:57:25.636681 I | receive: grpc 29
2023-02-13 20:57:28.639889 I | receive: grpc 32
2023-02-13 20:57:31.643062 I | receive: grpc 35
2023-02-13 20:57:33.645208 I | receive: grpc 37
2023-02-13 20:57:35.648217 I | receive: grpc 39
2023-02-13 20:57:37.652005 I | receive: grpc 41
2023-02-13 20:57:39.653990 I | receive: grpc 43
2023-02-13 20:57:41.656116 I | receive: grpc 45
2023-02-13 20:57:43.657680 I | receive: grpc 47
2023-02-13 20:57:45.659936 I | receive: grpc 49
2023-02-13 20:57:47.662667 I | receive: grpc 51
2023-02-13 20:57:49.665158 I | receive: grpc 53
2023-02-13 20:57:51.667787 I | receive: grpc 55
2023-02-13 20:57:53.669130 I | receive: grpc 57
2023-02-13 20:57:55.672291 I | receive: grpc 59
2023-02-13 20:57:57.673711 I | receive: grpc 61
2023-02-13 20:57:59.676281 I | receive: grpc 63
2023-02-13 20:58:01.679348 I | receive: grpc 65
2023-02-13 20:58:03.682384 I | receive: grpc 67
2023-02-13 20:58:05.687477 I | receive: grpc 69
2023-02-13 20:58:08.690259 I | receive: grpc 72
2023-02-13 20:58:11.696068 I | receive: grpc 75
2023-02-13 20:58:14.699459 I | receive: grpc 78
2023-02-13 20:58:17.702863 I | receive: grpc 81
2023-02-13 20:58:20.709975 I | receive: grpc 84
2023-02-13 20:58:23.718648 I | receive: grpc 87
2023-02-13 20:58:26.725422 I | receive: grpc 90
2023-02-13 20:58:29.731039 I | receive: grpc 93
2023-02-13 20:58:32.737702 I | receive: grpc 96
2023-02-13 20:58:35.742928 I | receive: grpc 99
可以看到,gRPC客户端负载均衡运行良好。
# 项目结构
[root@zsx protoc]# tree etcd2
etcd2
├── client.go
├── etcdv3
│ ├── discovery.go
│ └── register.go
├── go.mod
├── go.sum
├── proto
│ └── simple.pb.go
├── server1.go
├── server2.go
├── server.go
└── simple.proto2 directories, 10 files
1.9 总结
本文介绍了gRPC客户端负载均衡的实现,它简单实现了gRPC负载均衡的功能。但在对接其他语言时候比较麻烦,
需要每种语言都实现一套服务发现和负载策略。
目前官方只提供了取第一个地址pick_first和轮询round_robin两种负载均衡策略。
下篇将介绍如何自定义负载均衡策略。
有兴趣了解第三种负载均衡方法External Load Balancing Service的,可以参考这个项目:
https://github.com/bsm/grpclb
关于负载均衡的其它写法可以参考一下GitHub:
-
https://github.com/wothing/wonaming -
https://github.com/wwcd/grpc-lb
相关文章:
gRPC之gRPC负载均衡(客户端负载均衡)(etcd)
1、gRPC负载均衡(客户端负载均衡)(etcd) 本篇将基于etcd的服务发现前提下,介绍如何实现gRPC客户端负载均衡。 1.1 gRPC负载均衡 gRPC官方文档提供了关于gRPC负载均衡方案Load Balancing in gRPC https://github.com/grpc/grpc/blob/master/doc/load-balancing.m…...
语音识别技术paddlespeech的安装和使用
PaddleSpeech 介绍 PaddleSpeech是百度飞桨(PaddlePaddle)开源深度学习平台的其中一个项目,它基于飞桨的语音方向模型库,用于语音和音频中的各种关键任务的开发,包含大量基于深度学习前沿和有影响力的模型。PaddleSpe…...
【机器学习】034_多层感知机Part.2_从零实现多层感知机
一、解决XOR问题 1. 回顾XOR问题: 如图,如何对XOR面进行分割以划分四个输入 对应的输出 呢? 思路:采用两个分类器分类,每次分出两个输入 ,再借助这两个分类从而分出 。 即采用同或运算,当两…...
2023年中职“网络安全“—Web 渗透测试①
2023年中职"网络安全"—Web 渗透测试① Web 渗透测试任务环境说明:1.访问地址http://靶机IP/task1,分析页面内容,获取flag值,Flag格式为flag{xxx};2.访问地址http://靶机IP/task2,访问登录页面。…...
Android——资源IDnonFinalResIds和“Attribute value must be constant”错误
一、异常描述 通过资源ID引用资源提示错误 Attribute value must be constant 二、解决方案 在根目录下的文件 gradle.properties 中添加如下配置,然后Sync Project android.nonFinalResIdsfalse 三、问题原因 android.nonFinalResIds 是Android开发中一个用于解…...
批量创建表空间数据文件(DM8:达梦数据库)
DM8:达梦数据库 - - 批量创建表空间数据文件 环境介绍1 批量创建表空间SQL2 达梦数据库学习使用列表 环境介绍 在某些场景(分区表子表)需要批量创建表空间,给不同的表使用,以下代码是批量创建表空间的SQL语句; 1 批量创建表空间SQL --创建 24个数据表空间,每个表空间有3个数…...
简单聊聊加密和加签的关系与区别
大家好,我是G探险者。 平时我们在项目上一定都听过加密和加签,加密可能都好理解,知道它是保障的数据的机密性,那加签是为了保障啥勒?它和加密有啥区别? 带着这个疑问,我们就来聊聊二者的区别。…...
视频转码方法:多种格式视频批量转FLV视频的技巧
随着互联网的发展,视频已成为日常生活中不可或缺的一部分。然而,不同的视频格式可能适用于不同的设备和平台,因此需要进行转码。在转码之前,要了解各种视频格式的特点和适用场景。常见的视频格式包括MP4、AVI、MKV、FLV等。其中&a…...
【Java 进阶篇】Redis 数据结构:轻松驾驭多样性
引言 Redis是一款强大的键值对存储系统,其数据结构的多样性是其引以为傲的特点之一。在这篇博客中,我们将深入探讨Redis的主要数据结构,包括字符串、哈希表、列表、集合和有序集合,并通过实例代码演示它们的用法。 1. 字符串&am…...
东用科技智能公交识别系统无线传输方案
在科技不断进步和人工智能快速发展的当下,人脸识别技术已逐渐应用于各个领域。其中,公共交通领域便是重要的应用场景之一。人脸识别技术的引入可以提高交通的安全性、效率及便利性。 为了实现公交公司对乘客的身份识别和安全管理的需求,提高运…...
Django批量插入数据及分页器
文章目录 一、批量插入数据二、分页1.分页器的思路2.用一个案例试试3.自定义分页器 一、批量插入数据 当我们需要大批量创建数据的时候,如果一条一条的去创建或许需要猴年马月 我们可以先试一试for循环试试 我们首先建立一个模型类来创建一个表 models.pyÿ…...
PHP 语法||PHP 变量
PHP 脚本在服务器上执行,然后将纯 HTML 结果发送回浏览器。 基本的 PHP 语法 PHP 脚本可以放在文档中的任何位置。 PHP 脚本以 <?php 开始,以 ?> 结束: <?php // PHP 代码 ?> 值得一提的是,通过设定php.ini的相…...
【python基础(四)】if语句详解
文章目录 一. 一个简单示例二. 条件测试1. 检查多个条件1.1. 使用and关联多个条件1.2. 使用or检查多个条件1.3. in的判断 2. 布尔表达式 三. if语句1. 简单的if语句2. if-else语句3. if-elif-else结构4. 使用多个elif代码块5. 省略else代码块 四. 使用if语句处理列表1. 检查特殊…...
Spring Boot中常用的参数传递注解
RequestParam:用于将请求参数绑定到控制器处理方法的参数上,适用于GET请求。PathVariable:用于获取请求URL中的动态参数,适用于RESTful风格的URL。RequestBody:用于将请求体中的JSON字符串绑定到控制器处理方法的参数上…...
Quartz .Net 的简单使用
参考了:c# .net framework 4.5.2 , Quartz.NET 3.0.7 - runliuv - 博客园 (cnblogs.com) https://www.cnblogs.com/personblog/p/11277527.html, Quartz.NET 作业调度(一):Test - 简书 自己要轮询的任务:…...
面试Java笔试题精选解答
文章目录 热身级别数组中重复的数字思路:使用map或HashSet来遍历一遍就可以找出重复的字符样例解答 用两个栈实现队列思路:Stack1正向进入,队头在栈底,用于进队列操作;Stack2是Stack1倒栈形成,队头在栈顶&a…...
使用Python画一棵树
🎊专栏【不单调的代码】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【如愿】 🥰欢迎并且感谢大家指出我的问题 文章目录 🌹Turtle模块🎄效果🌺代码🛸代码…...
nginx学习(4)Nginx 负载均衡
负载均衡:是将负载分摊到不同的服务单元,既保证服务的可用性,又保证响应 足够快,给用户很好的体验。 在 linux 下有 Nginx、LVS、Haproxy 等等服务可以提供负载均衡服 务, 而且 Nginx 提供了几种分配方式(策略)&#…...
WSL登录时提示nsenter: cannot open /proc/320/ns/time: No such file or directory的解决办法
在登录 WSL 的 Ubuntu 时,不仅要求 root 权限,还登录失败,提示“nsenter: cannot open /proc/320/ns/time: No such file or directory”。 解决办法是在 powershell 中执行 “wsl – sudo vi /etc/profile”命令,删除文件内容&a…...
git修改远程分支名称
先拉取old_branch最新代码到本地 git checkout old_branchgit pull origin old_branch本地修改后并推送 git branch -m old_branch new_branch # 修改分支名称git push --delete origin old_branch # 删除在远程的老分支推送新分支 git push origin new_branch本地分支与远…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
