protobuffer、gRPC、restful gRPC的相互转化

文档

protobuf

 Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通信协议和数据存储等领域。前端

优势

  • 平台无关,语言无关,可扩展;
  • 提供了友好的动态库,使用简单;
  • 解析速度快,比对应的XML快约20-100倍;
  • 序列化数据很是简洁、紧凑,与XML相比,其序列化以后的数据量约为1/3到1/10。

安装

tax -xvf protobuf-all-xxx.tar.gz
cd protobuf-xxx
./configure
make
make check
sudo make install
  • 安装其余库
go get github.com/golang/protobuf/proto   // golang的protobuf库文件

// 插件
go get github.com/golang/protobuf/protoc-gen-go  // 用于根据protobuf生成golang代码,语法 protoc --go_out=. *.proto

语法

book/book.protogit

syntax="proto3";
package book;

// import "xxx/xx.proto"

// 出版社
message Publisher{
    required string name = 1
}  
// 书籍信息
message Book {
     required string name = 1;
    message Author {
        required string name = 1;
        required string address = 1;
    }
    required Author author = 2;

    enum BookType{
        SCIENCE = 1 ;
        LITERATURE = 2;
    }

    optional BookType type = 3;
    optional Publisher publisher = 4
}
  • syntax="proto3":指定protobuf的版本
  • package book:声明一个报名,通常与文件目录名相同
  • import "xxx/xx.proto":导入其余的包,这样你就可使用其余的包的数据结构
  • required、optional、repeated:表示该字段是否必须填充;required表示必须指定且只能指定一个;当optional表示可选,可指定也可不指定,但不可超过一个不指定值的时候会采用空值,如string类型的字段会用字符串表示;repeated表示能够重复,相似与编程语言中的list
  • message Author:在一个message体内定义一个message结构体
  • enum:是枚举类型结构体
  • 数字:字段的标识符,不可重复
  • 数据类型: int3二、int6四、uint3二、uint6四、sint3二、sint6四、double、float、 string、bool、bytes、enum、message等等

在golang使用

protobuf采用以上的book.proto文件github

并使用如下命令生成go文件golang

protoc --go_out=. *.proto

在代码中使用编程

package main

import (
    b "book"
    "github.com/golang/protobuf/proto"
)

func main(){
    ...
    // 将实例转为proto编码
    var b = &b.Book{Name:"xxx", Author:b.Author{Name:"yyy"}}
    protoBook, err := proto.Marshal(b)
    ...
    // 讲proto编码转化为实例
    var b2 b.Book
    err = proto.Unmarshal(protoBook, &b2)    
    ...
}

grpc

gRPC是由Google主导开发的RPC框架,使用HTTP/2协议并用ProtoBuf做为序列化工具。其客户端提供Objective-C、Java接口,服务器侧则有Java、Golang、C++等接口。使用grpc能够方便的调用其余进程的方法,调用须要传输的数据使用的是proto编码。这对于大型项目来讲,能够有效的提升数据的解编码效率和数据传输率。json

proto service定义

一个RPC service就是一个可以经过参数和返回值进行远程调用的method,咱们能够简单地将它理解成一个函数。由于gRPC是经过将数据编码成protocal buffer来实现传输的。所以,咱们经过protocal buffers interface definitioin language(IDL)来定义service method,同时将参数和返回值也定义成protocal buffer message类型。具体实现以下所示,包含下面代码的文件叫helloworld.proto:api

syntax = "proto3";
 
 package helloworld;
 
// The greeter 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;
}

接着,根据上述定义的service,咱们能够利用protocal buffer compiler ,即protoc生成相应的服务器端和客户端的GoLang代码。生成的代码中包含了客户端可以进行RPC的方法以及服务器端须要进行实现的接口。服务器

假设如今所在的目录是$GOPATH/src/helloworld/helloworld,咱们将经过以下命令生成gRPC对应的GoLang代码:restful

