zoukankan      html  css  js  c++  java
  • gRPC的简介与实例详解

    什么是gRPC

    gRPC是什么?可以用官网的一句话来概括:A high-performance, open-source universal RPC framework。

    所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。如下图所示就是一个典型的RPC结构图。

    gRPC vs Restful API

    gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议(严格地说,gRPC使用的http2.0,而restful api则不一定)。不过gRPC还是有些特有的优势,如下:

    gRPC可以通过protobuf来定义接口,从而可以有更加严格的接口约束条件。关于protobuf可以参见下期Protobuf简明教程,另外,通过protobuf可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高性能。

    gRPC可以方便地支持流式通信(理论上通过http2.0就可以使用streaming模式, 但是通常web服务的restful api似乎很少这么用,通常的流式数据应用如视频流,一般都会使用专门的协议如HLS,RTMP等,这些就不是我们通常web服务了,而是有专门的服务器应用。)

    使用场景

    • 需要对接口进行严格约束的情况,比如我们提供了一个公共的服务,很多人,甚至公司外部的人也可以访问这个服务,这时对于接口我们希望有更加严格的约束,我们不希望客户端给我们传递任意的数据,尤其是考虑到安全性的因素,我们通常需要对接口进行更加严格的约束。这时gRPC就可以通过protobuf来提供严格的接口约束。

    • 对于性能有更高的要求时。有时我们的服务需要传递大量的数据,而又希望不影响我们的性能,这个时候也可以考虑gRPC服务,因为通过protobuf我们可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,而且通过http2我们可以实现异步的请求,从而大大提高了通信效率。

    但是,通常我们不会去单独使用gRPC,而是将gRPC作为一个部件进行使用,这是因为在生产环境,我们面对大并发的情况下,需要使用分布式系统来去处理,而gRPC并没有提供分布式系统相关的一些必要组件。而且,真正的线上服务还需要提供包括负载均衡,限流熔断,监控报警,服务注册和发现等等必要的组件。不过,这就不属于本篇文章讨论的主题了,我们还是先继续看下如何使用gRPC。

    gRPC DEMO实例详解

    • 通过protobuf来定义接口和数据类型

    • 编写gRPC server端代码

    • 编写gRPC client端代码
      本文使用golang去实现demo,其中protobuf和grpc扩展的安装就跳过了。

    新建userrpc.proto

    syntax = "proto3";
    package user;
    option go_package = "./grpc/user";
    
    // The User service definition.
    service User {   
      // Get all Users with id - A server-to-client streaming RPC.
      rpc GetUsers(UserFilter) returns (stream UserRequest) {}
      // Create a new User - A simple RPC 
      rpc CreateUser (UserRequest) returns (UserResponse) {}
    }
    
    // Request message for creating a new user
    message UserRequest {
      int32 id = 1;  // Unique ID number for a User.
      string name = 2;
      string email = 3;
      string phone= 4;
    
      message Address {
        string province = 1;
        string city = 2;  
      }
      repeated Address addresses = 5;
    }
    
    message UserResponse {
      int32 id = 1;
      bool success = 2;
    }
    message UserFilter {
      int32 id = 1;
    }

    编译 .proto 文件

    protoc  --go_out=plugins=grpc:. userrpc.proto

    新建服务端server.go

    package main
    
    import (
        "log"
        "net"
    
        "golang.org/x/net/context"
        "google.golang.org/grpc"
        pb "userrpc/grpc/user"
    )
    
    const (
        port = ":50051"
    )
    
    // server is used to implement user.UserServer.
    type server struct {
        savedUsers []*pb.UserRequest
    }
    
    // CreateUser creates a new User
    func (s *server) CreateUser(ctx context.Context, in *pb.UserRequest) (*pb.UserResponse, error) {
    
        s.savedUsers = append(s.savedUsers, in)
        return &pb.UserResponse{Id: in.Id, Success: true}, nil
    }
    
    // GetUsers returns all users by given id
    func (s *server) GetUsers(filter *pb.UserFilter, stream pb.User_GetUsersServer) error {
        for _, user := range s.savedUsers {
            if filter.Id == 0 {
                continue
            }
            if err := stream.Send(user); err != nil {
                return err
            }
        }
        return nil
    }
    
    func main() {
        lis, err := net.Listen("tcp", port)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
    
        // Creates a new gRPC server
        s := grpc.NewServer()
        pb.RegisterUserServer(s, &server{})
        s.Serve(lis)
    }

    客户端client.go

    package main
    
    import (
        "io"
        "log"
    
        "golang.org/x/net/context"
        "google.golang.org/grpc"
        pb "userrpc/grpc/user"
    )
    
    const (
        address = "localhost:50051"
    )
    
    // createUser calls the RPC method CreateUser of UserServer
    func createUser(client pb.UserClient, user *pb.UserRequest) {
        resp, err := client.CreateUser(context.Background(), user)
        if err != nil {
            log.Fatalf("Could not create User: %v", err)
        }
        if resp.Success {
            log.Printf("A new User has been added with id: %d", resp.Id)
        }
    }
    
    // getUsers calls the RPC method GetUsers of UserServer
    func getUsers(client pb.UserClient, id *pb.UserFilter) {
        // calling the streaming API
        stream, err := client.GetUsers(context.Background(), id)
        if err != nil {
            log.Fatalf("Error on get users: %v", err)
        }
        for {
            user, err := stream.Recv()
            if err == io.EOF {
                break
            }
            if err != nil {
                log.Fatalf("%v.GetUsers(_) = _, %v", client, err)
            }
            log.Printf("User: %v", user)
        }
    }
    func main() {
        // Set up a connection to the gRPC server.
        conn, err := grpc.Dial(address, grpc.WithInsecure())
        if err != nil {
            log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        // Creates a new UserClient
        client := pb.NewUserClient(conn)
    
        user := &pb.UserRequest{
            Id:    1,
            Name:  "test",
            Email: "fasd@163.com",
            Phone: "132222222",
            Addresses: []*pb.UserRequest_Address{
                &pb.UserRequest_Address{
                    Province: "hebei",
                    City:     "shijiazhuang",
                },
            },
        }
    
        // Create a new user
        createUser(client, user)
        // Filter with an  id
        filter := &pb.UserFilter{Id: 1}
        getUsers(client, filter)
    }

    启动server.go

    go run server.go

    新打开一个窗口,启动client.go

    go run client.go

    结果为

    2019/07/04 17:01:16 A new User has been added with id: 1
    2019/07/04 17:01:16 User: id:1 name:"test" email:"fasd@163.com" phone:"132222222" addresses:<province:"hebei" city:"shijiazhuang" >

    小结

    Api实现起来比较繁琐,给开发带来难度。总的来说gRPC是一个不错的跨语言rpc解决方案,当然每个人都自己的看法或见解。针对不同的业务场景采用不同的解决方案,最终都是运行效率和开发效率的相互妥协的结果。

  • 相关阅读:
    TreeList Linq
    MasterDetail Linq
    C# 事务处理
    设计模式——代理模式(Proxy Pattern)
    设计模式——装饰模式(Decorator Pattern)
    C# 调用WCF服务
    加密解密
    Effective C#高效编程(02:常量)
    切换城市功能
    DataPager控件使用
  • 原文地址:https://www.cnblogs.com/eflypro/p/12468233.html
Copyright © 2011-2022 走看看