热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

gRPC初探——概念介绍以及如何构建一个简单的gRPC服务

目录引言1.gRPC简介2.使用ProtocolBuffers进行服务定义2.1定义消息2.2定义服务

目录

  • 引言
  • 1. gRPC简介
  • 2. 使用Protocol Buffers进行服务定义
    • 2.1 定义消息
    • 2.2 定义服务接口
  • 3.构建简单的gRPC服务
    • 3.1 编写proto文件,定义消息和接口
    • 3.2 通过maven插件生成相应代码
    • 3.3 gRPC服务端创建
    • 3.5 gRPC客户端创建
    • 3.6 测试
  • 4. 总结
  • 5. 参考资料

引言

对于分布式系统而言,不同的服务分布在不同的节点上,一个服务要完成自己的功能经常需要调用其他服务的接口,比如典型的微服务架构。通常这种服务调用方式有两种,一种是发送HTTP请求的方式,另一种则是RPC的方式,RPC是Remote Procedure Call(远程过程调用)的简称,可以让我们像调用本地接口一样使用远程服务。相比HTTP调用,RPC的方式至少在以下几个方面有优势

  • 传输效率
    RPC可以自定义TCP报文,基于TCP协议进行通信,比如dubbo;同时也支持使用HTTP2协议进行通信,比如gRPC。这相比传统的HTTP1.1协议报文体积会更小,传输效率会更高。
  • 性能消耗
    RPC框架通常自带高效的序列化机制,序列化和反序列化耗时更低,序列化后的字节数通常也更小。
  • 负责均衡
    RPC框架通常自带负载均衡策略,而HTTP请求要做负载均衡需要外部应用如Nginx的支持。
  • 服务治理
    下游服务新增,重启,下线时能自动通知上游使用者,而HTTP的方式需要事先通知并修改相关配置。

正因为基于RPC方式的服务调用有着性能消耗低,传输效率高,更容易做负载均衡和服务治理的优点,所以分布式系统内部大多采用这种方式进行分布式服务调用。可供选择的RPC框架很多,比如Hession,Dubbo,Thrift这些很早就开源,平时项目中使用也很多。不过最近有一个叫gRPC的RPC框架很火,被使用在很多微服务相关的开源项目中,比如华为的Apache ServiceComb Saga。这篇博客作为我学习gRPC的入门笔记,只对它的核心概念和简单用法做些介绍

1. gRPC简介

gRPC是由Google开发并开源的RPC框架,它具有以下特点

  • 语言中立
    支持C,Java,Go等多种语言来构建RPC服务,这是gRPC被广泛的应用在微服务项目中的重要原因,因为不同的微服务可能用不同的语言构建。
  • 基于HTTP/2协议
    支持双向流,消息头压缩,单TCP的多路复用,服务端推送等,这些特性使得gRPC更加适用于移动场景下的客户端和服务端之间的通信。
  • 基于IDL定义服务
    编写.proto文件即可生成特定语言的数据结构、服务端接口和客户端Stub。
  • 支持Protocol Buffer序列化
    Protocol Buffer是由Google开发的一种数据序列化协议(类似于XML、JSON、Hession),平台无关,压缩和传输效率高,语法简单,表达能力强。

一个gRPC服务的大体架构可以用官网上的一幅图表示

gRPC服务端使用C++构建,客户端可以使用Ruby或者Java构建,客户端通过一个Stub存根(代理)对象发起RPC调用,请求和响应消息都使用Protocol Buffer进行序列化。

当我们在微服务中使用gRPC时,整个服务调用过程如下所示(图片来自网络)

通过gRPC,远程服务的调用对使用者更加简单和透明,底层的传输方式,序列化方式,通信细节等统统不需要关系,当然这些对其他RPC框架而言也适用。

2. 使用Protocol Buffers进行服务定义

一个直观的想法,在客户端调用服务端提供的远程接口前,双方必须进行一些约定,比如接口的方法签名,请求和响应的数据结构等,这个过程称为服务定义。服务定义需要特定的接口定义语言(IDL)来完成,gRPC中默认使用protocol buffers。它是google很早就开源的一款序列化框架,其定义了一种数据序列化协议,独立于语言和平台,提供了多种语言的实现:Java,C++,Go等,每一种实现都包含了相应语言的编译器和库文件。使用它进行服务定义需要编写.proto后缀的IDL文件,并通过其编译器生成特定语言的数据结构、服务端接口和客户端Stub代码。

