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

简介: 前面已经介绍了几种 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(),

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

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
4月前
|
负载均衡 应用服务中间件 nginx
百度搜索:蓝易云【Nginx和tomcat实现负载均衡教程】
至此,你已经成功地使用Nginx和Tomcat实现了负载均衡。Nginx将根据配置的负载均衡策略将客户端请求分发到多个Tomcat服务器上,以提高系统的性能和可用性。请注意,在实际生产环境中,还需要进行其他配置和优化,如健康检查、会话保持等,以满足具体的需求。
36 0
|
4月前
|
负载均衡 安全 前端开发
百度搜索:蓝易云【Nginx与Tomcat负载均衡-动静分离教程】
这些是将Nginx与Tomcat结合使用实现负载均衡和动静分离的基本步骤。根据您的需求和具体环境,可能还需要进行其他配置和调整。请确保在进行任何与网络连接和安全相关的操作之前,详细了解您的网络环境和安全需求,并采取适当的安全措施。
50 1
|
6月前
|
负载均衡 Dubbo 应用服务中间件
微服务技术系列教程(31) - Dubbo-原理及负载均衡分析
微服务技术系列教程(31) - Dubbo-原理及负载均衡分析
57 0
|
6月前
|
负载均衡 算法 应用服务中间件
Nginx系列教程(08) - Upstream Server 负载均衡
Nginx系列教程(08) - Upstream Server 负载均衡
87 0
|
6月前
|
负载均衡 应用服务中间件 Linux
Nginx系列教程(14) - LVS+KeepAlived+Nginx实现高性能负载均衡集群
Nginx系列教程(14) - LVS+KeepAlived+Nginx实现高性能负载均衡集群
222 0
|
6月前
|
负载均衡 算法 Java
分布式系列教程(10) -分布式协调工具Zookeeper(负载均衡原理实现)
分布式系列教程(10) -分布式协调工具Zookeeper(负载均衡原理实现)
50 0
|
6月前
|
负载均衡 Dubbo 应用服务中间件
Nginx系列教程(11) - HTTP动态负载均衡(一)
Nginx系列教程(11) - HTTP动态负载均衡(一)
70 0
|
5月前
|
负载均衡 前端开发 应用服务中间件
百度搜索:蓝易云【Nginx使用之反向代理、负载均衡、动静分离教程。】
通过这样的配置,Nginx将根据请求的URL路径选择是将请求转发到后端服务器还是直接返回静态资源文件。
140 0
|
5月前
|
负载均衡 应用服务中间件 nginx
百度搜索:蓝易云【Docker安装Nginx,并实现负载均衡教程。】
通过按照上述步骤,你就可以使用Docker安装Nginx并实现负载均衡。请确保在创建Nginx配置文件时定义了正确的负载均衡策略,并根据需要进行适当的配置和调整。
125 0
|
6月前
|
负载均衡 Java 应用服务中间件
微服务技术系列教程(21) - SpringCloud- 负载均衡器Ribbon
微服务技术系列教程(21) - SpringCloud- 负载均衡器Ribbon
50 0