什么是 gRPC?

gRPC 的几种常见模式

在学习 gRPC 的时候,相信大家对于它的四种模式都有了解,我们来简单回顾一下:

  • 简单模式(Simple RPC):这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的 RPC 没有什么大的区别,所以不再详细介绍。

  • 服务端数据流模式(Server-side streaming RPC):这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。如果是使用我们容器云功能的同学应该会发现,我们的容器实时日志流就是使用了这个典型模式。

  • 客户端数据流模式(Client-side streaming RPC):与服务端数据流模式相反,这次是客户端源源不断地向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。

  • 双向数据流模式(Bidirectional streaming RPC):顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。

接下来我们通过一个小例子来看看 gRPC 具体的使用流程。

假设我们有一个聊天机器人,现需要增加一个对外提供服务的接口。具体需求为,接口传入参数是一个人名,返回一段内容是“Hello 人名”的音频。如果这个是让你在不使用 gRPC 的情况下,你会怎么做?大家可能会选择使用 restful api 来实现这个功能,传入人名,返回音频二进制数据。

那么如果使用 gRPC,我们需要怎么来设计呢?

第一步,需要定义一个接口文档,也就是 proto 文件。在定义内会定义一个 Service,接下来再在 Service 里定义一个 SayHello 的方法。下面定义传入参数,输入 name 返回 message,需要注意 message 是 bytes 类型,即返回的格式是二进制数据。对于 Golang 底层对应的是一个 bytes 数据,对于其他语言可能是字节流或二进制。

syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
bytes message = 1;

定义完成后 ,下一步就是使用 protoc 命令行工具生成代码。下图左侧是初始化的项目,你会发现,有一个单独的目录 protoc,存放了 hello.proto 这个文件,这个文件就是前面定义好的。

下图右侧是自动生成代码后的项目结构,生成了一个 pkg/helloworld 的包,里面有一个 hello.pb.go,打开这个文件,你会发现刚才定义的 proto 已经被翻译成了 Go 语言。具体 protoc 命令行工具如何使用,可以自行搜索下,这里不再过多展开。

定义好了 proto 文件,以及生成了相应的 package 代码,下一步我们就可以编写业务逻辑了。

Hello gRPC - 服务端业务代码

import (
"google.golang.org/grpc"
pb "grpc-hw/pkg/helloworld"
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
tempFile := "srv.wav"
err := exec.Command("flite", "-t", "Hello "+in.GetName(), "-o", tempFile).Run()
if err != nil {
return nil, fmt.Errorf("make audio failed: %v", err)
}
data, _ := ioutil.ReadFile(tempFile)
return &pb.HelloReply{Message: data}, nil
}
func main() {
lis, _ := net.Listen("tcp", port)
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
s.Serve(lis)
}

在服务端侧,需要实现 SayHello 的方法来满足 GreeterServer 接口的要求。SayHello 方法的传入参数,是在 proto 文件中定义的 HelloRequest,传出参数,是在 proto 文件中定义的 HelloReply,以及一个 error。

业务逻辑也比较简单,获取 HelloRequest 中 Name 字段,然后通过命令行行工具转换成对应的音频,将 bytes 数组存在在 HelloReply 中返回。

Hello gRPC - 客户端业务代码

func main() {
flag.StringVar(&address, "addr", address, "server address")
flag.StringVar(&name, "name", "world", "name")
flag.Parse()
// Set up a connection to the server.
conn, _ := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
tempFile := "cli.wav"
ioutil.WriteFile(tempFile, r.Message, 0666)
exec.Command("afplay", tempFile).Run()
}

我们再来看一下如何实现 Client。首先,建立一个 gRPC 的连接,并初始化 GreeterClient,然后直接调用下 GreeterClient 的 SayHello 的方法,将把返回结果另存为一个文件,通过播放器播放就可以了。

总体而言,整个使用过程很简单,并且非常容易上手,让开发可以更加关注在客户端、服务端业务的实现,不用操心传输层的事情。

gRPC 的使用总结

通过刚刚的小例子,我们来总结一下 gRPC 的使用:

  • 定义好接口文档

  • 工具生成服务端/客户端代码

  • 服务端补充业务代码

  • 客户端建立 gRPC 连接后,使用自动生成的代码调用函数

  • 编译、运行

以上 5 步就是 gRPC 的简单使用方法了。

gRPC 与 Protobuf

接下来我们来聊聊 gRPC 跟 Protobuf 之间的联系,当然在这之前我们需要先知道 Protobuf 是什么。

Protobuf

Protobuf 是一个语言无关、平台无关的可扩展的结构化数据序列化方案。大家可能会觉得它跟 JSON 好像没什么区别,功能上看起来是一样的,但像上文那样去定义 SayHello 的操作,JSON 是做不到的,JSON 只能定义一个请求体或者一个返回体,没法定义一个方法,但是 Protobuf 是可以的。