2.1 定义消息

消息是表示RPC接口的请求参数和响应结果的数据结构。如下定义了一个请求消息和响应消息

//定义请求消息的结构
message SearchResponse {
  // repeated表示该字段可以重复任意次,等价于数组:Result[]
  repeated Result result = 1;
}

//定义响应消息的结构
message Result {
  //required表示该字段的值恰好为1个
  required string url = 1;
  //optional表示该字段的值为0或1个
  optional string title = 2;
  
  repeated string snippets = 3;
}

定义消息的关键字为message,相当于java中的class关键字,一个消息就相当于java中的一个类。消息内可以有多个字段,字段的类型可以分类如下

  • 基本数据类型
    int32表示java中的int,int64表示java中的long,string表示java中的string,具体的对应关系如下表所示

  • 复杂数据类型
    枚举,map等。
enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
map map_field = N;

和java中类中可以定义类一样,Protocol Buffers中消息内也可以定义消息,形成多层的嵌套结构

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      required int64 ival = 1;
      optional bool  booly = 2;
    }
  }

关于消息定义,有几点需要注意的地方
1.消息中的字段前可以有修饰符,修饰符主要有三种

  • required
    required int64 ival = 1;
    该字段的值恰好只有一个,没有或传入多个都将报错。

  • optional
    optional int32 result_per_page = 3 [default = 10];
    该字段的值有0个或1个,传入多个将报错。且以optional修饰的字段可以设置默认值,若没有设置,则编译器会根据类型自动设置一个默认值,比如string设置为空字符串,bool类型设置为false等。

  •  repeated
    repeated int32 samples = 4
    该字段相当于java中的数组,可以有0个或多个值。

2.消息中的字段有唯一编号,如下所示

这个唯一编号用来在消息的二进制格式中进行字段的区分,范围从1-229 - 1,其中19000-19999是保留编号不能使用。这些字段编号在使用过程中不能进行修改,否则会出现问题。

2.2 定义服务接口

标题中的接口可以类比java中的Interface,内部可以有多个方法。gRPC中使用service关键定义服务接口

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

该服务接口HelloService内部只有一个rpc方法SayHello,请求参数为HelloRequest,响应结果为HelloResponse。

grpc中可以定义4中类型的rpc方法

  • 1.简单rpc方法
rpc SayHello(HelloRequest) returns (HelloResponse){
}

客户端发送一个请求,从服务端获得一个响应,整个过程就像一个本地的方法调用。

  • 2.服务端流式响应的rpc方法
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}

客户端发送一个请求,并从服务端获得一个流(stream)。服务端可以往流中写入N个消息作为响应,并且每个消息可以单独发送,客户端可以从流中按顺序读取这些消息,如下图所示(图片来自网络)

  • 3.客户端流式请求的rpc方法
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}

客户端通过流发送一连串的多个请求,并等待从服务端返回的一个响应。

  • 4.双向流式rpc方法
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}

客户端通过流发送N个请求,服务端通过流发送N个响应,彼此相互独立,并且读写没有特定的次序要求,比如服务端可以收到所有请求后再返回响应,也可以每读取一个或K个请求会返回响应。
该特性可以充分利用HTTP/2.0的多路复用功能,实现了服务端和客户端的全双工通信,如下图所示(图片来自网络)

3.构建简单的gRPC服务

按照惯例,编写一个gRPC版本的hello world来讲解如何构建一个简单的gRPC服务——客户端发送一个请求,服务端返回一个响应。
比如
客户端:takumiCX
服务端:Hello takumiCX

3.1 编写proto文件,定义消息和接口

  • 创建proto文件

  • 定义消息和接口
//Protocal Buffers的版本有v2和v3之分,语法有较多变化,且相互不兼容
//这里使用的v3版本的
syntax = "proto3";

//编译后生成的消息类HelloRequest和HelloReply是否分别放在单独的class文件中
option java_multiple_files = true;
//生成代码的包路径
option java_package = "com.takumiCX.greeter";

//最外层的类名称
option java_outer_classname = "HelloWorldProto";

//包命名空间
package helloworld;

// 服务接口
service Greeter {
    // 一个简单的rpc方法
    rpc SayHello (HelloRequest) returns (HelloReply) {}

}

