5分钟学会 gRPC

介绍
我猜测大部分长期使用 Java 的开发者应该较少会接触 gRPC,毕竟在 Java 圈子里大部分使用的还是 Dubbo/SpringClound 这两类服务框架。
我也是近段时间有机会从零开始重构业务才接触到 gRPC 的,当时选择 gRPC 时也有几个原因:

- 基于云原生的思路开发部署项目,而在云原生中
gRPC几乎已经是标准的通讯协议了。 - 开发语言选择了 Go,在 Go 圈子中
gRPC显然是更好的选择。 - 公司内部有部分业务使用的是
Python开发,在多语言兼容性上gRPC支持的非常好。
经过线上一年多的平稳运行,可以看出 gRPC 还是非常稳定高效的;rpc 框架中最核心的几个要点:
- 序列化
- 通信协议
- IDL(接口描述语言)
这些在 gRPC 中分别对应的是:
- 基于
Protocol Buffer序列化协议,性能高效。 - 基于
HTTP/2标准协议开发,自带stream、多路复用等特性;同时由于是标准协议,第三方工具的兼容性会更好(比如负载均衡、监控等) - 编写一份
.proto接口文件,便可生成常用语言代码。
HTTP/2
学习 gRPC 之前首先得知道它是通过什么协议通信的,我们日常不管是开发还是应用基本上接触到最多的还是 HTTP/1.1 协议。

由于 HTTP/1.1 是一个文本协议,对人类非常友好,相反的对机器性能就比较低。
需要反复对文本进行解析,效率自然就低了;要对机器更友好就得采用二进制,HTTP/2 自然做到了。
除此之外还有其他优点:
- 多路复用:可以并行的收发消息,互不影响
HPACK节省header空间,避免HTTP1.1对相同的header反复发送。
Protocol
gRPC 采用的是 Protocol 序列化,发布时间比 gRPC 早一些,所以也不仅只用于 gRPC,任何需要序列化 IO 操作的场景都可以使用它。
它会更加的省空间、高性能;之前在开发 https://github.com/crossoverJie/cim 时就使用它来做数据交互。
package order.v1;
service OrderService{
rpc Create(OrderApiCreate) returns (Order) {}
rpc Close(CloseApiCreate) returns (Order) {}
// 服务端推送
rpc ServerStream(OrderApiCreate) returns (stream Order) {}
// 客户端推送
rpc ClientStream(stream OrderApiCreate) returns (Order) {}
// 双向推送
rpc BdStream(stream OrderApiCreate) returns (stream Order) {}
}
message OrderApiCreate{
int64 order_id = 1;
repeated int64 user_id = 2;
string remark = 3;
repeated int32 reason_id = 4;
}
使用起来也是非常简单的,只需要定义自己的 .proto 文件,便可用命令行工具生成对应语言的 SDK。
具体可以参考官方文档:
https://grpc.io/docs/languages/go/generated-code/
调用
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
test.proto

生成代码之后编写服务端就非常简单了,只需要实现生成的接口即可。
func (o *Order) Create(ctx context.Context, in *v1.OrderApiCreate) (*v1.Order, error) {
// 获取 metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
}
fmt.Println(md)
fmt.Println(in.OrderId)
return &v1.Order{
OrderId: in.OrderId,
Reason: nil,
}, nil
}

客户端也非常简单,只需要依赖服务端代码,创建一个 connection 然后就和调用本地方法一样了。
这是经典的 unary(一元)调用,类似于 http 的请求响应模式,一个请求对应一次响应。

Server stream
gRPC 除了常规的 unary 调用之外还支持服务端推送,在一些特定场景下还是很有用的。

func (o *Order) ServerStream(in *v1.OrderApiCreate, rs v1.OrderService_ServerStreamServer) error {
for i := 0; i < 5; i++ {
rs.Send(&v1.Order{
OrderId: in.OrderId,
Reason: nil,
})
}
return nil
}
服务端的推送如上所示,调用 Send 函数便可向客户端推送。
for {
msg, err := rpc.RecvMsg()
if err == io.EOF {
marshalIndent, _ := json.MarshalIndent(msgs, "", "\t")
fmt.Println(msg)
return
}
}
客户端则通过一个循环判断当前接收到的数据包是否已经截止来获取服务端消息。
为了能更直观的展示这个过程,优化了之前开发的一个 gRPC 客户端,可以直观的调试 stream 调用。

上图便是一个服务端推送示例。
Client Stream

