尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Protobuf 从零到实战:Linux 环境编译安装与版本避坑指南

Protobuf 从零到实战:Linux 环境编译安装与版本避坑指南 1. Protobuf是什么它解决了什么问题为什么值得花时间学如果你常年跟Linux服务器、后端服务或者微服务打交道大概率对ProtobufProtocol Buffers这个名字不陌生。它是Google开源的跨语言结构化数据序列化方案简单说就是把内存里的对象变成一段紧凑的二进制流方便在网络里传、在磁盘上存另一端拿到二进制流之后再按约定好的规则还原成对象。这中间的过程业界叫它序列化和反序列化。相较大家更熟悉的JSON和XMLProtobuf最直观的优势是瘦。同样的数据JSON要带一堆花括号、冒号、引号而Protobuf按照二进制格式落盘字段用编号区分而不是名字体积经常能压缩到JSON的一半以下解析速度更是成倍数地提升。在高并发、高吞吐的场景下这个差距对带宽和CPU开销的影响非常明显。另外Protobuf自带的编译工具protoc可以直接生成Python、C、Java、Go等多语言代码只要大家共用一个.proto协议文件不同语言之间就能无障碍通信。grpc、很多内部RPC框架、大数据组件底层都跑在Protobuf上可以说它是后端链路里的基础设施之一。这篇内容适合谁两种人。一种是刚接触Linux开发项目里要用Protobuf但不知道从哪里下手的新手可以按步骤把环境装好、跑通一个最小例子。另一种是已经在用Protobuf、但切换机器或重新部署时总在环境上踩坑的开发者这里会带你深入理解编译安装的链路并提供一份可以直接抄的排查清单。整个过程我会用自己实际编译安装时的真实操作顺序来讲附带版本选择的思路和踩过的坑。2. 安装前的环境盘点搞清需求再动手能省不少冤枉时间2.1 你真正需要装的是什么很多人第一次装Protobuf会有点懵因为一搜教程有的让你apt install libprotobuf-dev有的让你去GitHub下载源码编译还有的直接pip install protobuf。这里先理顺一个概念完整的一套Protobuf环境其实包含三部分。protoc编译器读取.proto协议文件生成对应语言的代码。对应语言的运行时库比如C的libprotobufPython的protobuf包Java的protobuf-java编译生成的代码在运行时会依赖这套库。对应语言自己的插件或支持比如grpc需要grpc_python_plugin之类的东西如果你只是做序列化暂时用不到。我只做基础的序列化和反序列化那protoc加上某个语言的运行时库就够了。如果未来要上grpc那就得额外装grpc的编译插件和运行时这里先不提。另外要明确一点protoc的版本和运行时库的版本必须保持大版本一致。比如你用protoc 3.21.x生成的代码最好也用libprotobuf 3.21.x去编译运行如果混用3.x和25.x这种跨越了大版本的组合轻则编译期报类型对不上重则运行期直接core dump。这是个很容易被忽略的点后面我会专门展开。2.2 三种安装路径怎么选Linux下装Protobuf常见的做法有三条各有取舍。第一种直接用系统包管理器装。apt install -y protobuf-compiler libprotobuf-dev一行搞定快是真快。但问题也明显软件源里的版本通常比较老比如Ubuntu 20.04源里的protoc还是3.6.x而很多项目已经依赖3.20甚至4.x的语法特性装旧版本很容易在编译别人工程时莫名其妙的报missing field、生成代码和运行库不匹配。第二种使用Python的pip install protobuf只解决Python运行时库的问题并不带protoc编译器。所以纯Python用户还得另外装编译器或者用grpc_tools.protoc代替。这里的坑是如果你执行了pip install protobuf它会按照自己的最新版本装而你系统里C运行时库如果比较旧两边版本就岔开了。第三种下载官方源码手动编译安装。这是兼容性最好、可控性最强的方式也是这篇教程的重点。虽然过程多几步但编译一次系统里就有了protoc加上标准C运行时库之后无论给Python、Go还是其他语言用都打好了底子。对那些要部署到多台机器的场景我甚至建议把编译好的二进制打包分发省得每台机器都重复编译。综合下来我的建议是正式开发环境、生产环境、或者需要跟grpc配合的项目老老实实走源码编译。临时试一下、跑个小demo系统源里装一下也无妨但别把它当成长期环境。2.3 编译前必须查的几样东西动手编译前先把基础工具理一遍。Protobuf是C写的依赖g、make、autoconf、automake、libtool这些常规工具链。多数Linux发行版自带但如果是精简版、容器镜像很可能缺。建议按下面顺序检查# 检查g g --version # 检查make版本别太老 make --version # 检查autoconf系工具 autoconf --version automake --version libtool --version # 缺什么就装什么Debian/Ubuntu系 sudo apt update sudo apt install -y build-essential autoconf automake libtool # CentOS/RHEL系 sudo yum groupinstall -y Development Tools sudo yum install -y autoconf automake libtool这里有个经验之谈最好不要在有旧版protoc的机器上直接覆盖编译安装容易造成protoc --version显示的版本和ls /usr/bin/protoc对不上号。旧版如果用apt装过可以先卸掉sudo apt remove -y protobuf-compiler libprotobuf-dev如果之前已经手动编译装到了/usr/local建议先跑一下which protoc确认路径避免后面装完却调用了旧版本。3. 完整编译安装流程实录从源码到protoc可用的全步骤3.1 下载源码版本怎么选官方源码托管在GitHub的protocolbuffers/protobuf仓库。选版本这事不同类型项目有不同讲究。目前Protobuf的主版本有v3.x和v4.xv4.x主要对应google.protobuf内部的演进但社区习惯上还是叫它3.2x或4.2x。我的建议是如果你想省心选一个比较新的稳定tag比如v3.21.12或更新的v26.x系列。但要注意一个细节v21.0之后官方对C源码结构做了一次大调整autogen.sh和configure文件的生成方式有了变化有些老教程里的步骤已经不完全适用。所以别盲目抄老命令以当前版本的官方README为准。实操中我一般选择v3.21.12原因有三一是它正好是Android Gradle插件等一大批中间件依赖过的版本兼容性验证得比较多二是它的编译方式和老教程差异不大网上资料多出问题好搜索三是它对C11的支持已经非常成熟老编译器也不挑。# 比如我把所有源码放在 /opt/source 下 mkdir -p /opt/source cd /opt/source # 下载指定tag的源码包注意tag名必须写全 wget https://github.com/protocolbuffers/protobuf/releases/download/v3.21.12/protobuf-cpp-3.21.12.tar.gz # 解压并进入目录 tar -zxvf protobuf-cpp-3.21.12.tar.gz cd protobuf-3.21.12如果你是离线环境或者GitHub下载慢可以试试用国内的镜像加速站但一定要校验下载文件的完整性。protobuf-cpp-*.tar.gz这种包本身自带configure文件不需要执行autogen.sh如果你下载的是GitHub自动生成的源码zip包那种才需要先跑autogen.sh生成configure。3.2 configure、make、install一步步来进入源码目录后标准的安装三部曲是configure、make、make install。不过每步都有值得讲究的细节。先配置安装路径# 推荐直接装到 /usr/local这也是官方默认路径 ./configure --prefix/usr/local # 当然你想装到自定义目录也行 # ./configure --prefix/opt/protobuf我不太建议把prefix改到太偏的路径除非你能确保后续编译其他项目时PKG_CONFIG_PATH和LD_LIBRARY_PATH都指向这个目录。装到/usr/local的好处是绝大多数Linux发行版默认就会搜索/usr/local/lib下的动态库不用额外设环境变量。接着编译这一步最熬人# 先看CPU核数 nproc # 比如8核就make -j8别傻乎乎的make单线程等半天 make -j8看CPU核数再决定并行编译数量这个习惯很重要。make -j$(nproc)直接用所有核机器内存不够的话会编译到一半被杀进程。如果你在2G内存的小机器上建议make -j2慢点也能接受。编译过程中如果出现报错不要慌着搜compiler error多数时候是缺少依赖或者g版本太低。比如error: uint64_t does not name a type这种就是老编译器对C11标准支持不完整升级g基本能解决。编译没问题就安装sudo make install装完之后刷新动态库缓存这步很容易被漏掉漏了的后果是protoc命令能跑但一执行就报error while loading shared libraries: libprotobuf.so.23: cannot open shared object file。# 刷新动态库缓存 sudo ldconfig # 确认版本 protoc --version如果protoc --version输出的是libprotobuf 3.21.12说明编译器装好了。这时候C运行时库也一并装到了/usr/local/libls /usr/local/lib | grep protobuf能看到一堆libprotobuf.so*文件。3.3 装完后的环境变量配置多数情况装到/usr/local不需要额外配置但有几类特殊情况你还是得手动设置环境变量。如果你的Linux发行版比较特别/usr/local/lib不在默认动态库搜索路径里那就需要往/etc/ld.so.conf.d/里加一个文件比如/etc/ld.so.conf.d/protobuf.conf内容写/usr/local/lib保存后执行sudo ldconfig。如果你装了多个版本的Protobuf想临时切换时可以通过LD_LIBRARY_PATH指定优先用哪个库但这是临时方案别写进全局配置里不然会污染其他程序。如果你是给特定用户安装没sudo权限./configure --prefix$HOME/protobuf之后必须在自己用户的~/.bashrc里加上export PATH$HOME/protobuf/bin:$PATH export LD_LIBRARY_PATH$HOME/protobuf/lib:$LD_LIBRARY_PATH export PKG_CONFIG_PATH$HOME/protobuf/lib/pkgconfig:$PKG_CONFIG_PATHPKG_CONFIG_PATH可能很多人没接触过。它是给pkg-config工具用的很多C项目编译时通过pkg-config --cflags --libs protobuf找头文件和库路径。装到非标准路径时必须配置否则第三方工程会发现不了Protobuf。4. 不只是CPython环境下的Protobuf集成方案4.1 pip安装与源码编译的配合前面说过protoc和运行时库是两回事。在C环境装好之后你已经有了protoc编译器但Python这边通常还要装对应的Python运行时依赖。最常用的是protobuf这个包# 推荐使用虚拟环境 python3 -m venv myenv source myenv/bin/activate # 安装运行时库 pip install protobuf装完之后用Python的protoc生成代码时还是调用刚才编译好的protoc命令而不是Python里的什么接口。例如protoc --python_out./ person.proto它会在当前目录生成一个person_pb2.py这个文件里已经呈现代码逻辑写程序时直接import person_pb2即可。如果Python包的版本和protoc版本差得太多person_pb2.py加载时会提示RuntimeError: Generated code is too old for this runtime反过来是Generated code is too new。所以最好把两个版本对齐pip install protobuf3.21.12是比较保险的做法。4.2 用 grpc_tools 当编译器省一步如果你只是想在Python环境里舒服地使用Protobuf不想为C编译的事劳心有一种替代方案直接用grpcio-tools里附带的protoc。pip install grpcio-tools # 用它生成python代码 python3 -m grpc_tools.protoc -I./ --python_out. --grpc_python_out. person.proto这个方案对纯Python用户更轻量。但要注意它只是把protoc相关的Python封装打包了底层还是会发出一个C扩展的调用。它的好处是版本跟随pip管理不用自己处理/usr/local下的文件冲突。缺点也有如果你需要同时生成C代码它做不到还是得用系统的protoc。我个人的习惯是如果整个项目是多语言体系——比如C写核心服务、Python写工具脚本——就统一用源码编译的protoc所有语言都从同一个版本的编译器生成代码避免两个入口带出版本分叉的问题。4.3 运行时库与编译器版本匹配的快检方法多语言混用场景下排查版本不匹配有几个很快的土方法。先看protoc的版本protoc --version再看Python运行时库的版本python3 -c import google.protobuf; print(google.protobuf.__version__)如果protoc是3.21.12Python那边也应该是3.21.x。一旦出现RuntimeError直接重装匹配版本pip install protobuf3.21.12至于C运行时库的版本可以用一个小程序查也可以更粗暴地直接用strings去看动态库里的版本字符串strings /usr/local/lib/libprotobuf.so.23 | grep 3.21实际项目中多数诡异的“序列化结果对不上”“生成的代码编译不过”最后都能追溯到版本错配这个检查值得养成习惯。5. 手写一个最小例子从.proto文件到编译运行的全流程5.1 编写协议文件做了一次完整安装下一步当然要立刻验证它能不能跑通。我建议别直接去啃复杂项目先写个最小例子把整条链路打通。建立一个工作目录比如~/proto_demo在里面新建person.protosyntax proto3; package demo; message Person { string name 1; int32 id 2; string email 3; }简单说明几个关键点。第一行proto3是语法版本别漏否则protoc默认按proto2处理字段修饰符写法完全不同。package demo相当于命名空间生成的C类会在demo命名空间里Python的_pb2模块也能通过它避免名字冲突。字段赋值规则是“字段名 编号”编号一旦确定最好不要改动它直接决定二进制流里数据的布局改坏编号老数据就解不开了。5.2 编译.proto生成C代码执行cd ~/proto_demo protoc --cpp_out./ person.proto如果不出意外目录里会多出person.pb.h和person.pb.cc两个文件。这里多说一句--cpp_out生成的是C的代码--python_out生成的是Python代码你可以一条命令同时输出多份protoc --cpp_out./ --python_out./ person.proto这在实际项目里非常常用一份协议后端用C处理数据脚本用Python做分析两边代码由同一个proto驱动天然保持同步。5.3 写一个序列化/反序列化的C示例为了验证环境确实可用写个简单的C程序把消息塞到二进制流里再解析回来。新建main.cpp#include iostream #include string #include person.pb.h int main() { // 序列化 demo::Person person; person.set_name(张三); person.set_id(42); person.set_email(zhangsanexample.com); std::string data; bool ok person.SerializeToString(data); if (!ok) { std::cerr serialize failed std::endl; return -1; } std::cout serialized size: data.size() bytes std::endl; // 反序列化 demo::Person parsed; if (!parsed.ParseFromString(data)) { std::cerr parse failed std::endl; return -1; } std::cout name: parsed.name() std::endl; std::cout id: parsed.id() std::endl; std::cout email: parsed.email() std::endl; return 0; }编译命令需要注意生成的person.pb.cc要一起参与编译链接时加-lprotobuf。如果你是直接编译、用系统自带的旧版libprotobuf或者新版装到了非标准路径链接阶段会报找不到-lprotobuf的错。g -stdc11 main.cpp person.pb.cc -lprotobuf -o demo执行./demo正常输出serialized size: 34 bytes name: 张三 id: 42 email: zhangsanexample.com看到这个结果这一整套Linux下的Protobuf环境就算真的通了。这34个字节如果用JSON表示至少五六十个字节差距一目了然。5.4 用CMake接管编译更贴合真实工程命令行编译演示没问题但真实工程一般都用CMake组织这里也把流程列一下方便你直接抄。新建CMakeLists.txtcmake_minimum_required(VERSION 3.10) project(proto_demo) set(CMAKE_CXX_STANDARD 11) # 找Protobuf库 find_package(Protobuf REQUIRED) # 用protoc自动生成代码 protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS person.proto) add_executable(demo main.cpp ${PROTO_SRCS} ${PROTO_HDRS}) target_include_directories(demo PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${Protobuf_INCLUDE_DIRS}) target_link_libraries(demo PRIVATE ${Protobuf_LIBRARIES})然后mkdir -p build cd build cmake .. make ./demoprotobuf_generate_cpp这个函数会帮你自动调用protoc这是官方提供的CMake模块比手动拼接命令优雅得多。而且编译生成的person.pb.cc文件位于build目录target_include_directories里必须带上${CMAKE_CURRENT_BINARY_DIR}否则头文件路径找不到。6. 安装与使用中的高频坑点与排查实录6.1protoc找不到共享库症状protoc --version能执行但报错protoc: error while loading shared libraries: libprotobuf.so.23: cannot open shared object file: No such file or directory原因几乎都是动态库搜索路径里没有/usr/local/lib或者ldconfig没刷新。先看库装在哪ls /usr/local/lib/libprotobuf*确认缓存里有没有ldconfig -p | grep protobuf没有就刷新sudo ldconfig刷新还不行检查/etc/ld.so.conf.d/下有没有包含/usr/local/lib的配置我之前在一台精简架构的服务器上遇到过类似问题原因是那台机器的ld.so.conf里压根不搜索/usr/local/lib解决办法就是新建一个protobuf.conf文件写进路径再刷新。6.2 版本错配导致生成代码编译失败症状用新protoc生成的.pb.h链接旧版libprotobuf编译期可能报出一堆“未定义成员”“类型不匹配”。这类问题的特征是代码是你写的错误却出在生成的.pb.cc里第一反应可能怀疑生成器有问题。其实根因就是版本跨度太大。比如protoc 25.x生成的代码要求libprotobuf运行库至少25.x系统里如果只有3.6接口自然对不上。排查方式把protoc --version和pkg-config --modversion protobuf对比如果不一致要么升级运行库要么降级protoc。项目里如果存在多个目录分别装了不同版本检查which protoc和protoc --version的实际路径使用type -a protoc查看有没有被alias或PATH覆盖。6.3make编译到一半被kill症状大工程编译到一半突然Killed进程消失。原因基本是内存不足。Protobuf的C代码量不小并行编译对内存有要求。对策很简单控制并行度make -j2甚至make -j1临时加交换分区比如用fallocate -l 4G /swapfile硬扩一个swap出来如果实在编译不过去就下载官方release包里的预编译protoc二进制不过需要注意预编译包一般只有protoc不含C运行时库C编译得靠系统包或自己装一遍libprotobuf6.4 多版本Protobuf冲突在一台机器上可能因为历史原因同时存在/usr/bin/protoc老版本和/usr/local/bin/protoc新版本。PATH的先后顺序决定了你调用的是哪个。踩过几次坑之后我的习惯是旧版全部卸载干净只保留一个手动编译的版本装在/usr/local在.bashrc里顶上加一行export PATH/usr/local/bin:$PATH确保优先如果你实在需要多版本共存建议借助容器或者chroot隔离环境不要在同一台机器上堆多个版本的文件时间长了绝对会出幺蛾子。6.5 一个版本排查速查表整理一份速查方便你以后遇到问题对号入座症状可能原因快速排查解决思路执行protoc报找不到共享库动态库路径未配置ldd $(which protoc)刷新ldconfig或配置LD_LIBRARY_PATH编译生成的代码报类型不匹配protoc与libprotobuf版本不一致protoc --version与pkg-config --modversion protobuf对比统一版本重装运行时库Python加载pb2报RuntimeError运行时protobuf包与protoc版本不匹配python3 -c import google.protobuf; print(google.protobuf.__version__)pip install protobuf对应版本make被Killed内存不足free -h查看可用内存降低并行度或增加swap7. 更进一步的实践建议如果你的机器上以后要搞grpc、或者要做跨语言多服务通信有几点可以提前安排上第一建议把.proto文件的目录结构设计好。项目一大协议文件会很分散。常见的做法是单独建一个proto/目录把不同模块的proto分目录存放编译脚本里统一用-I指定多个搜索路径。这样既能避免同名文件冲突也方便做版本管理。第二认真对待字段编号。编号不只是顺序号它是线上数据格式的一部分。字段编号一旦发布到线上基本就不能改了。新增字段时用新的编号废弃字段时reserved掉不要复用旧编号。这个规矩不遵守长期迭代之后新旧实例之间数据解析就会错乱。第三构建脚本里固化版本。我现在每到一个新环境第一件事就是把Protobuf的版本写进构建说明最好还用脚本自动下载指定版本的包而不是依赖系统源里的版本。这样无论换哪台机器都能保证编译器与运行库版本一致省掉很多莫名其妙的联动问题。第四如果项目里同时用C和Python尽量让两边共用同一个protoc生成代码。你可以把生成代码的脚本统一维护CI里自动跑避免手工执行导致两边文件不同步。跨语言项目最怕的就是协议修改了某个语言侧生成代码没更新线上通信一脸懵。第五对性能敏感的服务来说序列化时的内存分配也值得关注。SerializeToString、ParseFromString这种接口简单好用但在高频调用下会有不少内存分配开销。更极致的手段是复用Message对象或者使用SerializeToArray将数据直接塞进预分配的内存块。这些属于后续优化的话题但值得在架构设计时留个心。8. 写在最后一些真正想提醒的话我编译安装Protobuf的次数可能比大多数教程作者都多毕竟每次换工作电脑、每次搭新的服务器、每次帮同事救环境都要重来一遍。回顾这些经历最深的体会是Protobuf安装本身不复杂复杂的是版本控制和环境隔离。如果你想在这个领域少踩坑记住这几句话就够了源码编译安装最稳版本锁定最关键。多语言项目务必统一protoc版本运行时库版本要和编译器保持一致。疑难杂症先怀疑动态库路径和版本再怀疑代码。把安装步骤和版本写进文档、脚本化让未来的自己和同事都能省心。最后再分享一个小技巧我第一次在服务器上装完Protobuf后习惯性地执行了一个小验证脚本直接生成代码、编译、跑demo直到输出serialized size才确认环境合格。后来每次给新机器装完我都会把这个验证动作保留在脚本里。宁可多花两分钟也不要装机装到深夜才发现某个库连不上。这大概是所有环境反复折腾之后最容易总结出来的实用经验。
返回列表