Protobuf 多用于协议通讯、数据存储和其他更多用途。它是一个比较灵活的、高效的、自动化的结构化数据序列机制,但是更小,更快并且更简单。一旦定义好数据如何构造, 就可以使用特殊生成的代码来轻易地读写结构化数据,无需关心用什么语言来实现。你甚至可以更新数据结构而不打破已部署的使用"旧有"格式编译的程序。

上图是 Protobuf 与 JSON 以及 JSON stream 三者之间的性能比较,可以明显的看到在解码的时候 Protobuf 比其他两项快了不只一星半点。

上图中,我们可以看到 Protobuf 还是有一些缺点的,比如浏览器的支持没有其他几个支持的好。但是,在数据安全方面,由于传输过程中采用的是加密压缩后的字节流,一般无法直接查看,安全性非常好。以及在处理速度方面,因为编解码效率很高使得整体吞吐量有了显著提升。还有一点,定义方法,这个是其他两种序列化协议所做不到的。

gRPC 跟 Protobuf 的联系

虽然每次 gRPC 与 Protobuf 都是同时出现的,但是其实两者之间并没有很深的联系。只是因为两者都是由 Google 开发的,和 gRPC 本身负载无关,在使用时也可以选择 JSON 等等,但是考虑到 Protobuf 有定义方法的优势,在微服务里还是很推荐使用的。

gRPC vs Restful API

上图是 gRPC 与 Restful API 的对比,平常我们可能更多使用 Restful API。但从图上可以看到,因为 gRPC 用的是 Protobuf,本身就比较小所以很快,不像 JSON 体积比较大、比较慢。另外,gRPC 是加载 HTTP/2 上面的,延迟比较低,也因为 HTTP/2 支持链接复用,这就可以让多个 stream 共用一个连接,从而进一步提升速度。对比 Restful 则使用的是 HTTP 1.1,延迟比较高,而且在一般情况下,每次请求都需要建一下新的连接。

gRPC 是双向的。什么是双向呢?比如我们平常做 Restful,都是从客户端到服务端,但是服务端没办法直接主动向客户端发送信息,gRPC 则可以。gRPC 也支持流,Restful只支持Request/Response 这样的机制。gRPC是面向 API 的,没有限制,也面向增删改查。gRPC 可以通过 Protobuf 直接生成代码,而 Restful 需要依赖第三方。

另外 gRPC 支持 RPC 可以调用服务器上的一些方法,而 Restful 是基于HTTP的语法,很多东西需要自己去定义。这方面相信大家都有感触,比如 REST 定义post/put/delete/get时,因为每个人都有自己的习惯,所以协作时需要沟通讨论进行指定。但是 gRPC 就不需要了,它支持定义函数式办法,不需要大家去考虑如何设计语法。

以上就是 gRPC 跟 Restful API 的对比。

引入 gRPC 需要考虑哪些问题?

那么当我们引入 gRPC 的时候需要考虑什么呢?以下几点肯定是不可避免的考虑项:

  • 是否可以满足当前需求

  • 性能如何

  • 连接异常断开后,是否需要客户端重试

  • TCP 连接是否可以复用

  • 业务层改动是否足够便利

  • 业务后期迭代是否会出现问题,如何避免

这个也是我们引入一项新的东西时,往往需要考虑到的问题。

回顾与总结

从选择 gRPC 到整个项目落地,以及现在上线后正常使用。整个过程中,我对于项目的思考可以包括了过去、现在和未来三个阶段。

对我而言,过去就是要去看我选择的这个东西,用的人多不多,完善程度怎么样了?而现在则是要结合项目,看看合不合适,能不能使用。当然不能思考到能使用就结束,我们还需要考虑这个项目在未来的 3-5 年的发展,你引入它后在这个时间内需不需要大的变动。这个是非常重要的,虽然我们现在常说敏捷开发,也经常会进行很多的调整,但是在类似 gRPC 这种底层基础来说,是固定的。

以上就是我今天的全部分享内容,讲的比较简单,希望能带给大家一些收获。

推荐阅读

秋天的第一份“干货” I Referer 防盗链,为什么少了个字母 R?

“网页内容无法访问”可能是跨域错误!

