一文搞定gRPC

概述

什么是gRPC

gRPC是一个高性能、开源的、通用的远程过程调用 (RPC,Remote Procedure Call) 框架,由Google主导开发。gRPC用于在服务端和客户端之间进行通信,支持多种流行的编程语言。gRPC主要使用Protocol Buffers作为其接口定义语言,来描述服务接口和消息类型。

要实现gRPC服务,需要完成以下步骤:
1)定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。
2)在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。
3)在客户端拥有一个像服务端一样的方法的存根,即Stub
### gRPC与HTTP相比的优缺点
优点:

  1. 性能:gRPC基于HTTP/2协议,因此可以利用其多路复用、二进制帧、头部压缩等优点,从而提高性能。
  2. 通信协议:gRPC使用Protocol Buffers(Protobuf),这是一种比JSON更紧凑、更快速的二进制格式,同时还可以生成API接口和数据结构代码,而HTTP通常使用JSON或XML作为数据格式,这在某些情况下可能会更加冗长且效率较低。
  3. :gRPC支持四种类型的流,包括单向流和双向流,而HTTP/1.1只支持请求/响应模式。
  4. 双向认证和负载均衡:gRPC支持TLS和双向认证,并且原生支持各种类型的负载均衡。
  5. 语言独立:gRPC支持多种语言,包括C、Java、Python、Go、Ruby、C#等,这使得在不同的系统和语言之间进行通信变得更加容易。

缺点:

  1. 浏览器兼容性:由于gRPC基于HTTP/2,而不是所有的浏览器都支持HTTP/2,因此它在浏览器兼容性上可能存在问题。
  2. 可读性差:gRPC使用二进制格式,对于调试和测试来说,可读性比HTTP+JSON差。

gRPC常用的场景

  1. 微服务:gRPC是构建微服务的理想选择,因为它可以轻松处理服务间的通信和数据传输。由于其性能优异,可以用来构建高性能的微服务架构。
  2. 低延迟、高并发的系统:gRPC的高性能特性使其非常适合需要低延迟和高并发的系统。
  3. 实时应用:gRPC的流特性使得它非常适合实时应用,比如在线游戏、聊天服务、物联网设备通信等。
  4. 多语言环境:由于gRPC支持多种编程语言,因此非常适合多语言环境的使用,可以方便地在不同语言编写的服务之间进行通信。

实现gRPC

安装gRPC

Go安装gRPC
  1. 安装gRPC

    go get -u google.golang.org/grpc

  2. 安装Protocol Buffers v3

    brew install protobuf

  3. 安装Go的protobuf插件

    go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc

Python安装gRPC
  1. 安装gRPC
    pip install grpcio
  2. 安装Protobuf
    pip install protobuf
  3. 安装gRPC的Protobuf插件
    pip install grpcio-tools

定义.proto文件

什么是Protocol Buffers

Protocol Buffers (Protobuf) 是 Google 开发的一种用于结构化数据序列化的协议,类似于 XML、JSON 和 YAML,但相比之下,它更小、更快、更简单。可以用于数据存储、通信协议、数据交换等领域。

proto文件介绍

下面通过一个例子说明编写一个.proto文件,主要格式如下:

syntax = "proto3";  // 版本声明,使用Protocol Buffers v3版本
package getWords;  // 声明包名,该文件的参数都在这个命名空间中的,避免与其他proto文件有相同参数名而发生冲突

option go_package="./getWords;getWords";  // 以(;)进行分隔,前面是生成文件的路径,后面是就是生成go代码时的package名

// 定义服务,带有接口getPersionalInformation
service Greeter {
  rpc getPersionalInformation (PersonRequest) returns (PersonReply) {}
}

// 定义请求消息
message PersonRequest {
	int32 id = 1;
}

// 定义响应消息
message PersonReply {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {  // 枚举类型
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }
  repeated PhoneNumber phones = 4;  // 重复字段
}

该文件的字段声明是:有一个PersonReply的消息类型,它有四个字段,其中第一个字段是name,类型为字符串,第二个字段为id,类型是int32,第三个字段是email,类型是字符串,第四个字段是phones,它是一个重复字段,且类型是PhoneNumber。PhoneNumber也是一个消息类型,它有两个字段,一个是字符串类型number,一个是PhoneType类型的type。PhoneType是一个枚举类型,它有三个值,MOBILE、HOME和WORK。如果用json表示一条消息的话,如下:

