最简单的 gRPC 教程—4 多路复用、元数据、负载均衡

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 前面已经介绍了几种 gRPC 的进阶特性,这篇文章再来看看 gRPC 的:• 多路复用• 元数据• 负载均衡我把前面的 Order 服务再复制一份,作为本篇文章的代码演示。

前面已经介绍了几种 gRPC 的进阶特性,这篇文章再来看看 gRPC 的:

  • 多路复用
  • 元数据
  • 负载均衡

我把前面的 Order 服务再复制一份,作为本篇文章的代码演示。


多路复用


首先来看看多路复用,在前面的代码演示中,服务器端其实只有一个 gRPC 服务,但是 gRPC 还支持在一个服务端运行多个 gRPC 服务。




我们有一个现成的订单 Order 服务,这里我再新增一个 问候服务 Greeter,还是在 OrderInfo.proto 文件中,添加如下代码:

// 问候服务
service GreeterService {
  rpc sayHello(google.protobuf.StringValue) returns (google.protobuf.StringValue);
}


然后在服务端的 main.go 中注册服务:

s := grpc.NewServer()
//注册订单服务
order.RegisterOrderManagementServer(s, server)
//注册问候服务
order.RegisterGreeterServiceServer(s, &GreeterServer{})


在客户端这边,调用的时候,可以创建对应的 Client,然后调用相应的方法即可:

// 问候服务客户端
greeterClient := order.NewGreeterServiceClient(conn)
_, err = greeterClient.SayHello(ctx, &wrappers.StringValue{Value: "roseduan"})
if err != nil {
   log.Println("call greeter server [say hello] err.", err)
}


元数据

在多个微服务的调用当中,信息交换常常是使用方法之间的参数传递的方式,但是在有些场景下,一些信息可能和 RPC 方法的业务参数没有直接的关联,所以不能作为参数的一部分,在 gRPC 中,可以使用元数据来存储这类信息。

首先来看一下一个简单的元数据的发送和接收的例子,首先在客户端定义并在创建订单的时候发送元数据信息:

client := order.NewOrderManagementClient(conn)
md := metadata.Pairs(
   "timestamp", time.Now().Format(time.RFC3339),
   "test-key", "val1",
   "test-key", "val2",
)
//使用元数据context
mdCtx := metadata.NewOutgoingContext(context.Background(), md)
fmt.Println("----------------use metadata----------------")
testOrder := &order.Order{Destination: "beijing", Items: []string{"book1", "book2"}, Price: 123.232}
_, err = client.AddOrder(mdCtx, testOrder)


然后就可以在服务端的 AddOrder 方法中,获取到设置的 metadata 信息了:

//获取元数据
if md, ok := metadata.FromIncomingContext(ctx); !ok {
   log.Println("failed to get metadata")
} else {
   log.Printf("metadata from client : %+v\n", md)
}


当然,在服务器也可以发送元数据到客户端当中,比如下面的这个例子:

在服务端的方法中定义:

//发送一个header元信息
md := metadata.New(map[string]string{"location": "San Jose", "timestamp": time.Now().Format(time.StampNano)})
err = grpc.SendHeader(ctx, md)
if err != nil {
   log.Println("send header err")
}


在服务端中的接收该信息:

//接收从服务端发送过来的metadata信息
var header metadata.MD
_, err = client.AddOrder(mdCtx, testOrder, grpc.Header(&header))
log.Printf("metadata from server : %+v\n", header)


负载均衡

负载均衡策略一般是服务端的负载均衡和客户端的负载均衡,针对服务端代理的负载均衡,一般可采用 Nginx 或者 Envoy 来实现,让他们来提供不同的负载均衡算法,示意图如下:

客户端的负载均衡,指的是在请求发送的时候,在客户端进行服务的选择,从而实现负载均衡。




接下来看一个客户端负载均衡的代码示例:

假设 gRPC 服务端有一个服务,运行在了两个端口上,分别为 50051 和 50052,下面是服务端的代码:

type Server struct {
   addr string
}
func (s *Server) SayHello(ctx context.Context, req *wrappers.StringValue) (resp *wrappers.StringValue, err error) {
   resp = &wrappers.StringValue{}
   log.Println("the server port is ", s.addr)
   return
}
func startServer(addr string) {
   listener, err := net.Listen("tcp", addr)
   if err != nil {
      log.Println("tcp listen err.", err)
      return
   }
   s := grpc.NewServer()
   load_balance_demo.RegisterEchoServiceServer(s, &Server{addr})
   log.Printf("serving on %s\n", addr)
   if err := s.Serve(listener); err == nil {
      log.Fatalf("failed to serve: %v", err)
   }
}
func main() {
   var wg sync.WaitGroup
   for _, addr := range addrs {
      wg.Add(1)
      go func(val string) {
         defer wg.Done()
         startServer(val)
      }(addr)
   }
   wg.Wait()
}


