go如何使用GRPC | 青训营

114 阅读5分钟

GRPC是什么

gRPC = google+ Remote Procedure Call, google出品的远程过程调用(像在本地一样调用远程方法)

屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节

RPC分类

  • 支持多语言的 RPC 框架,比较成熟的有 Google 的 gRPC、Apache(Facebook)的 Thrift;
  • 只支持特定语言的 RPC 框架,例如新浪微博的 Motan;
  • 支持服务治理等服务化特性的分布式服务框架,其底层内核仍然是 RPC 框架, 例如阿里的 Dubbo。

应用场

内部做模块化拆分之后,如果涉及到不同语言的接口调用,就可以使用gRPC.另外,安卓客户端和后端后台服务做交互,也可以直接使用gRPC

4.特点/优缺点(具体到gRPC)

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

优点:1.支持HTTP2 2.支持多语言 3.支持安卓移动端

缺点:服务治理相关能力缺失,另外负载均衡和服务发现等功能要业务扩展实现

GRPC实战

1.安装 protobuf

github.com/protocolbuf… 选择对应版本下载后解压到GOPATH路径下即可

2.在go.mod中引入grpc

 go get google.golang.org/grpc

3.安装GO协议编译器的插件

 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

4.编写proto文件

创建client和service目录,在两目录中创建proto目录,分别创建client.proto和server.proto文件 proto文件相当于一层约束,规定了在rpc调用时,有哪些api,api接口需要传递哪些参数,返回值类型,以及服务名称等信息

 syntax = "proto3";  
   
 // 生成go文件时的包名  
 option go_package = ".;service";  
   
 // 服务定义  
 service SayHello {  
     // 定义一个方法 方法的形参和返回值类型在下面定义  
     rpc SayHello(HelloRequest) returns (HelloResponse){}  
 }  
   
 // message 就像是go中struct,java中的class  
 message HelloRequest{  
     string requestName = 1; // 数字作为标识号 放在函数具体位置  
     int64 age = 2;          // //    repeated string ids = 3;// repeated为切片类型  
 }  
   
   
 message HelloResponse{  
     string responseMsg = 1;  
 }

5.初始化项目文件

创建client和service目录,在两目录中创建proto目录,之后在cmd中执行

 protoc --go_out=. .\server.proto
 protoc --go-grpc_out=. .\server.proto

命令执行成功后会创建hello.pb.go和hello_grpc.pb.go,可以写一个shell脚本进行建立。后面再加

文件目录为

编写代码

1.服务端

对grpc_pb.go文件中的UnimplementedSayHelloServer函数进行重写

 type server struct {  
    pb.UnimplementedSayHelloServer  //导入包 pb "grpc_demo/server/proto" 
 }  
 // 实现业务逻辑  
 func (s *server) SayHello(ctx context.Context,req *pb.HelloRequest) (*pb.HelloResponse, error) {  
    return &pb.HelloResponse{ResponseMsg: "hello, " + req.RequestName}, nil  
 }

启动服务端

  • 监听端口
  • 启动grpc服务
  • 在grpc服务器中注册服务,即上面的server
  • 启动grpc服务
 func main() {  
    // 监听端口  
    listen, _ := net.Listen("tcp", ":8081")  
    // 开启grpc服务  
    grpcServer := grpc.NewServer()  
    // 在grpc服务器中注册我们自己写的服务  
    pb.RegisterSayHelloServer(grpcServer, &server{})  
    // 启动服务  
    err := grpcServer.Serve(listen)  
    if err != nil {  
       panic(err)  
    }  
 }

2.客户端

服务端代码相对简单

  • 连接服务端
  • 创建请求服务的客户端
  • 执行rpc调用
  • 获取结果
 func main() {   
    // 连接到server 此处不使用安全连接  
    conn, err := grpc.Dial("127.0.0.1:8081", grpc.WithTransportCredentials(insecure.NewCredentials()))  
   
    if err != nil {  
       log.Fatal(err)  
    }  
    defer conn.Close()  
   
    // 建立连接  
    client := pb.NewSayHelloClient(conn)  
    // 执行rpc调用,从server端获取执行结果  
    helloResponse, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "sunzyclient", Age: 10})  
    if err != nil {  
       log.Fatal(err)  
    }  
   
    fmt.Println(helloResponse.GetResponseMsg())  
 }