protoc --go_out=plugins=grpc:. helloworld.proto

此时,将在目录下生成helloworld.pb.go文件数据结构

server

server.go

package main
 
// server.go
 
import (
    "log"
    "net"
 
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "helloworld/helloworld"
)
 
const (
    port = ":50051"
)
 
type server struct {}

// 当接收到请求的时候回调用该方法
// 参数由grpc本身根据请求进行构造 
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
 
func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatal("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    s.Serve(lis)
}

其中pb是咱们刚才根据proto生成的go文件的包

client

package main
 
//client.go
 
import (
    "log"
    "os"
 
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "helloworld/helloworld"
)
 
const (
    address     = "localhost:50051"
    defaultName = "world"
)
 
func main() {
    // 创建一个grpc链接
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatal("did not connect: %v", err)
    }
    defer conn.Close()
    // 新建一个客户端,方法为:NewXXXClinent(conn),XXX为你在proto定义的服务的名字
    c := pb.NewGreeterClient(conn)
 
    name := defaultName
    if len(os.Args) >1 {
        name = os.Args[1]
    }
    // 调用远程,并获得返回
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatal("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}

restful转grpc

使用grpc的优势不少,二进制的数据能够加快传输速度,基于http2的多路复用能够减小服务之间的链接次数,和函数同样的调用方式也有效的提高了开发效率。不过使用grpc也会面临一个问题,咱们的微服务对外必定是要提供Restful接口的,若是内部调用使用grpc,在某些状况下要同时提供一个功能的两套API接口,这样就不只下降了开发效率,也增长了调试的复杂度。因而就想着有没有一个转换机制,让Restful和gprc能够相互转化。

grpc-gateway应运而生

安装

首先你得要根据本文以前的步骤安装proto和grpc,而后以下安装一些库

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go

用法

定义service的proto文件

syntax = "proto3";
 package example;

 import "google/api/annotations.proto";

 message StringMessage {
   string value = 1;
 }
 
 service YourService {
   rpc Echo(StringMessage) returns (StringMessage) {
     option (google.api.http) = {
       post: "/v1/example/echo"
       body: "*"
     };
   }
 }

option 表示处理哪些path的请求以及如何处理请求体(参数),见https://cloud.google.com/serv...

生成go文件

protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --go_out=plugins=grpc:. \
  path/to/your_service.proto

protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --grpc-gateway_out=logtostderr=true:. \
  path/to/your_service.proto

以上生成的两个文件,第一个是pb.go文件,给grpc server用的;第二个是pb.gw.go文件,给grpc-gateway用的,用于grpc和restful的相互转化

服务器

package main

import (
  "flag"
  "net/http"

  "github.com/golang/glog"
  "golang.org/x/net/context"
  "github.com/grpc-ecosystem/grpc-gateway/runtime"
  "google.golang.org/grpc"
    
  gw "path/to/your_service_package"
)

var (
  echoEndpoint = flag.String("echo_endpoint", "localhost:9090", "endpoint of YourService")
)

func run() error {
  ctx := context.Background()
  ctx, cancel := context.WithCancel(ctx)
  defer cancel()

  mux := runtime.NewServeMux()
  opts := []grpc.DialOption{grpc.WithInsecure()}
  err := gw.RegisterYourServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
  if err != nil {
    return err
  }

  return http.ListenAndServe(":8080", mux)
}

func main() {
  flag.Parse()
  defer glog.Flush()

  if err := run(); err != nil {
    glog.Fatal(err)
  }
}

测试

curl -X POST -k http://localhost:8080/v1/example/echo -d '{"name": " world"}

{"message":"Hello  world"}

流程以下:curl用post向gateway发送请求,gateway做为proxy将请求转化一下经过grpc转发给greeter_server,greeter_server经过grpc返回结果,gateway收到结果后,转化成json返回给前端。

相关文章
相关标签/搜索