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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341