{
  "name": "John",
  "id": 1,
  "email": "xx@xx.com"
  "phones": [{"number": "123", "type": "MOBILE"}, {"number": "345", "type": "HOME"}]
}
proto字段类型

想要完成自己的.proto文件的定义,主要在于字段的声明,常用的字段类型如下:

  • 字符串:string
  • 整型:int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64
  • 浮点型:float, double
  • 布尔值:bool
  • 枚举类型:enum
  • 字节序列:bytes
  • 嵌套消息类型
  • 重复字段:repeated

值得注意的是,在 Protobuf 3 中所有字段都是可选的,字段是否有默认值取决于字段的类型。同时 Protobuf 3 移除了required和optional标签。

HelloWorld.proto

我们使用源代码中的HelloWorld示例

  1. 初始化项目:go mod init helloWorld
  2. 创建proto文件

./helloWorld
├── go.mod
└── helloworld
├── helloworld.proto

syntax = "proto3";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

使用Go实现gRPC服务端

  1. 生成grpc代码
    protoc --go_out=./ --go-grpc_out=./ ./helloworld/helloworld.proto
    ./helloWorld
    ├── go.mod
    └── helloworld
    ├── helloworld.pb.go
    ├── helloworld.proto
    └── helloworld_grpc.pb.go

  2. 编写服务端代码

    // Package main implements a server for Greeter service.
    package main
    
    import (
    	"context"
    	"flag"
    	"fmt"
    	"log"
    	"net"
    
    	"google.golang.org/grpc"
    	pb "helloWorld/helloworld"
    )
    
    var (
    	port = flag.Int("port", 50051, "The server port")
    )
    
    // server is used to implement helloworld.GreeterServer.
    type server struct {
    	pb.UnimplementedGreeterServer
    }
    
    // SayHello implements helloworld.GreeterServer
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    	log.Printf("Received: %v", in.GetName())
    	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
    }
    
    func main() {
    	flag.Parse()
    	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
    	if err != nil {
    		log.Fatalf("failed to listen: %v", err)
    	}
    	s := grpc.NewServer()
    	pb.RegisterGreeterServer(s, &server{})
    	log.Printf("server listening at %v", lis.Addr())
    	if err := s.Serve(lis); err != nil {
    		log.Fatalf("failed to serve: %v", err)
    	}
    }
    

    ./helloWorld
    ├── go.mod
    ├── go.sum
    ├── helloworld
    │ ├── helloworld.pb.go
    │ ├── helloworld.proto
    │ └── helloworld_grpc.pb.go
    └── server
    └── main.go

  3. 运行server
    go mod tidy
    go run server/main.go

使用Python实现gRPC客户端

  1. 生成grpc代码
    python -m grpc_tools.protoc -I. --python_out=./ --grpc_python_out=./ ./helloworld/helloworld.proto
    ./helloWorld
    └── helloworld
    ├── helloworld.proto
    ├── helloworld_pb2.py
    └── helloworld_pb2_grpc.py

  2. 编写客户端代码

    from __future__ import print_function
    
    import logging
    
    import grpc
    from helloworld import helloworld_pb2
    from helloworld import helloworld_pb2_grpc
    
    
    def run():
       # NOTE(gRPC Python Team): .close() is possible on a channel and should be
       # used in circumstances in which the with statement does not fit the needs
       # of the code.
       print("Will try to greet world ...")
       with grpc.insecure_channel("localhost:50051") as channel:
           stub = helloworld_pb2_grpc.GreeterStub(channel)
           response = stub.SayHello(helloworld_pb2.HelloRequest(name="you"))
       print("Greeter client received: " + response.message)
    
    
    if __name__ == "__main__":
       logging.basicConfig()
       run()
    

    ./helloWorld
    ├── client
    │ └── client.py
    └── helloworld
    ├── init.py
    ├── helloworld.proto
    ├── helloworld_pb2.py
    └── helloworld_pb2_grpc.py

  3. 运行client
    python client/client.py
    获得输出:
    Will try to greet world …
    Greeter client received: Hello you

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海夜风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值