大家好,我是了不起。
最近我們在專案中,透過使用 protobuf 格式作為儲存資料的一個載體。一個不小心就給自己埋了個大坑,還是過了好久才發現。
protobuf 全名為Protocal buffers. 它是由Google 研發的,一種可跨語言、可跨平台、可擴展的序列化數據的機制。類似於 XML ,但它更小、更快、更簡單。你只需要定義一次你希望的資料如何被結構化,然後你可以使用它的產生工具,產生包含一些序列化和反序列化等操作的原始碼。可以輕鬆地從各種資料流和使用各種程式語言寫入和讀取結構化的資料。
proto2版本支援在Java、Python、Objective-C和C 中產生程式碼。使用新的proto3語言版本,你還可以使用Kotlin、Dart、Go、Ruby、PHP和C#,還有更多的語言。
在我們的新專案中,我們透過使用 protobuf 格式來儲存專案運行的資料。這樣我們在調試過程中,可能會根據現場錄製的資料進行本地的調試。
message ImageData { // ms int64 timestamp = 1; int32 id = 2; Data mat = 3; } message PointCloud { // ms int64 timestamp = 1; int32 id = 2; PointData pointcloud = 3; } message State { // ms int64 timestamp = 1; string direction = 2; } message Sensor { repeated PointCloud point_data = 1; repeated ImageData image_data = 2; repeated State vehicle_data = 3; }
我們定義了這樣一組數據, 然後儲存的時候,因為Sensor 這3個資料來源的幀率不一樣,因此儲存的時候,單一Sensor 中其實只包含了一組數據,另外兩個類型的資料並沒有包含進去。
當我們只錄製單一 pack 的時候,我們並沒有遇到問題。直到我們覺得單一包,不能長時間錄製,我們需要找一種解決方法來分割包 。
當時覺得這個一定是很簡單的,我們就設定了一個套件達到 500M 的時候,我們就讓後面的資料存到新的套件裡。很順利的寫完,然後放到現場進行資料錄製。錄製一段時間之後,我們把包包拿回來進行模擬測試我們的新程式。發現有些包的資料解析出來是有問題的。程式運行到一半會卡在那裡不動。經過多次測試,發現部分包有這個問題。
我們一開始懷疑的是,判斷檔案大小的方式不對,影響到了分包。因為判斷文件大小的時候,會去開啟文件。但是經過好幾種其他的不打開文件的方式判斷,從而進行分割。還是遇到了部分錄製的包有問題。
這時我才懷疑到 protobuf 對儲存資料會有一些特殊的要求。後來我看了一些文章,了解到 protobuf 儲存多組資料到一個檔案需要有標誌符。要不然後面從檔案解析回來的時候,protobuf 因為不知道單一資料的停止符在哪裡,導致資料解析出錯。
到這裡,這個坑出現了。 我們儲存了一系列的資料到單一套件中,沒有做任何分隔符號的操作。 protobuf在解析的時候,把文件中所有的內容都解析成了單一Sensor。 Sensor 中包含裡所有數據, protobuf 主動合併了所有儲存的資料。
在這時,我才發現以前單包錄製的時候,數據都是對的,那真的是我運氣好。 protobuf恰好解析成功了。
既然知道 protobuf 會這麼操作,那我們就只要知道 protobuf 怎麼分割就行了。這個方法還真不好找,因為像我們這樣使用的人太少了。中文搜尋完全搜不到這一塊的內容,可能大家都不會使用protobuf來儲存資料吧,大家使用的方式應該都是多個服務中進行互動的場景吧。
最後透過stackoverflow上的一些回答找到了答案,從回答中得知,這個解決辦法在 protobuf 3.3 的時候,才正式被合併進去。看起來這個功能真的很少用。
bool SerializeDelimitedToOstream(const MessageLite& message, std::ostream* output); bool ParseDelimitedFromZeroCopyStream( MessageLite* message, io::ZeroCopyInputStream* input, bool* clean_eof);
透過這對方法,可以對檔案進行依照資料流一個一個的儲存讀取。再也不用擔心資料被合併讀取。
當然透過這種方式儲存的數據,不能被原來的解析方式所解析,儲存的而進行格式完全變了。這種方式會先儲存二進位資料的大小,再儲存二進位資料。
經過一番折騰,終於搞定了這個分割的坑。使用場景可能比較小眾,導致了許多資料根本找不到。靠自己看原始碼才發現這些問題。 C 的原始碼真不好讀,有很多的模板方法、模板類別容易錯過一些細節。最後還是看的C#的程式碼,才完全確認的。
以上是原話重寫:一個意外的發現是,原本被視為 bug 的問題其實是 Protobuf 設計中的一個特性的詳細內容。更多資訊請關注PHP中文網其他相關文章!