思考gRPC :为什么是protobuf

简介: 背景谈到RPC,就避免不了序列化的话题。gRPC默认的序列化方式是protobuf,原因很简单,因为两者都是google发明的,哈哈。

背景

谈到RPC,就避免不了序列化的话题。

gRPC默认的序列化方式是protobuf,原因很简单,因为两者都是google发明的,哈哈。

在当初Google开源protobuf时,很多人就期待是否能把RPC的实现也一起开源出来。没想到最终出来的是gRPC,终于补全了这一块。

跨语言的序列化方案

事实上的跨语言序列化方案只有三个: protobuf, thrift, json。

  • json体积太大,并且缺少类型信息,实际上只用在RESTful接口上,并没有看到RPC框架会默认选json做序列化的。

国内一些大公司的使用情况:

  • protobuf ,腾迅,百度等

  • thrift,小米,美团等

  • hessian, 阿里用的是自己维护的版本,有js/cpp的实现,因为阿里主用java,更多是历史原因。

序列化里的类型信息

序列化就是把对象转换为二进制数据,反序列化就把二进制数据转换为对象。

各种序列化库层出不穷,其中有一个重要的区别:类型信息存放在哪

可以分为三种:

  1. 不保存类型信息

    典型的是各种json序列化库,优点是灵活,缺点是使用的双方都要知道类型是什么。当然有一些json库会提供一些扩展,偷偷把类型信息插入到json里。

  2. 类型信息保存到序列化结果里

    比如java自带的序列化,hessian等。缺点是类型信息冗余。比如RPC里每一个request都要带上类型。因此有一种常见的RPC优化手段就是两端协商之后,后续的请求不需要再带上类型信息。

  3. 在生成代码里带上类型信息

    通常是在IDL文件里写好package和类名,生成的代码直接就有了类型信息。比如protobuf, thrift。缺点是需要生成代码,双方都要知道IDL文件。

类型信息看起来是一个小事,但在安全上却会出大问题,后面会讨论。

实际使用中序列化有哪些问题

这里讨论的是没有IDL定义的序列化方案,比如java自带的序列化,hessian, 各种json库。

  • 大小莫名增加,比如用户不小心向map里put了大对象。
  • 对象之间互相引用,用户根本不清楚序列化到底会产生什么结果,可能新加一个field就不小心被序列化了
  • enum类新增加的不能识别,当两端的类版本不一致时就会出错
  • 哪些字段应该跳过序列化 ,不同的库都有不同的 @Ignore ,没有通用的方案
  • 很容易把一些奇怪的类传过来,然后对端报ClassNotFoundException
  • 新版本jdk新增加的类不支持,需要序列化库不断升级,如果没人维护就悲剧了
  • 库本身的代码质量不高,或者API设计不好容易出错,比如kryo

gRPC是protobuf的一个插件

以gRPC官方的Demo为例:

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 {
  string message = 1;
}

可以看到rpc的定义也是写在proto文件里的。实际上gRPC是protobuf的一个扩展,通过扩展生成gRPC相关的代码。

protobuf并不是完美解决方案

在protobuf出来以后,也不断出现新的方案。比如

protobuf的一些缺点:

  • 缺少map/set的支持(proto3支持map)
  • Varint编码会消耗CPU
  • 会影响CPU缓存,比如比较大的int32从4字节用Varint表示是5字节就不对齐了
  • 解码时要复制一份内存,不能做原地内存引用的优化

    protobuf在google 2008年公开的,内部使用自然更早。当时带宽还比较昂贵,现在人们对速度的关注胜过带宽了。

protobuf需要生成代码的确有点麻烦,所以会有基于java annotation的方案:

同样thrift有:

序列化被人忽视的安全性问题

序列化漏洞危害很大

  1. 序列化漏洞通常比较严重,容易造成任意代码执行
  2. 序列化漏洞在很多语言里都会有,比如Python Pickle序列化漏洞。

很多程序员不理解为什么反序列化可以造成任意代码执行。

反序列化漏洞到底是怎么工作的呢?很难直接描述清楚,这些漏洞都有很精巧的设计,把多个地方的代码串联起来。可以参考这个demo,跑起来调试下就可以有直观的印象:

这里有两个生成java序列化漏洞代码的工具:

常见的库怎样防止反序列化漏洞

