Go 大小語意
proto.Size
函數透過遍歷其所有欄位(包括子訊息),傳回 proto.Message 的線路格式編碼的大小(以位元組為單位)。
特別是,它傳回 Go Protobuf 將如何編碼訊息 的大小。
典型用法
識別空的訊息
檢查 proto.Size
是否傳回 0 是一種簡單的方法來識別空的訊息
if proto.Size(m) == 0 {
// No fields set (or, in proto3, all fields matching the default);
// skip processing this message, or return an error, or similar.
}
大小限制程式輸出
假設您正在編寫一個批次處理管線,該管線為另一個我們在此範例中稱為「下游系統」的系統產生工作任務。下游系統已配置為處理中小型任務,但負載測試顯示,當呈現超過 500 MB 的工作任務時,系統會發生連鎖故障。
最好的修復方法是為下游系統新增保護(請參閱 https://cloud.google.com/blog/products/gcp/using-load-shedding-to-survive-a-success-disaster-cre-life-lessons),但當實作負載調節不可行時,您可以決定在管線中新增快速修復
func (*beamFn) ProcessElement(key string, value []byte, emit func(proto.Message)) {
task := produceWorkTask(value)
if proto.Size(task) > 500 * 1024 * 1024 {
// Skip every work task over 500 MB to not overwhelm
// the brittle downstream system.
return
}
emit(task)
}
不正確的用法:與 Unmarshal 無關
由於 proto.Size
傳回 Go Protobuf 將如何編碼訊息的位元組數,因此在解組 (解碼) 輸入的 Protobuf 訊息流時,使用 proto.Size
是不安全的
func bytesToSubscriptionList(data []byte) ([]*vpb.EventSubscription, error) {
subList := []*vpb.EventSubscription{}
for len(data) > 0 {
subscription := &vpb.EventSubscription{}
if err := proto.Unmarshal(data, subscription); err != nil {
return nil, err
}
subList = append(subList, subscription)
data = data[:len(data)-proto.Size(subscription)]
}
return subList, nil
}
當 data
包含 非最小線路格式 的訊息時,proto.Size
可能會傳回與實際解組的大小不同的大小,導致剖析錯誤(最佳情況)或最壞情況下不正確地剖析資料。
因此,只要所有輸入訊息都是由(相同版本的)Go Protobuf 產生,此範例才能可靠地運作。這令人驚訝,而且可能不是故意的。
提示: 請改用 protodelim
套件 來讀取/寫入大小分隔的 Protobuf 訊息流。
進階用法:預先調整緩衝區大小
proto.Size
的進階用法是在封送處理之前判斷緩衝區所需的大小
opts := proto.MarshalOptions{
// Possibly avoid an extra proto.Size in Marshal itself (see docs):
UseCachedSize: true,
}
// DO NOT SUBMIT without implementing this Optimization opportunity:
// instead of allocating, grab a sufficiently-sized buffer from a pool.
// Knowing the size of the buffer means we can discard
// outliers from the pool to prevent uncontrolled
// memory growth in long-running RPC services.
buf := make([]byte, 0, opts.Size(m))
var err error
buf, err = opts.MarshalAppend(buf, m) // does not allocate
// Note that len(buf) might be less than cap(buf)! Read below:
請注意,當啟用延遲解碼時,proto.Size
可能會傳回比 proto.Marshal
(以及類似 proto.MarshalAppend
的變體)將寫入的位元組還多的位元組!因此,當您將編碼的位元組放在線路(或磁碟)上時,請務必使用 len(buf)
並捨棄任何先前的 proto.Size
結果。
具體而言,當發生以下情況時,(子)訊息可以在 proto.Size
和 proto.Marshal
之間「縮小」
- 啟用延遲解碼
- 且訊息以 非最小線路格式 到達
- 且在呼叫
proto.Size
之前未存取訊息,表示尚未解碼 - 且在
proto.Size
之後(但在proto.Marshal
之前)存取訊息,導致其被延遲解碼
解碼會導致任何後續的 proto.Marshal
呼叫編碼訊息(而不是僅僅複製其線路格式),這會導致隱式正規化為 Go 編碼訊息的方式,目前以最小線路格式(但不要依賴它!)。
如您所見,這種情況相當具體,但儘管如此,將 proto.Size
結果視為上限,並且永遠不要假設結果與實際編碼的訊息大小相符,是最佳實務。
背景:非最小線路格式
當編碼 Protobuf 訊息時,有一個最小線路格式大小和許多較大的非最小線路格式,它們解碼為相同的訊息。
非最小線路格式(有時也稱為「反正規化線路格式」)指的是諸如非重複欄位多次出現、非最佳 varint 編碼、封裝的重複欄位以非封裝形式出現在線路上的情況以及其他情況。
我們可能會在不同的情況下遇到非最小線路格式
- 有意地。 Protobuf 支援透過串連其線路格式來串連訊息。
- 意外地。 (可能是協力廠商)Protobuf 編碼器並未理想地編碼(例如,在編碼 varint 時使用比必要更多的空間)。
- 惡意地。 攻擊者可能會專門製作 Protobuf 訊息以觸發網路上的崩潰。