【实战分享】从选型到项目落地,漫谈 gRPC的更多相关文章

  1. BI实战派:医疗BI项目落地方案

    任何BI项目面临的两大难题是项目价值和基础数据,BI项目应该给企业带来管理优化.业绩增长.医院面临的两大难题,一:绩效管理(奖金分配):二:医患关系:在医院开始自负盈亏时日常基本运营管理显得非常重要. ...

  2. nRF24L01+组网方式及防撞(防冲突)机制的实战分享

    利用多个nRF24L01+模块组网通信的实现方式 这里讨论的组网方式,不包含使用6个通道实现的多对1通信方式,因其只限于6个发送端,局限性很大,可以附加其他技术实现更好的组网,暂时这里不讨论.这里分享 ...

  3. 微信小程序教学第二章:小程序中级实战教程之预备篇 - 项目结构设计 |基于最新版1.0开发者工具

    iKcamp官网:http://www.ikcamp.com 访问官网更快阅读全部免费分享课程:<iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享>. ...

  4. .NET Core中的验证组件FluentValidation的实战分享

    今天有人问我能不能出一篇FluentValidation的教程,刚好今天在实现我们的.NET Core实战项目之CMS的修改密码部分的功能中有用到FluentValidation,所以就以修改用户密码 ...

  5. Redisson实现分布式锁(3)—项目落地实现

    Redisson实现分布式锁(3)-项目落地实现 有关Redisson实现分布式锁前面写了两篇博客作为该项目落地的铺垫. 1.Redisson实现分布式锁(1)---原理 2.Redisson实现分布 ...

  6. (转)【重磅】无监督学习生成式对抗网络突破,OpenAI 5大项目落地

    [重磅]无监督学习生成式对抗网络突破,OpenAI 5大项目落地 [新智元导读]"生成对抗网络是切片面包发明以来最令人激动的事情!"LeCun前不久在Quroa答问时毫不加掩饰对生 ...

  7. 实战突击: Java Web项目整合开发(PDF)

    实战突击:  Java  Web项目整合开发(PDF)

  8. 分享我在 vue 项目中关于 api 请求的一些实现及项目框架

    本文主要简单分享以下四点 如何使用 axios 如何隔离配置 如何模拟数据 分享自己的项目框架 本文主要目的为以下三点 希望能够帮到一些人 希望能够得到一些建议 奉上一个使用Vue的模板框架 我只是把 ...

  9. 2019元月新SAP项目落地记

    2019元月新SAP项目落地记 ~ 追求绝对安全感,要求无缝衔接 ~ 元旦刚过,我就开始了新SAP项目的寻找之旅. 笔者追求绝对的安全感,以及新旧项目之间的无缝衔接.即不能让自己长时间的闲着无项目做, ...

随机推荐

  1. 《To C产品经理进阶》

    我所说的,都是错的. To C产品设计和To B产品设计对一个优秀的产品经理的洞察能力.架构能力有共通的要求. 实际产品设计过程中,To C产品往往是从商业思维思考,侧重用户研究,思考用户心智,由产品 ...

  2. 在nginx下导出数据库数据

    首先上干货 解决问题 set_time_limit(0); //设置脚本运行时间为不限制 因为php脚本默认时间为30秒 ini_set('memory_limit', -1); //取消脚本运行内存 ...

  3. webpack5文档解析(上)

    webpack5 声明:所有的文章demo都在我的仓库里 webpack5 起步 概念 webpack是用于编译JavaScript模块. 一个文件依赖另一个文件,包括静态资源(图片/css等),都会 ...

  4. 还不会ida*算法?看完这篇或许能理解点。

    IDA* 算法分析 IDA* 本质上就是带有估价函数和迭代加深优化的dfs与,A * 相似A *的本质便是带 有估价函数的bfs,估价函数是什么呢?估价函数顾名思义,就是估计由目前状态达 到目标状态的 ...

  5. 【图论】USACO07NOV Cow Relays G

    题目大意 洛谷链接 给定一张\(T\)条边的无向连通图,求从\(S\)到\(E\)经过\(N\)条边的最短路长度. 输入格式 第一行四个正整数\(N,T,S,E\),意义如题面所示. 接下来\(T\) ...

  6. 数据查询语句:DQL(Data Query Language)

    一.基础查询 1.语法:select 查询列表 from 表名; 2.特点:1.通过select查询完的结果,是一个虚拟的表格,不是真实存在   2.查询列表可以是:字段.表达式.常量.函数等   3 ...

  7. 第三十二章 Linux常规练习题(一)

    一.练习题一 1.超级用户(管理员用户)提示符是____,普通用户提示符是____.2.linux关机重启的命令有哪些 ?3.bash是什么?4.bash特性, 常见的bash特性有哪些?5.网卡的配 ...

  8. <!DOCTYPE>,<address>,<applet>的用法

    希望以下内容能让大家有所收获 HTML <!DOCTYPE> 标签 实例 <!DOCTYPE html> <html> <head> <title ...

  9. Python字典的初识、增删改查及嵌套

    为什么要有字典? 列表可以存储大量的数据,但数据间的关联型不强 列表的查询速度相对慢 dict:字典,容器型数据类型 数据类型的分类: 可变与不可变 可变(不可哈希)的数据类型: 列表list,字典d ...

  10. OpenCV开发笔记(七十一):红胖子8分钟带你深入级联分类器训练

    前言   红胖子,来也!  做图像处理,经常头痛的是明明分离出来了(非颜色的),分为几块区域,那怎么知道这几块区域到底哪一块是我们需要的,那么这部分就涉及到需要识别了.  识别可以自己写模板匹配.特征 ...