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++. ...
随机推荐
- 通过json动态创建控制器
通过字符串来创建控制器 如果通过字符串来创建控制器 不可以直接通过类型来获取对应的类 因为Swift有命名空间,类前需要加上命名空间的名称 获取命名空间的名称 let executable = NSB ...
- Servlet Servlet的装载三种情况
感谢原文作者:DaleyDC 原文链接:https://blog.csdn.net/sinat_32873711/article/details/53170342 Servlet的装载三种情况: 自动 ...
- 通过Xib加载控制器的View
1.创建窗口self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];2.设置窗口根控制器2.1从XIB当 ...
- [转]API性能测试基本性能指标及要求
原文链接http://blog.csdn.net/strawbingo/article/details/46458959 指标的基本概念 1.事务(Transaction) 在web性能测试中,一个事 ...
- 推荐一款仿iPhone桌面的代码. ___王朋.
Demo:https://files.cnblogs.com/files/sixindev/LxGridView-master.zip 这是作者原来的效果图,很多东西还需要慢慢学习.作者用的很多类,根 ...
- JavaWeb中jsp路径斜杆(/)跟没斜杆的路径映射问题
在JavaWeb开发中,只要是写URL地址,那么建议最好以"/"开头,也就是使用绝对路径的方式,那么这个"/"到底代表什么呢?可以用如下的方式来记忆" ...
- windows内核基础与异常处理
前两日碰到了用异常处理来做加密的re题目 所以系统学习一下windows内核相关 windows内核基础 权限级别 内核层:R0 零环 核心态工作区域 大多数驱动程序 应用层:R3 用户态工作区域 只 ...
- 3.6 万颗星!开源 Web 服务器后起之秀,自带免费 HTTPS 开箱即用
众所周知,Web 服务器是 Web 开发中不可或缺的基础服务,在开发中经常会用到.耳熟能详的开源 Web 服务器有久负盛名的 Apache.性能强劲的 Nginx.而我们今天要介绍的开源项目是采用 G ...
- Solution -「UOJ #46」玄学
\(\mathcal{Description}\) Link. 给定序列 \(\{a_n\}\) 和 \(q\) 次操作,操作内容如下: 给出 \(l,r,k,b\),声明一个修改方案,表示 ...
- Devops 开发运维高级篇之Jenkins+Docker+SpringCloud微服务持续集成(上)
Devops 开发运维高级篇之Jenkins+Docker+SpringCloud微服务持续集成(上) Jenkins+Docker+SpringCloud持续集成流程说明 大致流程说明: 1) 开发 ...