除了支持服务端推送之外,客户端也支持。
客户端在同一个连接中一直向服务端发送数据,服务端可以并行处理消息。
// 服务端代码
func (o *Order) ClientStream(rs v1.OrderService_ClientStreamServer) error {
var value []int64
for {
recv, err := rs.Recv()
if err == io.EOF {
rs.SendAndClose(&v1.Order{
OrderId: 100,
Reason: nil,
})
log.Println(value)
return nil
}
value = append(value, recv.OrderId)
log.Printf("ClientStream receiv msg %v", recv.OrderId)
}
log.Println("ClientStream finish")
return nil
}
// 客户端代码
for i := 0; i < 5; i++ {
messages, _ := GetMsg(data)
rpc.SendMsg(messages[0])
}
receive, err := rpc.CloseAndReceive()
代码与服务端推送类似,只是角色互换了。

Bidirectional Stream

同理,当客户端、服务端同时都在发送消息也是支持的。
// 服务端
func (o *Order) BdStream(rs v1.OrderService_BdStreamServer) error {
var value []int64
for {
recv, err := rs.Recv()
if err == io.EOF {
log.Println(value)
return nil
}
if err != nil {
panic(err)
}
value = append(value, recv.OrderId)
log.Printf("BdStream receiv msg %v", recv.OrderId)
rs.SendMsg(&v1.Order{
OrderId: recv.OrderId,
Reason: nil,
})
}
return nil
}
// 客户端
for i := 0; i < 5; i++ {
messages, _ := GetMsg(data)
// 发送消息
rpc.SendMsg(messages[0])
// 接收消息
receive, _ := rpc.RecvMsg()
marshalIndent, _ := json.MarshalIndent(receive, "", "\t")
fmt.Println(string(marshalIndent))
}
rpc.CloseSend()
其实就是将上诉两则合二为一。

通过调用示例很容易理解。
元数据
gRPC 也支持元数据传输,类似于 HTTP 中的 header。
// 客户端写入
metaStr := `{"lang":"zh"}`
var m map[string]string
err := json.Unmarshal([]byte(metaStr), &m)
md := metadata.New(m)
// 调用时将 ctx 传入即可
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 服务端接收
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
}
fmt.Println(md)
gRPC gateway
gRPC 虽然功能强大使用也很简单,但对于浏览器、APP的支持还是不如 REST 应用广泛(浏览器也支持,但应用非常少)。
为此社区便创建了 https://github.com/grpc-ecosystem/grpc-gateway 项目,可以将 gRPC 服务暴露为 RESTFUL API。

