<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          字節(jié)跳動踩坑記:一知半解protobuf

          共 975字,需瀏覽 2分鐘

           ·

          2020-09-17 02:18

          本篇寫個小坑,別期望太高…




          在廣告系統(tǒng)里,對延遲是毫秒必爭(畢竟省下來的每一毫秒都可以用在后端優(yōu)化效果),因此我們和外部媒體之間的通信往往使用 protobuf 。


          相比 json、xml,protobuf 確實節(jié)省了不少編解碼的時間以及網(wǎng)絡(luò)開銷,不過相應(yīng)的代價是犧牲了便利性不能用 vi 等文本編輯器查看/修改,遇到問題時排查也比較麻煩。




          - 入坑 -


          比如 7 月份,某媒體希望一次請求中拉到多條廣告(用于信息流場景),因此在 imp 添加一個 ads_count 字段,用于標(biāo)識本次請求需要的廣告數(shù)量。


          過程是這樣,在?xxx.proto 里給?Impression 類型添加一個新字段

          package com.xxx;message BidRequest {??string?id?=?1;??int32?ver?=?2;  message Impression {????...????int32?ads_count?=?9;  }??Impression?imp?=?3;  ...}


          然后用 protoc 編譯,生成新版的?xxx.pb.go?

          $ protoc --go_out=. xxx.proto


          看起來挺簡單一個流程,結(jié)果還是出了問題:不論媒體請求中填了什么值,這邊 decode 出來,imp.GetAdsCount() 得到的總是 1 。




          - 排查?-


          由于我方代碼是自測過的,能夠正常取到 ads_count 的值,因此猜測是對方請求有點啥問題。


          于是將對方的請求錄下來,存到文件 req.pb 中,然后用?protoc 暴力解碼:

          $?protoc?--decode-raw?req.pb1 {  6: 0x3938373635343332}2: 13 {  1: 1  2: "6f63bd4df111480"  3: 1}...


          可以看到,我們什么也沒看懂。



          不過還好我們有 xxx.proto,借助已知信息,可以更好地解碼請求:

          $?protoc?--decode=com.xxx.BidRequest?xxx.proto??id: "123456789"ver: 1imp {??id:?1??...  ads_count: 1  10: 3}...


          看到了點不太對的東西。




          - 填坑?-


          在 imp 里面,除了 ads_count 之外,還看到了個 "10: 3"。


          由于 protobuf 的變量名不能是純數(shù)字,所以這應(yīng)當(dāng)是某個在類型定義里沒有出現(xiàn)的字段,decode時只能用其序號代替,由此可知,應(yīng)該是的 proto?文件應(yīng)該有些差異。


          經(jīng)過溝通,媒體確實在 ads_count 之前還加了另一個字段(可能是和其他合作方使用到的);雙方對齊以后,問題順利解決:


          修正 ads_count 的序號:

            message Impression {????...????int32?ads_count?=?10;  }


          用正確的 proto 來 decode:

          $?protoc?--decode=com.xxx.BidRequest?xxx.proto??id: "123456789"ver: 1imp {??id:?1  ...??ads_count:?3}...


          MISSION COMPLETED.




          - encoding?-


          問題是解決了,但是只寫這些就顯得太應(yīng)付了,就再介紹下 proto 文件是怎么編解碼的吧。


          官方有一篇很詳細的文檔介紹了編碼的過程(詳見文末“閱讀原文”),這里摘一些重點。


          以一個簡單的類型為例:

          message Test1 {??optional?int32 a = 1;}

          如果給 a 賦值 150 并序列化,會得到3個字節(jié)(16進制):

          08?96?01

          其中第一個字節(jié)(08)是一個 varint(每個字節(jié)的最高位 = 1 表示該 int 還需要拼上后續(xù)字節(jié)的低 7 bits),其內(nèi)容包含了第一個元素的序號(field number)和類型(wire type)。


          將 08 的二進制 "0000 1000" 拆分成三部分來解釋:

          • 0

            • 表示這個 varint 到這個字節(jié)就結(jié)束了

          • 0001

            • 表示其序號是1

          • 000

            • 表示其值類型也是個 varint


          注意,不管這個 varint 有多大,其末3位總是用于表示類型(wire type),可能的取值有:

          • 0:?varint

          • 1:?64-bit,如 fixed64, sfixed64, double

          • 2:?指定長度類型,如 string, bytes, 內(nèi)嵌類型

          • 5:?32-bit,如 fixed32, sfixed32, float


          第2、3個字節(jié)(96?01)是 a 的值,其二進制表示是

          1001?0110 0000 0001

          第 2 字節(jié)的最高位是 1 ,我們知道這個 varint 還沒結(jié)束;而第 3 字節(jié)的最高位是 0 ,這個 varint 就到此結(jié)束了。


          將兩個最高位去掉,拼出一個完整的二進制數(shù):

          0000001?0010110 = 150

          注意:varint 按字節(jié)序是小端存儲,因此第 3 個字節(jié)的 0000001 放在高位。




          - signed integers?-


          varint 看起來是個好東西,因為實踐中經(jīng)常會用到一些枚舉值,可能的取值范圍很小,使用 varint 只需要少量的空間。


          不過如果我們需要用 -1 的時候怎么辦呢?不管是用反碼還是補碼,都需要考慮符號位的問題? —— 對于 int32/int64,負數(shù)的編碼總是要占用 10 個字節(jié)。


          protobuf 的解決方案是為 sint32/sint64 引入 "ZigZag encoding",簡單來說就是交替使用 0,1,2,3,... 來表示 0,-1,1,-2,...,從而將較小的負數(shù)編碼為較小的無符號數(shù),再使用 varint 編碼。




          - 沒了?-


          就這樣吧,更多細節(jié)(string、內(nèi)嵌類型以及數(shù)組的編碼),請參考官方文檔(文末“閱讀原文”)。


          最后一個小問題,下面這個編碼后的消息,表示什么意思呢?

          12?03?36 36 36





          推薦閱讀



          學(xué)習(xí)交流 Go 語言,掃碼回復(fù)「進群」即可


          站長 polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術(shù)

          職場和創(chuàng)業(yè)經(jīng)驗


          Go語言中文網(wǎng)

          每天為你

          分享 Go 知識

          Go愛好者值得關(guān)注



          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中国无码一区二区三区 | 日韩AV一二三 | 成人做爱网站免费 | 波多野结衣一区二区三区国产精品 | 国产黄色电影免费观看 |