gRPC学习以及实践

相信大家都听过RPC、HTTP、Socket等协议,他们均可用于业务中来进行数据通信,又根据各自协议的特点,应用场景也比较多样、复杂,那大家是否听过或者了解gRPC呢?用来做什么呢?我们就来了解一下gRPC以及其用途。

介绍

用官方网站1一句话介绍介绍gRPC

A high-performance, open source universal RPC framework.
即:高性能、开源的通用型RPC框架

说起RPC,人们常会和HTTP做对比,两者在底层数据传输时本质基本一致,即全部基于TCP实现安全可靠的连接进行数据通信,但在应用层又有些不同。

RPC,即Remote Procedure Call(远程过程调用),主要在TCP协议之上进行工作;

HTTP,即HyperText Transfer Protocol(超文本传输协议),主要在HTTP协议之上进行工作。

从协议上来说,RPC更加高效一些。

gRPC结构图:
gRPC

gRPC基本基于定义服务的思想,指定远程调用的方法,包含方法的入参以及返回数据类型。服务端继承、实现接口并开启监听服务等待客户端请求;客户端保存一份副本,提供与服务端相同的方法。客户端、服务端的语言没有特别限制,只要支持gRPC协议基本可实现客户端、服务端的连接、数据通信。

目前gRPC支持的语言大致有:Golang、Python、Java、PHP、C&C++等。

gRPC的创建基于Protobuf,进行数据定义、服务接口定义等等,所以在深入了解gRPC前,最好对于protobuf有一定的了解。protobuf有proto2、proto3版本,现在大都基于proto3进行开发,所以大家了解proto32

基于gRPC实现restful接口,主要使用gRPC的一个插件,使得服务端通过一套代码即可对外提供HTTP服务、RPC服务,其架构如下图:
gRpc-gateway

gRPC使用

gRPC的使用通常有如下几步:

  1. 编写Protobuf,定义RPC的接口以及入参、出参,数据类型等
  2. 基于Protobuf编译成项目语言的文件,如go、java等
  3. 实现服务端功能模块,主要实现gRPC的接口
  4. 实现客户端功能

gRPC示例

Demo文件结构

.
├── example
│   ├── service.pb.go // 编译后的rpc文件
│   ├── service.pb.gw.go // 编译后的gateway文件
│   └── service.proto // protobuf文件
├── gw.go // gRPC的gateway服务端
├── gw_client.go // gRPC客户端
└── gw_server.go // gRPC服务端
1. 通过protobuf定义数据结构、类型
syntax = "proto3";

package example;

import "google/api/annotations.proto";

message StringMessage {
    string value = 1;
}

// 定义EchoServer
service EchoService {
    // 定义Echo接口,以及参数、返回数据
    rpc Echo(StringMessage) returns (StringMessage) {
        option (google.api.http) = {
          post: "/v1/example/echo" // 定义http服务的请求方法(post)、路由
          body: "*"
        };
    }
}
2. 编译protobuf文件为指定语言

demo主要为go语言,故而将protobuf编译为go语言版本,使用的命令为以下两条:

  • 编译为RPC数据结构、类型、服务

    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:. service.proto
    

    运行以上命令后,会生成:service.pb.go 文件,server端基于此编写server端服务功能,客户端基于此编写客户端调用逻辑。

  • 编译为Gateway的数据结构、服务转发等

    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:. service.proto
    

    运行以上命令,会生成:service.pb.gw.go 文件,server端基于此文件,启动HTTP服务,并将HTTP的请求转发到对应的RPC服务中。

  • 编译后的pb.go文件主要内容介绍

...
// 接口参数数据结构、类型
type StringMessage struct {
    Value                string   `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

...
// 获取gRPC客户端实例
func NewEchoServiceClient(cc *grpc.ClientConn) EchoServiceClient {
    return &echoServiceClient{cc}
}
...

// EchoServiceServer is the server API for EchoService service.
type EchoServiceServer interface {
    Echo(context.Context, *StringMessage) (*StringMessage, error)
}

...
// 注册服务端server功能
func RegisterEchoServiceServer(s *grpc.Server, srv EchoServiceServer) {
    s.RegisterService(&_EchoService_serviceDesc, srv)
}
3. 服务端&客户端功能实现

不管是server的功能,还是client的功能,全部基于 service.pb.go 进行开发实现,也就是说,生成一份pb.go文件,可以提供到服务端和客户端使用,从而能严格的保持客户端、服务端的数据结构类型、方法等的一致,客户端也无需太过关心接口的入参、出参数据以及类型,直接使用pb.go文件即可。

a. server代码示例
package main

import (
    pb "./example"
    "encoding/json"
    "fmt"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "log"
    "net"
)

const (
    port = ":9090"
)

// 实现pb.go中的EchoServiceServer接口
type server struct{}

// 接口的入参、出参结构已经确定,按需实现即可
func (s *server) Echo(ctx context.Context, in *pb.StringMessage) (*pb.StringMessage, error) {
    by, _ := json.Marshal(in)
    fmt.Println("Receive From Client:", string(by))
    return &pb.StringMessage{Value: "Hello " + in.Value}, nil
}
/**********/ 

func main() {
    // 启动server监听
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // 向gRPC服务中注册已经实现的服务
    s := grpc.NewServer()
    pb.RegisterEchoServiceServer(s, &server{})
    s.Serve(lis)
}

b. client代码示例
package main

import (
    pb "./example"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "log"
    "os"
)

const (
    address     = "localhost:9090"
    defaultName = "world"
)

func main() {
    // 连接到gRPC的服务端
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    
    // 基于连接获得gRPC的client实例
    c := pb.NewEchoServiceClient(conn)
    
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    // 在client实例上调用服务端方法,只需要遵循pb.go中的数据结构进行传参即可
    r, err := c.Echo(context.Background(), &pb.StringMessage{Value: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Value)
}

c. gateway代码示例
package main

import (
    gw "./example"
    "flag"
    "github.com/golang/glog"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "net/http"
)

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实例
    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithInsecure()}
    // 注册http服务的转发的endpoint
    err := gw.RegisterEchoServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
    if err != nil {
        return err
    }
    
    // 启动HTTP服务
    return http.ListenAndServe(":8080", mux)
}

func main() {
    flag.Parse()
    defer glog.Flush()
    
    if err := run(); err != nil {
        glog.Fatal(err)
    }
}

因为HTTP服务需要连接到gRPC的服务中才能对外提供服务,故而在启动项目前需要优先启动gRPC服务端,再启动gateway服务。

项目运行示例
  1. 启动gRPC服务端

    go run gw_server.go
    
  2. 启动gateway服务

    go run gw.go
    
  3. 发起RPC请求
    运行客户端

    go run gw_client.go
    

客户端的运行结果:


gw_client

服务端运行结果:


gw_server
  1. 发起HTTP请求

    使用postman工具发起http的post请求,结果:


    gw_http

gRPC服务端结果:


gw-http-server

到此整个gRPC的使用以及示例就基本介绍完毕了,在当今微服务比较流行的情况下,gRPC对于微服务中的使用还是有着比较重要的作用,各个服务之间不必关心对方服务的语言、数据结构、类型等,各个服务间基于proto文件进行数据转换、通信,以及语言特点选择http请求或者rpc请求。

gRPC也可以结合其他Go框架进行封装合一,如echo、gin框架等,充分利用Go语言框架的优势,对外提供更好的服务。

参考资料:
  1. gRPC官方网站
  2. Protobuf官方教程
  3. gRPC-gateway

你可能感兴趣的:(gRPC学习以及实践)