为了让测试可以习惯用 postman 进行接口测试,我们也将 gRPC 服务代理出去,更方便的进行测试。
反射调用
作为一个 rpc 框架,泛化调用也是必须支持的,可以方便开发配套工具;gRPC 是通过反射支持的,通过拿到服务名称、pb 文件进行反射调用。
https://github.com/jhump/protoreflect 这个库封装了常见的反射操作。
上图中看到的可视化 stream 调用也是通过这个库实现的。
负载均衡
由于 gRPC 是基于 HTTP/2 实现的,客户端和服务端会保持长连接;这时做负载均衡就不像是 HTTP 那样简单了。
而我们使用 gRPC 想达到效果和 HTTP 是一样的,需要对请求进行负载均衡而不是连接。
通常有两种做法:
- 客户端负载均衡
- 服务端负载均衡
客户端负载均衡在 rpc 调用中应用广泛,比如 Dubbo 就是使用的客户端负载均衡。
gRPC 中也提供有相关接口,具体可以参考官方demo。
https://github.com/grpc/grpc-go/blob/87eb5b7502/examples/features/load_balancing/README.md
客户端负载均衡相对来说对开发者更灵活(可以自定义适合自己的策略),但相对的也需要自己维护这块逻辑,如果有多种语言那就得维护多份。
所以在云原生这个大基调下,更推荐使用服务端负载均衡。
可选方案有:
- istio
- envoy
- apix
这块我们也在研究,大概率会使用 envoy/istio。
总结
gRPC 内容还是非常多的,本文只是作为一份入门资料希望能让不了解 gRPC 的能有一个基本认识;这在云原生时代确实是一门必备技能。
对文中的 gRPC 客户端感兴趣的朋友,可以参考这里的源码:
https://github.com/crossoverJie/ptg
5分钟学会 gRPC的更多相关文章
- 5分钟学会使用Less预编译器
5分钟学会使用Less预编译器 Less是什么? LESS CSS是一种动态样式语言,属于CSS预处理语言的一种,它使用类似CSS的语法为CSS赋予了动态语言的特性,如变量.继承.运算.函数等,更方便 ...
- 【grunt第二弹】30分钟学会使用grunt打包前端代码(02)
前言 上一篇博客,我们简单的介绍了grunt的使用,一些基础点没能覆盖,我们今天有必要看看一些基础知识 [grunt第一弹]30分钟学会使用grunt打包前端代码 配置任务/grunt.initCon ...
- 《量化投资:以MATLAB为工具》连载(2)基础篇-N分钟学会MATLAB(中)
http://www.matlabsky.com/thread-43937-1-1.html <量化投资:以MATLAB为工具>连载(3)基础篇-N分钟学会MATLAB(下) ...
- 《量化投资:以MATLAB为工具》连载(1)基础篇-N分钟学会MATLAB(上)
http://blog.sina.com.cn/s/blog_4cf8aad30102uylf.html <量化投资:以MATLAB为工具>连载(1)基础篇-N分钟学会MATLAB(上) ...
- [分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例)
[分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例) 踏雁寻花 发表于 2015-8-23 23:31:28 https://www.itsk.com/thread-35 ...
- 50分钟学会Laravel 50个小技巧
50分钟学会Laravel 50个小技巧 时间 2015-12-09 17:13:45 Yuansir-web菜鸟 原文 http://www.yuansir-web.com/2015/12/09 ...
- 10分钟学会Linux
10分钟学会Linux有点夸张,可是能够让一个新手初步熟悉Linux中最重要最主要的知识,本文翻译的英文网页在众多Linux入门学习的资料中还是很不错的. 英文地址:http://freeengine ...
- PHP学习过程_Symfony_(3)_整理_十分钟学会Symfony
这篇文章主要介绍了Symfony学习十分钟入门教程,详细介绍了Symfony的安装配置,项目初始化,建立Bundle,设计实体,添加约束,增删改查等基本操作技巧,需要的朋友可以参考下 (此文章已被多人 ...
- 30分钟学会使用Spring Web Services基础开发
时隔一年终于又推出了一篇30分钟系列,上一篇<30分钟学会反向Ajax>是2016年7月的事情了.时光荏苒,岁月穿梭.虽然一直还在从事Java方面的开发工作,但是私下其实更喜欢使用C++. ...
随机推荐
- new Date时间格式转换方法
平时经常会使用到时间转换,组件库已经有很多组件可以自动生成时间类型了,但是底层的封装方法还是得有 获取当前时间 new Date() 或者自己拥有一个毫秒时间 方法如下 /** * title: 时 ...
- 防火墙——firewalld
介绍 firewald是对于iptables的一个封装,可以让你更容易地管理iptables规则.firewalld是iptables前端控制器,用于实现持久地网络流量规则. 一.对比 firewal ...
- windows10使用wireshark抓取本机请求包
1.管理员运行cmd 右键左下角windows图标,管理员运行Windows PowerShell 2.输入ipconfg查看本机ip和网关ip 3.执行命令 route add 本机ip mask ...
- 如何修改TOMCAT的默认主页为你自己项目的主页
感谢作者:xxs673076773 原文链接:https://www.iteye.com/blog/xxs673076773-1134805 (最合适的) 最直接的办法是,删掉tomcat下原有Roo ...
- java创建自定义类的对象数组
1 public class Student{ 2 static int number = 0; // 静态变量的访问可以不用创建类的实例就可就可使用< 类名.属性 >的方法访问 3 ...
- 这个Dubbo注册中心扩展,有点意思!
今天想和大家聊聊Dubbo源码中实现的一个注册中心扩展.它很特殊,也帮我解决了一个困扰已久的问题,刚刚在生产中用了,效果很好,迫不及待想分享给大家. Dubbo的扩展性非常灵活,可以无侵入源码加载自定 ...
- 年前最后一次2022.1.28_RP++
T1同昨(我看到题目就粘上昨天的代码,结果题还没发我就A了hhhhhh) T2一开始想用深搜,结果T掉了...只好改广搜,就挺令人头大 点击查看宽广对比 #include<bits/stdc++ ...
- 【流行前沿】联邦学习 Partial Model Averaging in Federated Learning: Performance Guarantees and Benefits
Sunwoo Lee, , Anit Kumar Sahu, Chaoyang He, and Salman Avestimehr. "Partial Model Averaging in ...
- R数据分析:数据清洗的思路和核心函数介绍
好多同学把统计和数据清洗搞混,直接把原始数据发给我,做个统计吧,这个时候其实很大的工作量是在数据清洗和处理上,如果数据很杂乱,清洗起来是很费工夫的,反而清洗好的数据做统计分析常常就是一行代码的事情. ...
- Linux编译安装升级bash5.1
线上服务器有次做漏洞扫描时,被扫描出有bash漏洞.平时还是比较少遇到有bash的漏洞,好在编译升级比较简单. 测试环境系统:CentOS.Ubuntu 一.下载官网最新bash版本 bash官网下载 ...