在客户端呢,需要自定义负载均衡策略,我们选择最简单的 round_robin 算法。

下面是客户端的代码:

var addrs = []string{"localhost:50051", "localhost:50052"}
const (
   exampleScheme      = "example"
   exampleServiceName = "lb.example.com"
)
func main() {
   conn, _ := grpc.Dial(
      fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName),
      grpc.WithBalancerName(roundrobin.Name),
      grpc.WithInsecure(),
   )
   defer conn.Close()
   makeRPCs(conn, 10)
}
func makeRPCs(cc *grpc.ClientConn, n int) {
   client := load_balance_demo.NewEchoServiceClient(cc)
   for i := 0; i < n; i++ {
      callUnaryEcho(client, "test for load balance")
   }
}
func callUnaryEcho(c load_balance_demo.EchoServiceClient, message string) {
   ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   defer cancel()
   r, err := c.SayHello(ctx, &wrappers.StringValue{Value: message})
   if err != nil {
      log.Fatalf("could not greet: %v", err)
   }
   fmt.Println(r.Value)
}


由于服务端分别在两个端口上,因此这里我们可以使用命名解析器,将一个测试的域名指向 gRPC 的两个服务,命名解析器的代码如下:

type exampleResolverBuilder struct {}
type exampleResolver struct {
   target resolver.Target
   cc resolver.ClientConn
   addrsStore map[string][]string
}
func (*exampleResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
   r := &exampleResolver{
      target: target,
      cc: cc,
      addrsStore: map[string][]string{
         exampleServiceName: addrs,
      },
   }
   r.start()
   return r, nil
}
func (*exampleResolverBuilder) Scheme() string {return exampleScheme}
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 (*exampleResolver) ResolveNow(o resolver.ResolveNowOptions){}
func (*exampleResolver) Close() {}
func init() {
   resolver.Register(&exampleResolverBuilder{})
}


然后运行客户端和服务端的代码,会发现调用了 10 次服务端的方法,轮询调用每个服务,这正是 round_robin 的负载均衡逻辑。

如果将代码中的负载均衡逻辑改为 pick_first,那么则会一直调用第一个服务。

conn, _ := grpc.Dial(
   fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName),
   grpc.WithBalancerName(grpc.PickFirstBalancerName),
   grpc.WithInsecure(),

你可以在服务端方法中打上日志,来验证是否运行正确。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
7月前
|
负载均衡 安全 前端开发
百度搜索:蓝易云【Nginx与Tomcat负载均衡-动静分离教程】
这些是将Nginx与Tomcat结合使用实现负载均衡和动静分离的基本步骤。根据您的需求和具体环境,可能还需要进行其他配置和调整。请确保在进行任何与网络连接和安全相关的操作之前,详细了解您的网络环境和安全需求,并采取适当的安全措施。
76 1
|
7月前
|
负载均衡 应用服务中间件 nginx
百度搜索:蓝易云【Nginx和tomcat实现负载均衡教程】
至此,你已经成功地使用Nginx和Tomcat实现了负载均衡。Nginx将根据配置的负载均衡策略将客户端请求分发到多个Tomcat服务器上,以提高系统的性能和可用性。请注意,在实际生产环境中,还需要进行其他配置和优化,如健康检查、会话保持等,以满足具体的需求。
65 0
|
负载均衡 Dubbo 应用服务中间件
微服务技术系列教程(31) - Dubbo-原理及负载均衡分析
微服务技术系列教程(31) - Dubbo-原理及负载均衡分析
101 0
|
负载均衡 算法 应用服务中间件
Nginx系列教程(08) - Upstream Server 负载均衡
Nginx系列教程(08) - Upstream Server 负载均衡
660 0
|
负载均衡 应用服务中间件 Linux
Nginx系列教程(14) - LVS+KeepAlived+Nginx实现高性能负载均衡集群
Nginx系列教程(14) - LVS+KeepAlived+Nginx实现高性能负载均衡集群
1316 0
|
4月前
|
负载均衡 算法 微服务
基于gRPC的注册发现与负载均衡的原理和实战
基于gRPC的注册发现与负载均衡的原理和实战
|
3月前
|
运维 负载均衡 监控
slb学习教程
【9月更文挑战第1天】
62 0
|
5月前
|
消息中间件 存储 负载均衡
消息队列 MQ使用问题之如何在grpc客户端中设置负载均衡器
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
负载均衡 算法 Java
分布式系列教程(10) -分布式协调工具Zookeeper(负载均衡原理实现)
分布式系列教程(10) -分布式协调工具Zookeeper(负载均衡原理实现)
130 0
|
负载均衡 Dubbo 应用服务中间件
Nginx系列教程(11) - HTTP动态负载均衡(一)
Nginx系列教程(11) - HTTP动态负载均衡(一)
218 0