gRPC

引言

随着应用的日益复杂,单一服务已经不能很好地承载日益庞大的用户请求,当唯一一个服务由于各种原因不能正常运行(数据库满载,机房故障)导致系统整体挂掉显然是不能接受的。解决上述问题思路就是风险均摊,比如机房故障我们可以把服务部署在多个地区,然后把服务从大的服务拆分成若干个小的服务,这样当一个服务出现问题的时候也可以保证其他服务正常运行,至少不是所有服务全部炸掉,比如直播的时候弹幕服务不可用,但是用户依然可以正常观看直播,只是不能和主播实时互动而已。
以上粗略描述了微服务的基本思想和要解决的痛点。

gRPC?

要了解 grpc 首先我们要说说 rpcRemote procedure call,远程过程调用。
通常我们在本地调用一个方法的时候是用类似这样的方式:

1
2
3
4
5
6
7
func SayHello(name string) {
return "hello" + name
}

func main() {
res := SayHello("Ginta")
}

那如果我们想调用的函数是远程的一台机器,并且也想使用 SayHello(argument) 这种方式直接调用,就需要约定好怎么传参,返回参数是什么,以及怎么去连接。这种约定就是所谓的协议。rpc 主要包含通信协议和序列化协议:
通信协议:如http,tcp
序列化协议:如protobuf,json

我们常用的说的 restful 就是使用的 json 去实现的序列化,这种序列化方式的优势是直观可读,但是压缩率低,传输就会很慢。

protobuf是一款用C++开发的跨语言、跨平台、二进制编码的数据序列化协议,以超高的压缩率著称,极大地提高了传输效率。缺点就是需要专门的库去解析。

gRPC的官网主页只有一句简单的说明:”A high performance, open source universal RPC framework“。一个开源的高性能RPC框架。使用 HTTP2 为传输协议,HTTP1 中也可以多个请求利用一个连接,但是服务端返回的时候是根据pipeline中发送的顺序返回的,如果有一个阻塞了其他都不能返回,HTTP2 很好地解决了这一点。gRPC 使用 protobuf 来序列化数据。

protocol buffers

go的grpc实现

proto声明

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

package grpc;

option go_package = "grpc/pb;proto";


service HelloService {
rpc SayHello (SayHelloRequest) returns (SayHelloResponse);
}

message SayHelloRequest {
string Name = 1;
}

message SayHelloResponse {
string Message = 1;
}

server

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
package main

import (
"context"
"fmt"
"net"

"google.golang.org/grpc"
pb "grpc-study/pb"
)

type Server struct {
pb.UnimplementedHelloServiceServer
}

func (s *Server) SayHello(ctx context.Context, request *pb.SayHelloRequest) (*pb.SayHelloResponse, error) {
return &pb.SayHelloResponse{
Message: fmt.Sprintf("hello %s", request.Name),
}, nil
}

func main() {
// open a port
listen, _ := net.Listen("tcp", ":9090")

// create a grpc server
server := grpc.NewServer()

// register service
pb.RegisterHelloServiceServer(server, &Server{})

server.Serve(listen)
}

client

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
package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

pb "grpc-study/pb"
)

func main() {
conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return
}
defer conn.Close()

// start a client
client := pb.NewHelloServiceClient(conn)
response, err := client.SayHello(context.Background(), &pb.SayHelloRequest{Name: "Ginta"})
if err != nil {
return
}
fmt.Println(response.GetMessage())
}

grpcurl

grpcurl 工具可以查询 grpc 服务的 API, 用来调试 grpc 服务很方便
安装:go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
要使用 grpcurl 我们要在代码里先启动 reflection 反射服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
...
""google.golang.org/grpc/reflection""
)

func main() {
...
server := grpc.NewServer()

// register service
pb.RegisterHelloServiceServer(server, &Server{})

// register reflection
reflection.Register(server)
....
}

然后就可以使用 grpcurl 来调试了,先看看服务有哪些接口, grpcurl localhost:9000 list,报了一个异常 Failed to dial target host “localhost:9000”: tls: first record does not look like a TLS handshakegrpc 是用的 http2 协议, 虽然不强求但是一般传输也都是要加密的,这里提示我们少了 TLS 加密,我们可以先使用明文。grpcurl -plaintext localhost:9000 list
可以看到已经返回了我们服务的列表:

1
2
grpc.HelloService
grpc.reflection.v1alpha.ServerReflection

,第二个是我们开启的反射服务,可以用 grpcurl -plaintext localhost:9000 list grpc.HelloService 命令看看 grpc.HelloService 服务有哪些方法名,或者用grpcurl -plaintext localhost:9000 describe grpc.HelloService会返回更多的信息,包括方法的出入请求。

1
2
3
4
grpc.HelloService is a service:
service HelloService {
rpc SayHello ( .grpc.SayHelloRequest ) returns ( .grpc.SayHelloResponse );
}

我们来请求一下这个 SayHello 方法,grpcurl -plaintext -d '{"Name": "Ginta"}' localhost:9000 grpc.HelloService/SayHello:

1
2
3
{
"Message": "hello Ginta"
}

补充

grpc通信加密

服务发现

作者

Ginta

发布于

2023-01-26

更新于

2024-11-11

许可协议

评论