通过上面的步骤,即可完成最简单的grpc调用

3.增加token认证

server中main.go

 package main  
 import (  
    "context"  
    "errors"   
    "fmt"   
    "google.golang.org/grpc"       "google.golang.org/grpc/credentials/insecure"   "google.golang.org/grpc/metadata"   
    pb "grpc_demo/server/proto"  
    "net"
    )  
   
 type server struct {  
    pb.UnimplementedSayHelloServer  
 }  
 // 实现业务逻辑  
 func (s *server) SayHello(ctx context.Context,req *pb.HelloRequest) (*pb.HelloResponse, error) {  
    // 获取元数据  
    md, ok := metadata.FromIncomingContext(ctx)  
    if !ok {  
       return nil, errors.New("token does not exist")  
    }  
   
    var appId string  
    var appKey string  
    if v, ok := md["appid"]; ok { // 这里所有字母都为小写  
       appId = v[0]  
    }  
    if v, ok := md["appkey"]; ok {  
       appKey = v[0]  
    }  
    fmt.Printf("appId: %v, appKey: %v", appId, appKey)  
    if appId != "sunzy" || appKey != "123456"{  
       return nil, errors.New("token error")  
    }  
   
    return &pb.HelloResponse{ResponseMsg: "hello, " + req.RequestName}, nil  
 }  
   
 func main() {  
    // 监听端口  
    listen, _ := net.Listen("tcp", ":8081")  
    // 开启grpc服务  
    grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))  
    // 在grpc服务器中注册我们自己写的服务  
    pb.RegisterSayHelloServer(grpcServer, &server{})  
    // 启动服务  
    err := grpcServer.Serve(listen)  
    if err != nil {  
       panic(err)  
    }  
 }

client中的main.go

 package main  
   
 import (  
    "context"  
    "fmt"   
    "google.golang.org/grpc"    "google.golang.org/grpc/credentials/insecure"   
    pb "grpc_demo/server/proto"  
    "log"
    )  
   
   
 // 自定义token认证  
 type ClientAuthToken struct {  
   
 }  
   
 func (c *ClientAuthToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error){  
    return map[string]string{  
       "appId" : "sunzy",  
       "appKey" : "123456",  
    }, nil  
 }  
   
 func (c *ClientAuthToken) RequireTransportSecurity() bool{  
    return false  // 不使用ssl认证  
 }  
   
 func main() {  
    var ops []grpc.DialOption  
    ops = append(ops, grpc.WithTransportCredentials(insecure.NewCredentials()))  
    ops = append(ops, grpc.WithPerRPCCredentials(new(ClientAuthToken)))  
    // 连接到server 此处不使用安全连接  
    conn, err := grpc.Dial("127.0.0.1:8081", ops...)  
   
    if err != nil {  
       log.Fatal(err)  
    }  
    defer conn.Close()  
   
    // 建立连接  
    client := pb.NewSayHelloClient(conn)  
    // 执行rpc调用,从server端获取执行结果  
    helloResponse, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: "sunzyclient", Age: 10})  
    if err != nil {  
       log.Fatal(err)  
    }  
   
    fmt.Println(helloResponse.GetResponseMsg())  
 }

grpc拦截器

上面代码中token是在服务函数中验证的,显然不合理,grpc与gin一样也有拦截器,可以添加一个拦截器进行token的验证

创建grpc的拦截器

 func tokenHandler() grpc.UnaryServerInterceptor {
   return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
     log.Print("tokenHandler Interceptor...")
     // 获取元数据
     md, ok := metadata.FromIncomingContext(ctx)
     if !ok {
       return nil, errors.New("token does not exist")
     }
     var appId string
     var appKey string
     if v, ok := md["appid"]; ok { // 这里所有字母都为小写
       appId = v[0]
     }
     if v, ok := md["appkey"]; ok {
       appKey = v[0]
     }
     log.Printf("appId: %v, appKey: %v\n", appId, appKey)
     if appId != "sunzy" || appKey != "123456"{
       return nil, errors.New("token error")
     }
     log.Print("token is valid")
     resp, err = handler(ctx,req)
     return resp, err
   }
 }

在main函数中添加拦截器

 grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()),grpc.ChainUnaryInterceptor(tokenHandler())) //添加拦截器