Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go快速上手Protocol Buffers

用Go快速上手Protocol Buffers详解

作者:Hello.Reader

Protobuf是一种高效的跨语言序列化协议,适用于Go语言,本文介绍了如何安装和使用Protobuf,包括定义协议、生成Go代码、序列化和反序列化,以及版本演进和兼容性,通过一个完整的示例,展示了如何在Go项目中使用Protobuf

一、为什么选 Protobuf(而不是 XML / 自定义格式 / gob)

gob 在纯 Go 环境很香,但跨栈共享数据就不如 Protobuf 了;XML 可读性好但“又大又慢”;自定义字符串编码维护成本高。

二、准备环境

1.安装 protoc(编译器)

按平台安装好 Protocol Buffers Compiler。

2.安装 Go 生成插件

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

确保 $GOBIN(默认 $GOPATH/bin)在 $PATH 中,这样 protoc 才能找到 protoc-gen-go

三、定义协议:addressbook.proto

syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";

// 生成代码的 import 路径;Go 包名取最后一段(这里是 tutorialpb)
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";

message Person {
  string name = 1;
  int32  id   = 2;  // 唯一 ID
  string email = 3;

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
  google.protobuf.Timestamp last_updated = 5;
}

enum PhoneType {
  PHONE_TYPE_UNSPECIFIED = 0;
  PHONE_TYPE_MOBILE = 1;
  PHONE_TYPE_HOME = 2;
  PHONE_TYPE_WORK = 3;
}

message AddressBook {
  repeated Person people = 1;
}

要点速记:

四、生成 Go 代码

protoc \
  -I=$SRC_DIR \
  --go_out=$DST_DIR \
  $SRC_DIR/addressbook.proto

生成:.../tutorialpb/addressbook.pb.go

这一文件内含以下类型/成员(节选):

五、构造与使用:像普通 Go 结构体一样

import pb "github.com/protocolbuffers/protobuf/examples/go/tutorialpb"

p := pb.Person{
  Id:    1234,
  Name:  "John Doe",
  Email: "jdoe@example.com",
  Phones: []*pb.Person_PhoneNumber{
    {Number: "555-4321", Type: pb.PhoneType_PHONE_TYPE_HOME},
  },
}

六、序列化与反序列化

(1)写入:proto.Marshal

import (
  "io/ioutil"
  "google.golang.org/protobuf/proto"
)

book := &pb.AddressBook{People: []*pb.Person{&p}}

out, err := proto.Marshal(book)
if err != nil { log.Fatalln("encode error:", err) }

if err := ioutil.WriteFile("book.bin", out, 0644); err != nil {
  log.Fatalln("write error:", err)
}

(2)读取:proto.Unmarshal

in, err := ioutil.ReadFile("book.bin")
if err != nil { log.Fatalln("read error:", err) }

book2 := &pb.AddressBook{}
if err := proto.Unmarshal(in, book2); err != nil {
  log.Fatalln("parse error:", err)
}

备注:Go 的 protojson 可做 JSON 编解码,但这不在本入门最小闭环中。

七、版本演进与兼容性(必须牢记的三条)

  1. 绝不要修改已有字段的 tag 编号
  2. 可以删除 字段。
  3. 可以新增 字段,但必须使用从未使用过的 tag(包含已删除过的也不能复用)。

遵守后:

八、项目组织与构建小贴士

模块路径go_package 建议与实际仓库路径一致,避免 import 冲突。

目录布局:把 .proto 放在 proto/,生成物放在 pkg/ 或与业务分离的模块中,易于升级。

版本固定:在 go.mod 固定 google.golang.org/protobuf 版本,避免 CI/CD 环境差异。

常见错误

标签号规划:把 1–15 留给高频/repeated;给未来预留区间,写注释记录使用情况。

测试:为序列化/反序列化写回归测试,尤其是演进前后字节兼容性(可用“旧版本字节样本”作为 fixture)。

九、完整最小示例

创建 addressbook.proto → 生成 addressbook.pb.go → 读写:

package main

import (
  "io/ioutil"
  "log"

  pb "github.com/protocolbuffers/protobuf/examples/go/tutorialpb"
  "google.golang.org/protobuf/proto"
)

func main() {
  // 构造
  p := &pb.Person{
    Id:    1,
    Name:  "Ada",
    Email: "ada@example.com",
    Phones: []*pb.Person_PhoneNumber{
      {Number: "123456", Type: pb.PhoneType_PHONE_TYPE_MOBILE},
    },
  }
  book := &pb.AddressBook{People: []*pb.Person{p}}

  // 写
  data, err := proto.Marshal(book)
  if err != nil { log.Fatal(err) }
  if err := ioutil.WriteFile("book.bin", data, 0644); err != nil { log.Fatal(err) }

  // 读
  raw, err := ioutil.ReadFile("book.bin")
  if err != nil { log.Fatal(err) }
  var got pb.AddressBook
  if err := proto.Unmarshal(raw, &got); err != nil { log.Fatal(err) }

  log.Printf("people: %v", got.People[0].Name)
}

十、总结

到这里,你已经掌握了 Go + Protobuf 的核心闭环:定义 → 生成 → 读写 → 可演进

.proto 当作跨团队、跨语言的稳定契约,你会在服务通信、数据持久化、跨栈协作中获得高性能与低心智负担

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文