// 请求消息
message HelloRequest {
    string name = 1;
}

// 响应消息
message HelloReply {
    string message = 1;
}

3.2 通过maven插件生成相应代码

  • pom文件配置如下

    
        io.grpc
        grpc-netty-shaded
        1.16.1
    
    
        io.grpc
        grpc-protobuf
        1.16.1
    
    
        io.grpc
        grpc-stub
        1.16.1
    



    
        
            kr.motd.maven
            os-maven-plugin
            1.5.0.Final
        
    
    
        
            org.xolstice.maven.plugins
            protobuf-maven-plugin
            0.5.1
            
                com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}
                grpc-java
                io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier}
            
            
                
                    
                        compile
                        compile-custom
                    
                
            
        
    


在target目录下可以看到编译器通过编译proto文件为我们生成了对应的类,如下图所示

3.3 gRPC服务端创建

  • 第一步:首先要创建一个具体的服务接口实现类GreeterImpl,扩展gRPC为我们生成的服务抽象类GreeterGrpc.GreeterImplBase,重写服务方法
 //扩展gRPC自动生成的服务接口抽象,实现业务功能
    static class GreeterImpl extends GreeterGrpc.GreeterImplBase{

        @Override
        public void sayHello(HelloRequest request, StreamObserver responseObserver) {

            //构建响应消息,从请求消息中获取姓名,在前面拼接上"Hello "
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();

            //在流关闭或抛出异常前可以调用多次
            responseObserver.onNext(reply);

            //关闭流
            responseObserver.onCompleted();

        }
    }
  • 创建server对象,监听特定端口,注册具体的服务实现类并启动
 //服务要监听的端口
        int port=50051;

        //创建server对象,监听端口,注册服务并启动
        Server server = ServerBuilder.
                forPort(port)  //监听50051端口
                .addService(new GreeterImpl()) //注册服务
                .build()  //创建Server对象
                .start(); //启动

        log.info("Server started,listening on "+port);

        server.awaitTermination();

完整代码如下

/**
 * @author: takumiCX
 * @create: 2018-12-01
 **/
public class HelloWorldServer {

    private static final Logger log=Logger.getLogger(HelloWorldServer.class.getName());


    //扩展gRPC自动生成的服务接口,实现业务功能
    static class GreeterImpl extends GreeterGrpc.GreeterImplBase{

        @Override
        public void sayHello(HelloRequest request, StreamObserver responseObserver) {

            //构建响应消息,从请求消息中获取姓名,在前面拼接上"Hello "
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();

            //在流关闭或抛出异常前可以调用多次
            responseObserver.onNext(reply);

            //关闭流
            responseObserver.onCompleted();

        }
    }


    public static void main(String[] args) throws IOException, InterruptedException {

        //服务要监听的端口
        int port=50051;

        //创建服务对象,监听端口,注册服务并启动
        Server server = ServerBuilder.
                forPort(port)  //监听50051端口
                .addService(new GreeterImpl()) //注册服务
                .build()  //创建Server对象
                .start(); //启动

        log.info("Server started,listening on "+port);

        server.awaitTermination();

    }

}

gRPC的服务端创建过程如下所示(图片来自网络)

3.5 gRPC客户端创建

整个过程可以分为3步

  • 1.根据服务端的ip和端口号,创建ManagedChannel
  • 2.创建供客户端使用的stub对象,可以创建两种类型的stub,一种进行同步调用,一种进行异步调用,后者发起调用的业务线程不会同步阻塞。
  • 3.通过stub对象发起rpc调用,获取服务端响应。

完整代码如下:

/**
 * @author: takumiCX
 * @create: 2018-12-01
 **/
public class HelloWorldClient {

    private static final Logger log=Logger.getLogger(HelloWorldClient.class.getName());


    public static void main(String[] args) {


        String host="localhost";

        int port=50051;

        //1.创建ManagedChannel,绑定服务端ip地址和端口
        ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build();

        //2.获得同步调用的stub对象
        GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);

//        //获得异步调用的stub对象
//        GreeterGrpc.GreeterFutureStub futureStub = GreeterGrpc.newFutureStub(channel);