下面来看下常见的序列化方案是怎么防止反序列化漏洞的:

  1. Java Serialization

    • jdk里增加了一个filter机制 http://openjdk.java.net/jeps/290 ,这个一开始是出现在jdk9上的,后面移值回jdk6/7/8上,如果安装的jdk版本是比较新的,可以找到相关的类
  2. jackson-databind

  3. fastjson

    • fastjson通过一个denyList来过滤掉一些危险类的package,参见ParserConfig.java
    • fastjson在新版本里denyList改为通过hashcode来隐藏掉package信息,但通过这个DenyTest5可以知道还是过滤掉常见危险类的package
    • fastjson在新版本里默认把autoType的功能禁止掉了

所以总结下来,要么白名单,要么黑名单。当然黑名单机制不能及时更新,业务方得不断升jar包,非常蛋疼。白名单是比较彻底的解决方案。

为什么protobuf没有序列化漏洞

这些序列化漏洞的根本原因是:没有控制序列化的类型范围

为什么在protobuf里并没有这些反序列化问题?

  • protobuf在IDL里定义好了package范围
  • protobuf的代码都是自动生成的,怎么处理二进制数据都是固定的

protobuf把一切都框住了,少了灵活性,自然就少漏洞。

总结

  • 应该重视反序列化漏洞,毕竟Oracle都不得不考虑把java序列化废弃了
  • 序列化漏洞的根本原因是:没有控制序列化的类型范围
  • 防止序列化漏洞,最好是使用白名单
  • protobuf通过IDL生成代码,严格控制了类型范围
  • protobuf不是完美的方案,但是作为跨语言的序列化事实方案之一,IDL生成代码比较麻烦也不是啥大问题

链接

相关文章
|
JSON 应用服务中间件 API
利用Grafana的API Key+Nginx反向代理实现Grafana免登录访问
利用Grafana的API Key+Nginx反向代理实现Grafana免登录访问
1021 1
|
人工智能 算法
通义千问春节上新,除夕夜AI和你一起过大年!
通义千问春节上新,除夕夜AI和你一起过大年!
326 4
|
NoSQL Redis
Redis安装布隆(Bloom Filter)过滤器
Redis安装布隆(Bloom Filter)过滤器
782 0
Redis安装布隆(Bloom Filter)过滤器
|
XML JSON Java
GRPC与 ProtoBuf 的理解与总结
GRPC与 ProtoBuf 的理解与总结
690 0
|
消息中间件 Kafka Go
使用github.com/IBM/sarama 编写消费kafka的功能
使用github.com/IBM/sarama 编写消费kafka的功能
|
边缘计算 负载均衡 网络协议
B站千万级长连接实时消息系统的架构设计与实践
本文将介绍B站基于golang实现的千万级长连接实时消息系统的架构设计与实践,包括长连接服务的框架设计,以及针对稳定性与高吞吐做的相关优化。
338 9
|
搜索推荐 算法 C++
蓝桥杯分糖果、最小化战斗力差距、小蓝零花钱
这是一个关于算法问题的集合,包括三个不同的任务: 1. **分糖果**:肖恩有不同种类的糖果要分给学生,目标是使得到糖果字符串的字典序最大且尽量小。给定糖果种类数和一个初始字符串,输出能达到的最小字典序的最大值。 2. **最小化战斗力差距**:小蓝需要将队员分为两组,每组战斗力差距最小。给定队员数量和战斗力值,找出最小的战斗力差距。 3. **小蓝的零花钱**:小蓝要在序列中分割偶数和奇数,每次分割代价是两端元素差的绝对值。目标是在预算内确定最多能进行多少次这样的分割。 每个问题都提供了输入输出示例和相应的C++代码片段来解决这些问题。
|
移动开发 前端开发 JavaScript
[初学者来练]用html+css+javascript个人博客作业需求
【6月更文挑战第3天】该项目旨在通过HTML、CSS和JavaScript创建一个响应式个人博客网站,提升前端开发技术理解与实践能力。主要包括设计简洁页面布局、实现主页展示、文章列表、详情页及评论功能。技术要求使用HTML5/CSS3/JavaScript,可选前端框架,强调代码质量和可维护性。提交内容需包括完整网站、开发文档和测试过的代码。评分标准涉及设计、功能实现、技术应用和文档质量。
144 0
|
SQL 关系型数据库 MySQL
必知的 MySQL 索引失效场景【包括实践验证】,别再踩坑了!(上)
必知的 MySQL 索引失效场景【包括实践验证】,别再踩坑了!
1576 2
|
消息中间件 NoSQL 算法
第一次凡尔赛,字节跳动3面+腾讯6面一次过,谈谈我的大厂面经
简单来说,就如标题一样,我今天也想要凡尔赛一次,原来大厂的面试也没有想象中的那么难,字节跳动3面+腾讯6面,就这么一次性过了,下面就细细聊聊我的大厂面经吧,希望能够给金三银四要面试的朋友提供一些经验。