一個 bug 竟然是 Protobuf 的 feature
大家好,我是了不起。
最近我們在項目中,通過使用 protobuf 格式作為存儲數(shù)據(jù)的一個載體。一個不小心就給自己埋了個大坑,還是過了好久才發(fā)現(xiàn)。
protobuf 簡介
protobuf 全名叫 Protocal buffers. 它是由 Google 研發(fā)的,一種可跨語言、可跨平臺、可擴(kuò)展的序列化數(shù)據(jù)的機(jī)制。類似于 XML ,但是它更小、更快、更簡單。你只需要定義一次你希望的數(shù)據(jù)如何被結(jié)構(gòu)化,然后你可以使用它的生成工具,生成包含一些序列化和反序列化等操作的源代碼。可以輕松地從各種數(shù)據(jù)流和使用各種編程語言寫入和讀取結(jié)構(gòu)化的數(shù)據(jù)。
proto2版本支持在Java、Python、Objective-C和C++中生成代碼。使用新的proto3語言版本,你還可以使用Kotlin、Dart、Go、Ruby、PHP和C#,還有更多的語言。
怎么發(fā)現(xiàn)的?
在我們的新項目中,我們通過使用 protobuf 格式來存儲項目運行的數(shù)據(jù)。這樣我們在調(diào)試過程中,可能根據(jù)現(xiàn)場錄制的數(shù)據(jù)進(jìn)行本地的調(diào)試。
我們定義了這樣一組數(shù)據(jù), 然后存儲的時候,因為Sensor 這3個數(shù)據(jù)源的幀率不一樣,因此存儲的時候,單個 Sensor 中其實只包含了一組數(shù)據(jù),另外兩個類型的數(shù)據(jù)并沒有包含進(jìn)去。
當(dāng)我們只錄制單個 pack 的時候,我們并沒有遇到問題。直到我們覺得單個包,不能長時間錄制,我們需要找一種解決方法來分割包 。
當(dāng)時覺得這個一定是很簡單的,我們就設(shè)定了一個包達(dá)到 500M 的時候,我們就讓后面的數(shù)據(jù)存到新的包中。很順利的寫完,然后放到現(xiàn)場進(jìn)行數(shù)據(jù)錄制。錄制一段時間之后,我們把包拿回來進(jìn)行模擬測試我們的新程序。發(fā)現(xiàn)有些包的數(shù)據(jù)解析出來是有問題的。程序運行到一半會卡在那里不動。經(jīng)過多次測試,發(fā)現(xiàn)是部分包有這個問題。
我們一開始懷疑的是,判斷文件大小的方式不對,影響到了分包。因為判斷文件大小的時候,會去打開文件。但是經(jīng)過好幾種其他的不打開文件的方式判斷,從而進(jìn)行分割。還是遇到了部分錄制的包有問題。
這時我才懷疑到 protobuf 對存儲數(shù)據(jù)會有一些特殊的要求。后來看了一些文章,了解到 protobuf 存儲多組數(shù)據(jù)到一個文件需要有標(biāo)志符。要不然后面從文件解析回來的時候,protobuf 因為不知道單個數(shù)據(jù)的停止符在哪里,導(dǎo)致數(shù)據(jù)解析出錯。
到這里,這個坑出現(xiàn)了。我們存儲了一系列的數(shù)據(jù)到單個包中,沒有做任何分隔符的操作。protobuf在解析的時候,把文件中所有的內(nèi)容都解析成了單個Sensor。Sensor 中包含里所有數(shù)據(jù), protobuf 主動合并了所有存儲的數(shù)據(jù)。
在這時,我才發(fā)現(xiàn)以前單包錄制的時候,數(shù)據(jù)都是對的,那真的是我運氣好。protobuf恰好解析成功了。
怎么解決呢?
既然知道 protobuf 會這么操作,那我們就只要知道 protobuf 怎么分割就行了。這個方法還真不好找,因為像我們這樣使用的人太少了。中文搜索完全搜不到這一塊的內(nèi)容,可能大家都不會使用protobuf來存儲數(shù)據(jù)吧,大家使用的方式應(yīng)該都是多個服務(wù)中進(jìn)行交互的場景吧。
最終通過stackoverflow上的一些回答找到了答案,從回答中得知,這個解決辦法在 protobuf 3.3 的時候,才正式被合并進(jìn)去。看起來這個功能真的很少用啊。
通過這一對方法,可以對文件進(jìn)行按照數(shù)據(jù)流一個一個的存儲讀取。再也不用擔(dān)心數(shù)據(jù)被合并讀取。
當(dāng)然通過這種方式存儲的數(shù)據(jù),不能被原來的解析方式所解析,存儲的而進(jìn)行格式完全變了。這種方式會先存儲二進(jìn)制數(shù)據(jù)的大小,再存儲二進(jìn)制數(shù)據(jù)。
結(jié)束語
經(jīng)過一番折騰,終于搞定了這個分割的坑。使用場景可能比較小眾,導(dǎo)致了很多資料根本找不到??孔约嚎丛创a才發(fā)現(xiàn)這些問題。C++ 的源碼真不好讀,有很多的模板方法、模板類容易錯過一些細(xì)節(jié)。最后還是看的C#的代碼,才完全確認(rèn)的。






















