37

gRPC学习以及实践

 3 years ago
source link: https://studygolang.com/articles/31381
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

相信大家都听过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结构图:

MFRBVz.png!mobile

gRPC

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

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

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

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

buuuqaA.png!mobile

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

客户端的运行结果:

I7JrQri.png!mobile

gw_client

服务端运行结果:

aQFbya.png!mobile

gw_server

  1. 发起HTTP请求

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

    BJJrEjY.png!mobile

    gw_http

gRPC服务端结果:

aEfENjn.png!mobile

gw-http-server

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

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

参考资料:

  1. gRPC官方网站
  2. Protobuf官方教程
  3. gRPC-gateway

有疑问加站长微信联系

iiUfA3j.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK