gRPC 初识

1,651 阅读3分钟

我正在参加「掘金·启航计划」

前言

最近在看有关微服务的内容,今天想总结一下有关gRPC的知识.下面的图很好的解释了gRPC的特点,服务端负责实现接口来处理客户端的请求,客户端直接调用需要的服务就行.最突出的特点就是gRPC的服务端和客户端可以支持不同语言实现,也就是说不再有语言的障碍会啥用啥就好了(像其它一些RPC框架比如Dubbo就只支持Java而不能跨语言通信)

landing-2.svg

1. gRPC与ProtoBuf

如果对gRPC不熟悉可以先去看看gRPC官网的介绍

默认情况下,gRPC是使用Protocol Buffers来传递数据的,比起xml或者json更加轻量(二进制格式而不是文本格式).在这里关于ProtoBuf和gRPC的安装配置我就不多赘述,可以参考其他博客.

常规的步骤也很简单,根据官网的教程首先使用protobuf定义好服务,然后使用protoc生成各自语言的proto文件进行调用实现不同语言的服务端与客户端.

2. gRPC的适用场景

  • 分布式: gRPC的低延迟和高吞吐非常适合分布式微服务
  • 多语言开发: gRPC支持多种语言,因此适合不同语言的工程师们混合开发
  • 轻量化消息: gRPC发送的消息是ProtoBuf二进制,比普通的二进制格式消息有天然优势

3. Demo

我平时常用的几种语言就是C++ Go Python,不过如果做服务用C++还是比较少的,所以今天尝试一下用GoPython分别写服务端和客户端进行通信.

3.1 Proto定义及转换

syntax="proto3";
​
option go_package="./;hello";
​
package hello;
​
service Greeter{
  rpc SayHello(HelloRequest) returns (HelloReply){}
}
​
message HelloRequest{
  string name = 1;
}
​
message HelloReply{
  string message = 1;
}

这里定义了Greeter这个服务以及服务中传递的消息,接下来需要转换为两种语言的代码

  • Go

protoc --proto_path=../protos --go_out=plugins=grpc:. hello.proto

protoc --proto_path=../protos --go_out=. --go-grpc_out=. hello.proto

  • Python

pip install grpcio grpcio-tools

python -m grpc_tools.protoc -I ./protos --python_out=. --pyi_out=. --grpc_python_out=. ./protos/hello.proto

3.2 服务端部分Go实现

package main
​
import (
    pb "PRC_learning/hello"
    "context"
    "flag"
    "fmt"
    "google.golang.org/grpc"
    "log"
    "net"
)
​
type server struct{ pb.UnimplementedGreeterServer }
​
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
​
var port = flag.Int("port", 50001, "The Server Port")
​
func main() {
    flag.Parse()
    lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
    if err != nil {
        log.Fatal("fail to listen :%v", err)
    }
​
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("fail to serve:%v", err)
    }
}
​

flag可以通过命令行解析设置服务的端口号,这里ip我设置的是本地.这里服务主要是实现hello_grpc.pb.go中的接口

image-20230219164118559

3.3 客户端部分Python实现

import grpc
import hello_pb2
import hello_pb2_grpc
​
​
def run():
    with grpc.insecure_channel(target='localhost:50001',
                               options=[
                                   ('grpc.lb_policy_name','pick_first'),
                                   ('grpc.enable_retries',0),
                                   ('grpc.keepalive_timeout_ms',1000)
                               ]) as channel:
        stub=hello_pb2_grpc.GreeterStub(channel)
        response=stub.SayHello(hello_pb2.HelloRequest(name='xxx'),timeout=10)
    print(f"Greeter client received:{response.message}")
​
if __name__ == '__main__':
    run()

image-20230219170222017

关注于run函数部分,除了为了通信设置相同的端口,更应该注意到SayHello这个方法.这个Demo是一元的消息(还有流式的消息等形式)所以对应unary_unary这个方法

image-20230219170959896

需要传入某个方法,请求的编码序列化以及回应的解序列化.上面那张图就实现了这三个参数

image-20230219172203185

最后返回响应,输出响应值中的信息就完成了一次通信

3.4 运行

首先运行服务端 go run server.go

image-20230219173302142

然后运行客户端 python client.py

image-20230219173315341