思考gRPC:為什麼是protobuf
背景
談到RPC,就避免不了序列化的話題。
gRPC預設的序列化方式是protobuf,原因很簡單,因為兩者都是google發明的,哈哈。
在當初Google開源protobuf時,很多人就期待是否能把RPC的實現也一起開源出來。沒想到最終出來的是gRPC,終於補全了這一塊。
跨語言的序列化方案
事實上的跨語言序列化方案只有三個: protobuf, thrift, json。
- json體積太大,並且缺少型別資訊,實際上只用在RESTful介面上,並沒有看到RPC框架會預設選json做序列化的。
國內一些大公司的使用情況:
-
protobuf ,騰迅,百度等
-
thrift,小米,美團等
-
hessian, 阿里用的是自己維護的版本,有js/cpp的實現,因為阿里主用java,更多是歷史原因。
序列化裡的型別資訊
序列化就是把物件轉換為二進位制資料,反序列化就把二進位制資料轉換為物件。
各種序列化庫層出不窮,其中有一個重要的區別:型別資訊存放在哪?
可以分為三種:
-
不儲存型別資訊
典型的是各種json序列化庫,優點是靈活,缺點是使用的雙方都要知道型別是什麼。當然有一些json庫會提供一些擴充套件,偷偷把型別資訊插入到json裡。
-
型別資訊儲存到序列化結果裡
比如java自帶的序列化,hessian等。缺點是型別資訊冗餘。比如RPC裡每一個request都要帶上型別。因此有一種常見的RPC優化手段就是兩端協商之後,後續的請求不需要再帶上型別資訊。
-
在生成程式碼裡帶上型別資訊
通常是在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出來以後,也不斷出現新的方案。比如
- https://github.com/capnproto/capnproto
- https://github.com/google/flatbuffers
- https://avro.apache.org/
protobuf的一些缺點:
- 缺少map/set的支援(proto3支援map)
- Varint編碼會消耗CPU
- 會影響CPU快取,比如比較大的int32從4位元組用Varint表示是5位元組就不對齊了
-
解碼時要複製一份記憶體,不能做原地記憶體引用的優化
protobuf在google 2008年公開的,內部使用自然更早。當時頻寬還比較昂貴,現在人們對速度的關注勝過頻寬了。
protobuf需要生成程式碼的確有點麻煩,所以會有基於java annotation的方案:
同樣thrift有:
序列化被人忽視的安全性問題
序列化漏洞危害很大
- 序列化漏洞通常比較嚴重,容易造成任意程式碼執行
- 序列化漏洞在很多語言裡都會有,比如Python Pickle序列化漏洞。
很多程式設計師不理解為什麼反序列化可以造成任意程式碼執行。
反序列化漏洞到底是怎麼工作的呢?很難直接描述清楚,這些漏洞都有很精巧的設計,把多個地方的程式碼串聯起來。可以參考這個demo,跑起來除錯下就可以有直觀的印象:
這裡有兩個生成java序列化漏洞程式碼的工具:
常見的庫怎樣防止反序列化漏洞
下面來看下常見的序列化方案是怎麼防止反序列化漏洞的:
-
Java Serialization
- jdk裡增加了一個filter機制 http://openjdk.java.net/jeps/290 ,這個一開始是出現在jdk9上的,後面移值回jdk6/7/8上,如果安裝的jdk版本是比較新的,可以找到相關的類
- Oracle打算廢除java序列化:https://www.infoworld.com/article/3275924/java/oracle-plans-to-dump-risky-java-serialization.html
-
jackson-databind
- jackson-databind裡是過濾掉一些已知的類,參見SubTypeValidator.java
- jackson-databind的CVE issue列表
-
fastjson
- fastjson通過一個
denyList
來過濾掉一些危險類的package,參見ParserConfig.java - fastjson在新版本里
denyList
改為通過hashcode來隱藏掉package資訊,但通過這個DenyTest5
可以知道還是過濾掉常見危險類的package - fastjson在新版本里預設把
autoType
的功能禁止掉了
- fastjson通過一個
所以總結下來,要麼白名單,要麼黑名單。當然黑名單機制不能及時更新,業務方得不斷升jar包,非常蛋疼。白名單是比較徹底的解決方案。
為什麼protobuf沒有序列化漏洞
這些序列化漏洞的根本原因是:沒有控制序列化的型別範圍
為什麼在protobuf裡並沒有這些反序列化問題?
- protobuf在IDL裡定義好了package範圍
- protobuf的程式碼都是自動生成的,怎麼處理二進位制資料都是固定的
protobuf把一切都框住了,少了靈活性,自然就少漏洞。
總結
- 應該重視反序列化漏洞,畢竟Oracle都不得不考慮把java序列化廢棄了
- 序列化漏洞的根本原因是:沒有控制序列化的型別範圍
- 防止序列化漏洞,最好是使用白名單
- protobuf通過IDL生成程式碼,嚴格控制了型別範圍
- protobuf不是完美的方案,但是作為跨語言的序列化事實方案之一,IDL生成程式碼比較麻煩也不是啥大問題
連結
相關文章
- protobuf和gRPCRPC
- GRPC之protobuf理解RPC
- ubuntu編譯grpc & protobufUbuntu編譯RPC
- grpc系列- protobuf詳解RPC
- gRPC(一)入門:什麼是RPC?RPC
- 幽默:什麼是離線思考?
- 什麼是系統思考家?
- gRPC(二)入門:Protobuf入門RPC
- GRPC與 ProtoBuf 的理解與總結RPC
- Cpp(九) gRPC protobuf for C++ 基本使用RPCC++
- 什麼是“六頂思考帽”模型? - modernanalyst模型NaN
- 人是什麼?人生是什麼?人為什麼會變?
- 《Grpc+Protobuf學習筆記》一、前言RPC筆記
- 什麼是框架?為什麼說 Angular 是框架?框架Angular
- 為什麼對gRPC做負載均衡會很棘手?RPC負載
- 為什麼要有 Servlet ,什麼是 Servlet 容器,什麼是 Web 容器?ServletWeb
- GC是什麼?為什麼要有GC?GC
- 什麼是Docker?為什麼使用docker?Docker
- 《Grpc+Protobuf學習筆記》一、protobuf安裝生成程式碼外掛RPC筆記
- 《Grpc+Protobuf學習筆記》二、protobuf安裝生成程式碼外掛RPC筆記
- Python是什麼?為什麼這麼搶手?Python
- ITAM是什麼?為什麼它很重要?
- Python是什麼?為什麼要掌握python?Python
- Elasticsearch:是什麼?你為什麼需要他?Elasticsearch
- TypeScript是什麼,為什麼要使用它?TypeScript
- 什麼是HSTS,為什麼要使用它?
- 什麼是Nginx?Linux為什麼使用Nginx?NginxLinux
- heredoc是什麼?它能為PHP做什麼?PHP
- beego 什麼時候支援grpcGoRPC
- 什麼是Python?Python為什麼這麼搶手?Python
- “有生命、會思考”的智慧城市是什麼樣的?
- gRPC為什麼使用截止時間而不是超時時間?RPC
- 【前端筆記】Vuex 是什麼,為什麼需要前端筆記Vue
- 什麼是隧道代理 為什麼選隧道代理
- 公司為什麼要使用OKR,目的是什麼?OKR
- 什麼是 SCRM,企業為什麼需要SCRM?
- Python是什麼?為什麼Python受歡迎?Python
- 為什麼YFII是比特幣比特幣