Skip to content

Conversation

@Q1ngbo
Copy link
Contributor

@Q1ngbo Q1ngbo commented Jan 15, 2026

What problem does this PR solve?

Issue Number: resolve #2354 #978

Problem Summary:
Hi, 我基于先前的commit #3062 完成了flatbuffers协议对brpc的适配,准备将完整实现提交至社区。在brpc中增加flatbuffers(fb)支持可分为fb message构造与fb协议处理两部分。前者解决的问题是如何使用flatbuffers库提供的接口去创建一个message,以及应用接收到message后如何从中读取出数据;后者解决的是brpc如何处理"fb"协议。因此,我将相关实现拆分为了两个提交。本提交重点在于message构造。

我之所以将message构造提交至brpc仓库中而不是像grpc一样在google/flatbuffers中实现,主要出于两点考虑:首先,为了与IOBuf兼容,创建message使用的底层数据结构为之前已经提交的SingleIOBuf,在flatbuffers中使用该数据结构会导致循环依赖;其次,flatbuffers提供了完善的接口,只需要在brpc里定义好内存分配器,即可调用相关接口来构造消息,实现上很方便。

特别说明:

  1. 本次提交主要是为了征求各位评审老师的意见,暂未添加单测。
  2. 对于google/flatbuffers的修改需要与brpc中部分实现保持一致,所以针对flatbuffers仓库的修改暂未提交。
  3. 为了便于大家理解,我在example中增加了示例程序bechmark_fb,后续正式入库前将移除它,该目录下的test_generated.h, test.brpc.fb.h与test.brpc.fb.cpp正式由flatbuffers的编译工具flatc基于test.fbs自动生成的。基于该程序,在我们的测试环境下,flatbuffers协议相较于"baidu_std"协议在32~8KB大小的消息上QPS有20%+的提升。

Check List:

Key components:
- flatbuffers_common.h: Defines abstract interfaces (RpcChannel and Service)
  for FlatBuffers-based RPC communication, similar to protobuf's RPC interfaces.
- flatbuffers_impl.h: Implements FlatBuffers-specific message handling:
  * SlabAllocator: Custom allocator using SingleIOBuf for zero-copy operations.
  * Message: Wrapper for FlatBuffers messages with SingleIOBuf storage.
  * MessageBuilder: BRPC-specific FlatBufferBuilder with SlabAllocator.
  * ServiceDescriptor/MethodDescriptor: Service introspection support.
- flatbuffers_impl.cpp: Implementation of allocation, serialization, and
  service descriptor initialization logic
@Q1ngbo
Copy link
Contributor Author

Q1ngbo commented Jan 15, 2026

为了便于理解,我想对flatbuffers构造消息的基本流程进行简要补充说明,该示例程序的Scheme文件(类似于.proto文件)如下。

table EchoRequest {
    opcode:int;
    attachment:int;
}

Message需要通过MessageBuilder创建(定义在flatbuffers_impl.h中),该类继承了::flatbuffers::FlatBufferBuilder,它才是实际起作用的类,MessageBuilder的主要作用是向它传递自定义的内存分配器(SlabAllocator)。应用在创建message时,需要先定义一个MessageBuilder,然后基于它来添加元素,最终调用ReleaseMessage方法来获取完整消息,以下是一段示例代码。

brpc::flatbuffers::MessageBuilder mb;
auto message = mb.CreateString(g_request);
auto req = test::CreateBenchmarkRequest(mb, 123, 333, 1111, 2222, 0, message);
mb.Finish(req);
brpc::flatbuffers::Message request = mb.ReleaseMessage();

在构造message过程中,flatbuffers会使用一段连续buffer存储数据和metadata,在代码里,该buffer使用vector_downward表示,由FlatBufferBuilder管理。该Buffer维护了scratch_和cur_两个指针。在初次向buffer中添加数据时,会申请max(需要长度,1024)字节的内存,之后会使用cur_将数据添加在buffer末尾,将偏移量信息(FieldLoc)添加在buffer起始位置,如下图所示。如果在添加过程中buffer不够了,则会重新分配一个足够大的buffer,并将数据按序拷贝过去,所以或许可以配置一个GFlag来调整初次分配buffer的大小(TODO)。
image

brpc只需要定义好内存分配器SlabAllocator,在添加元素的过程中,当内存不足时flatbuffers会调用allocate或reallocate_downward,这实际上会调用SingleIOBuf中的相关方法。我们发现将rpc请求的header部分与message连续存储可获得明显性能提升。因此,在申请buffer时我们在Buffer前面预留出了一部分空间,其大小由宏DEFAULT_RESERVE_SIZE指定,默认为64B(也可通过GFlag配置,TODO)。这部分内存的消费者是brpc中的协议处理逻辑,在创建message过程中不会被使用到。
在完成数据添加后,应用需调用Finish方法完成创建,这一步flatbuffers会创建vtable,其主要执行逻辑为:1. 根据data大小为vtable申请空间;2. 从buffer起始处取出FieldLoc,将实际数据相对于"Table Data"起始地址的偏移量写入到vtable中;3. 在table data部分开头记录该位置相对于vtable开头的偏移量;4. 填充vtable中的vtable总长度、data总长度;5. 将"Table Data"相对于有效起始地址的偏移写入开头。vtable不是从buffer起始地址、而是从cur_左侧构建的,所以vtable和table data仍然是连续的,vtable左侧多余空间会被释放掉。以上面提到的EchoRequest为例,最终创建的完整内存布局如下所示(图中没画出为header部分预留的空间):
image
应用调用ReleaseMessage所获取到的Message中包含了该连续buffer对应的SingleIOBuf,该函数的执行逻辑为:1. 从vector_downward(buffer)中获取前面构造的msg地址;2. 从SingleIOBuf中获取分配该buffer所使用的block起始地址;3. 计算msg在block中的偏移,基于它创建一个BlockRef;4. 通过BlockRef创建SingleIOBuf,它会作为Message的一个成员变量。至此,创建message的过程就结束了。

flatbuffers读取消息的本质是进行间接寻址。参考上图,当读取EchoRequest中的opcode时,首先从buffer起始地址出读取Root Offset,获取Table Data相对于buffer起始地址的偏移量;然后从Table Data起始处获取Vtable的相对偏移量,再次跳转;然后从预先生成的FlatBuffersVTableOffset中获取opcode在vtable中的位置,访问它获取实际偏移量;当字段合法时,通过(Table Data + 偏移量)获取到实际值。该过程需要3次间接寻址。

message:string;
}

rpc_service BenchmarkService {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flatc 应该是不支持 rpc_service 的,请问这里是怎么处理的?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flatc 应该是不支持 rpc_service 的,请问这里是怎么处理的?

类似于为grpc实现的--grpc参数,可以给flatc增加了--brpc参数,来将其编译为brpc可用的service。如example/benchmark_fb/test.brpc.fb.h中的class BenchmarkService。但我尚未向flatbuffers提交pr,因为还可能需要根据brpc中实现进行相应修改。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

brpc只能使用protobuf吗?如果我想使用flatbuffer,能支持吗

2 participants