        Scanner scanner = new Scanner(System.in);
        while (true){
            //从控制台读取用户输入
            String name = scanner.nextLine().trim();
            //构建请求消息
            HelloRequest helloRequest = HelloRequest.newBuilder().setName(name).build();
            //通过stub代理对象进行服务调用,获取服务端响应
            HelloReply helloReply = stub.sayHello(helloRequest);
            final String message = helloReply.getMessage();
            log.warning("Greeting: "+message);
        }
    }
}

gRPC客户端的调用流程如下所示

3.6 测试

先启动gRPC服务端,然后启动gRPC客户单。客户端发送gRPC请求takumiCX,收到了来自服务端的响应Hello takumiCX

4. 总结

gRPC作为开源RPC框架的新势力,基于HTTP/2.0协议进行设计,使用高性能的Protocol Buffer进行消息的序列化,因而性能非常好,而且提供了完整的负载均衡和服务治理能力,加上其和语言无关、平台无关的特点,非常适合作为微服务内部服务间调用的选型。

5. 参考资料

《深入浅出gRPC》
https://developers.google.com/protocol-buffers/
https://grpc.io/docs/guides/concepts.html#service-definition


推荐阅读
  • 【转】强大的矩阵奇异值分解(SVD)及其应用
    在工程实践中,经常要对大矩阵进行计算,除了使用分布式处理方法以外,就是通过理论方法,对矩阵降维。一下文章,我在 ... [详细]
  • web页面报表js下载,web报表软件 ... [详细]
  • ABP框架是ASP.NET Boilerplate的简称,它不仅是一个开源且文档丰富的应用程序框架,还提供了一套基于领域驱动设计(DDD)的最佳实践架构模型。本文将详细介绍ABP框架的特点、项目结构及其在Web API优先架构中的应用。 ... [详细]
  • 深入理解Java多线程与并发机制
    本文探讨了Java多线程和并发机制的核心概念,包括多线程类的分类、执行器框架、并发容器及控制工具。通过详细解析这些组件,帮助开发者更好地理解和应用多线程技术。 ... [详细]
  • hdu4539郑厂长系列故事——排兵布阵http:acm.hdu.edu.cnshowproblem.php?pid4539问题描述:给你一个n行m列的0-1矩阵,0表示不 ... [详细]
  • 本文详细介绍了 Java 网站开发的相关资源和步骤,包括常用网站、开发环境和框架选择。 ... [详细]
  • Cookie学习小结
    Cookie学习小结 ... [详细]
  • 本文详细介绍了Java代码分层的基本概念和常见分层模式,特别是MVC模式。同时探讨了不同项目需求下的分层策略,帮助读者更好地理解和应用Java分层思想。 ... [详细]
  • 使用ArcGIS for Java和Flex浏览自定义ArcGIS Server 9.3地图
    本文介绍了如何在Flex应用程序中实现浏览自定义ArcGIS Server 9.3发布的地图。这是一个基本的入门示例,适用于初学者。 ... [详细]
  • V8不仅是一款著名的八缸发动机,广泛应用于道奇Charger、宾利Continental GT和BossHoss摩托车中。自2008年以来,作为Chromium项目的一部分,V8 JavaScript引擎在性能优化和技术创新方面取得了显著进展。该引擎通过先进的编译技术和高效的垃圾回收机制,显著提升了JavaScript的执行效率,为现代Web应用提供了强大的支持。持续的优化和创新使得V8在处理复杂计算和大规模数据时表现更加出色,成为众多开发者和企业的首选。 ... [详细]
  • 本文详细介绍了在CentOS 6.5 64位系统上使用阿里云ECS服务器搭建LAMP环境的具体步骤。首先,通过PuTTY工具实现远程连接至服务器。接着,检查当前系统的磁盘空间使用情况,确保有足够的空间进行后续操作,可使用 `df` 命令进行查看。此外,文章还涵盖了安装和配置Apache、MySQL和PHP的相关步骤,以及常见问题的解决方法,帮助用户顺利完成LAMP环境的搭建。 ... [详细]
  • 本文通过基准测试(Benchmark)对.NET Core环境下Thrift和HTTP客户端的微服务通信性能进行对比分析。基准测试是一种评估系统或组件性能的方法,通过运行一系列标准化的测试来衡量其表现。 ... [详细]
  • 快速掌握Tomcat 8.5.40的配置与应用技巧 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • Apache Hadoop HDFS QJournalProtocol 中 getJournalCTime 方法的应用与代码实例分析 ... [详细]
author-avatar
骑单车追梦的小男孩
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有