gRPC 简介实践

前言

现代的软件服务大多数是分布式应用程序,通过暴露自己的 API 对内或对外提供了一系列的功能点。服务与服务之间有时是跨语言、跨平台通信的。

为了解决这些复杂场景,市面上也涌现了有很多解决方案。比如构建 RESTful 服务,将服务能力转化为资源集合;也有面向函数调用的客户端-服务器模式:远程过程调用(Remote Procedure Calls)。今天要介绍的 gRPC 就是后者的演变,一个非常受欢迎分布式进程间通信技术。

认识 gRPC

gRPC 是 Google 在 2015 年推出的 RPC 框架。在认识 gRPC 之前,我们先来了解下 RPC 的相关知识。RPC 主要运用于分布式程序中,它构建了客户端-服务器模型,类似于请求-响应通信方式,只不过这种请求被我们抽象为了函数方法 + 入参信息,底层的网络通信则被屏蔽了起来,到最后就像本地方法调用一样。

Protocol Buffers

上面提到了函数的入参信息,有入参就有对象类型,而端到端的对象类型就势必涉及到
网络通信过程中的字节序列化和反序列化过程。在 gRPC 中,采用了 Protobuf(Protocol Buffers)作为序列化和反序列化协议。它是一种轻便高效的结构化数据存储格式,基于二进制编码构建,能够减少 CPU 的复杂解析,保障了 RPC 调用的高性能。

另外 Protobuf 支持多种编程语言,我们只需要对其进行接口定义描述,便可以根据描述文件自动生成客户端和服务端需要使用到的结构体和方法函数,就像是代码自动生成一样,大大提高了我们的编程效率。

HTTP/2

gRPC 是基于 HTTP/2 设计的,HTTP/2 也是 2015 年发布的,它是下一代的 HTTP 协议,具备很多高级功能,如:

  • 基于二进制格式传输,传输速度更快,更紧凑,不易出错。
  • 多路复用,单个连接可发送多个请求。
  • 对报头压缩,能降低传输开销。
  • 允许服务器主动推送。

正是这些 HTTP/2 的特性,使得 gRPC 能够使用较少的资源,获得较快的响应,在移动端设备上更加省电省流量。

gRPC 的使用

接口定义

当我们开发一个 gRPC 应用程序时,要做的第一件事情就是定义一个接口描述文件。在这个文件里,我们会将函数、参数信息都描述出来,就像下面这个 ProductInfo.proto 文件一样:

// ProductInfo.proto
syntax = "proto3";
package ecommerce;

// 服务里的接口列表
service ProductInfo {
    rpc addProduct(Product) returns (ProductID);
    rpc getProduct(ProductID) returns (Product);
}

// 参数信息
message Product {
    string id = 1;
    string name = 2;
    string description = 3;
}

message ProductID {
    string value = 1;
}

gRPC 服务端

当我们拿到定义好的接口描述文件 ProductInfo.proto 后,就可以使用 Protobuf 编译器:protoc 来生成我们的服务端代码了。假设我们的服务端采用的是 Go 语言,则在经过一系列插件的安装后,我们就可以使用下面的命令来编译生成代码了:

protoc --proto_path=IMPORT_PATH  --go_out=OUT_DIR  --go_opt=paths=source_relative path/to/file.proto

protoc 支持多种语言,具体大伙可以按照官方提供的来生成代码,总之,我们将接口定义文件,转化为了我们需要的服务端代码,我们只需要引用并实现接口函数即可为客户端提供对应的服务了:

import (
  ...
  "context"
  pb "github.com/grpc-up-and-running/samples/ch02/productinfo/go/proto"
  "google.golang.org/grpc"
  ...
)

// ProductInfo 接口的实现

// Add product remote method
func (s *server) AddProduct(ctx context.Context, in *pb.Product) (
      *pb.ProductID, error) {
   // 具体的业务逻辑
}

// Get product remote method
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID) (
     *pb.Product, error) {
   // 具体的业务逻辑
}

当然,定义完后还得注册下服务:

func main() {
  lis, _ := net.Listen("tcp", port)
  s := grpc.NewServer()
  pb.RegisterProductInfoServer(s, &server{})
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

gRPC 客户端

与服务端类似,我们将根据 ProductInfo.proto 文件生成客户端的代码,即所谓的存根(Stub)。假设我们的客户端使用的是 Java 语言,则我们可以像下面一样使用 Stub 了:

// 根据服务地址创建通道
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
   .usePlaintext(true)
   .build();

// 根据通道创建存根
ProductInfoGrpc.ProductInfoBlockingStub stub =
       ProductInfoGrpc.newBlockingStub(channel);

// 调用存根里的方法
StringValue productID = stub.addProduct(
       Product.newBuilder()
       .setName("Apple iPhone 11")
       .setDescription("Meet Apple iPhone 11." +
            "All-new dual-camera system with " +
            "Ultra Wide and Night mode.")
       .build());
       

整体流程

可以看到,其实接口定义文件 ProductInfo.proto 就是一个粘合剂,它将服务端的逻辑抽象成了语言无关的描述文件,让服务端依次去实现具体逻辑。然后通过它生成的客户端存根(Stub)则屏蔽了底层的通信流程,只需要暴露让上层可以调用的函数即可,就像本地函数调用一样。

gRPC 流程

其他特性

Metadata(元数据)

gRPC 没有使用 HTTP 所谓的 header 字段,而是使用 Metadata 来构建相关的头部信息。Metadata 是在一次 RPC 调用过程中关于这次调用的信息,以 key-value 的形式存在。

其中 key 是 string 类型, value 也是一组 string。 Metadata 对于 gRPC 本身来说透明,它使得 client 和 server 能为对方提供本次调用的信息。就像一次 http 请求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 请求, Metadata 的生命周期则是一次 RPC 调用。

Streaming(流)

得意于 HTTP/2 的多路复用能力,使得 gRPC 的客户端和服务端能够进行流式传输,例如我们可以边传输,边处理数据。 gRPC 的流式传输主要分为了下面几种:

  • 服务端流式 RPC:客户端发送单个请求,服务器可以发回多个响应。
  • 客户端流式 RPC:客户端发送多个请求,而服务器只发回一个响应。
  • 双向流式 RPC: 客户端和服务器同时相互发送消息而不等待响应。

Interceptors(拦截器)

gRPC 支持在请求/响应中使用拦截功能,进行消息的拦截并修改它们,这跟平常我们提到的 HTTP 中间件非常的相似。基于拦截器,我们可以实现类似身份认证、链式调用、重试等功能,这些对应微服务是非常的契合的。

负载均衡

gRPC 官方提供了关于负载均衡的方案:Load Balancing in gRPC,有兴趣的同学可以自己研究下。

gRPC 优点

正是前面的 Protobuf 和 HTTP/2 的底层支持,使得 gRPC 在一推出后就受到了许多人的追捧,它主要有以下几个特点:

  • 性能:比 REST + JSON 快 10 倍的性能,消息负载小而紧凑,并且通过压缩加快了传输速度。
  • 代码生成:通过语言无关的接口定义文件快速的生成各种语言的客户端、服务端代码,提高了开发效率。
  • 可拓展性好:提高了一体化的 RPC 解决方案,有许多内置的解决方案,例如数据交换、加密、身份验证、超时取消、拦截器、负载均衡等。

gRPC 的缺点

与其他技术一样,gRPC 有优点也有缺点,下面就是我们在开发应用程序时需要注意的点:

  • 有限的浏览器支持:由于 gRPC 大量使用 HTTP/2,因此无法之间在 Web 浏览器调用 gRPC 服务,gRPC 比较适用于手机 APP Client。
  • 不友好格式:Protobuf 将 gRPC 消息压缩成非可读格式,需要反序列化才拿到消息格式,不好调试。

总结

现代软件应用程序已经很少孤立存在了,更多是通过网络通信进行服务沟通。gRPC 是一种可拓展、松耦合且类型安全的解决方案.

与传统的基于 REST/HTTP 的通信相比,它能进行更有效的进程间通信,特别是现在流行的微服务架构里,在很多框架里都能看到它的身影存在。所以,是时候开始试试它的魅力所在了!

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 基础:GRPC的产生动机和设计原则grpc| python 实战 grpcPython版gRPC入门实验 - 知乎...
    王胜广阅读 3,900评论 0 2
  • 1.简介 1.1 问题 目前程序开发中,一个程序基本上是以各个服务组成,例如一个简单的系统,用户发起rest请求,...
    秃头猿猿阅读 2,670评论 0 3
  • RPC(Remote Procedure Call) RPC(Remote Procedure Call)— 远程...
    wavesnow阅读 34,342评论 0 4
  • 相信大家都听过RPC、HTTP、Socket等协议,他们均可用于业务中来进行数据通信,又根据各自协议的特点,应用场...
    Reaburoa阅读 721评论 0 1
  • 我们将介绍 Go 语言中最流行的 RPC 框架:gRPC,并带你探索其相对应的技术栈。 首先我们需要对RPC有所了...
    代码小杂鱼阅读 610评论 0 0