五.gRPC

什么是gRPC

gRPC是google开发的一个高性能、开源和通用的RPC框架,面向服务端和移动端,基于HTTP/2协议设计。

gRPC目前提供C、Java和Go语言版本,分别是:grpc、grpc-java和grpc-go。其中C版本支持C,C++,Node.js,Python,Ruby,Objective-C,PHP和C#。

在gRPC里,客户端应用可以像调用本地对象一样直接调用另外一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多RPC系统类似,gRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个gRPC服务器来处理客户端调用。

grpc

gRPC客户端和服务端可以在多种环境中运行和交互——从google内部的服务器到你的笔记本,并且可以用任何gRPC支持的语言来编写。所以,你很容易地用Java创建一个gRPC服务端,用Go、Python、Ruby来创建客户端。此外,Google最新API将有gRPC版本的接口,使你很容易地将Google的功能集成到你的应用里。

gRPC特点

  1. 语言中立,支持多种语言;
  2. 基于IDL文件定义服务,通过proto3工具生成指定语言的数据结构、服务端接口以及客户端Stub;
  3. 通信协议基于标准的HTTP/2设计,支持双向流、消息头压缩、但TCP的多路复用、服务端推送等特性,这些特性使得gRPC在移动端设备上更加省电和节省网络流量;
  4. 编码格式序列化支持PB和JSON,PB是一种语言无关的高性能序列化框架,基于HTTP/2+PB,保障了RPC调用的高性能

gRPC安装

gRPC依赖于ProtoBuf,在Linux系统下,通过以下步骤就可以安装好gRPC和ProtoBuf。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1. sudo yum install build-essential autoconf libtool libgflags-dev libgtest-dev clang libc++-dev pkg-config unzip

2. git clone -b $(curl -L http://grpc.io/release) https://github.com/grpc/grpc

3. cd grpc

4. git submodule update --init

5. make

6. sudo make install

7. cd third_party/protobuf

8. sudo ./autogen.sh

9. sudo ./configure

10. make

11. sudo make install

gRPC服务定义

简单RPC

客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样

1
2
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}

服务器端流式RPC

客户端发送请求到服务器,拿到一个流去读返回的消息序列。客户端读取返回的流,直到里面没有任何消息。从下述例子中可以看出,通过在 响应 类型钱插入stream关键字,可以指定一个服务端的流方法。

1
2
3
4
5
// Obtains the Features available within the given Rectangle.  Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}

客户端流式RPC

  • 客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端写入消息,它等待服务器完成读取它的响应。通过在 请求 类型前指定stream关键字来指定一个客户端的流方法。
1
2
3
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}

双向流式RPC

双方使用读写流去发送一个消息序列,两个流独立操作,因此客户端和服务器可以以任何喜欢的顺序读写:比如,服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读写和写入消息,或者其他读写的组合。每个流中的消息顺序被预留。你可以通过在请求和响应前加stream关键字去制定方法的类型。

1
2
3
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}

代码实现

proto文件(demo.proto)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";

package demo;

message Request {
string user_name = 1;
}

message Response {
string message = 2;
}

message HelloServer {
rpc SayHello(Request) returns(Response) {}
}

编译

protoc --go_out=plugin=grpc:. *.proto

  • 与普通proto文件编译有所区别,需要对应语言的grpc插件的支持;
  • 对go来说,要考虑路径是否配置正确 PATH=$PATH:$GOPATH/bin

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"context"
"fmt"
"google.golang.org/grpc"
pd "grpc-demo/protos"
"net"
)

type server struct {}
// 实现protos文件里定义的rpc服务
func (this *server)SayHello(ctx context.Context, in *pd.Request) (out *pd.Response, err error){
fmt.Println("收到grpc客户端发送的请求,username:", in.UserName)
res := new(pd.Response)
res.Message = "hello " + in.UserName
return res, nil
}

func main () {
//创建网络
ln, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("?????", err)
}

// 创建grpc服务
srv := grpc.NewServer()
// 注册服务
pd.RegisterHelloServerServer(srv, &server{})

// 启动服务,开始监听,等待网络连接
err = srv.Serve(ln)
if err != nil {
fmt.Println("rpc server init failed, err: ", err)
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"context"
"fmt"
"google.golang.org/grpc"
pd "grpc-demo/protos"
)

func main() {
conn, err := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())
if err != nil {
fmt.Println("网络连接失败,err: ", err)
}
defer conn.Close()

var name string
fmt.Print("输入你的名字:")
fmt.Scan(&name)

clt := pd.NewHelloServerClient(conn)
res, err := clt.SayHello(context.Background(), &pd.Request{UserName: name})
if err != nil {
fmt.Println("grpc调用失败:", err)
}

fmt.Println("grpc服务器返回结果:", res.Message)
}

执行结果

1
2
3
➜  grpc go run server.go
收到grpc客户端发送的请求,username: rpc
收到grpc客户端发送的请求,username: grpc
1
2
3
4
5
6
➜  grpc go run client.go
输入你的名字:rpc
grpc服务器返回结果: hello rpc
➜ grpc go run client.go
输入你的名字:grpc
grpc服务器返回结果: hello grpc