深入解析 yyjson:Fluent Bit 内置的高性能 C 语言 JSON 解析库
深入解析 yyjsonFluent Bit 内置的高性能 C 语言 JSON 解析库【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit导读yyjson 是一个用 ANSI CC89编写的高性能 JSON 解析与序列化库以单头单源文件yyjson.hyyjson.c的形式提供在现代 CPU 上可以每秒读写数 GB 的 JSON 数据。本仓库Fluent Bit将其作为核心 JSON 后端内置位于 lib/yyjson-0.12.0/并默认启用CMake 选项FLB_YYJSON默认为 ON用于解析采集到的 JSON 日志、格式化输出与内部 msgpack 转换等关键路径。读完本文你将掌握 yyjson 的读写 API、配置选项、性能特征以及它在 Fluent Bit 数据管道中的真实工作方式。一、yyjson 是什么一个面向性能的 ANSI C JSON 库yyjson 的作者是 ibireme定位是a high performance JSON library written in ANSI C。它围绕速度、可移植性与标准合规做了针对性设计其核心特性可以概括为七点特性说明Fast在现代 CPU 上每秒可读取或写入数 GB 的 JSON 数据Portable严格遵循 ANSI CC89跨平台兼容性好Strict遵循 RFC 8259 JSON 标准严格校验数字格式与 UTF-8Extendable支持按需开启各项 JSON5 特性支持自定义内存分配器Accuracy可精确读写int64、uint64、double数值Flexible支持无限层级嵌套、\u0000字符、非 null 结尾字符串Manipulation支持 JSON Pointer、JSON Patch、JSON Merge Patch 查询与修改Developer-Friendly仅需一个.h和一个.c文件即可集成在设计取舍上yyjson 也明确声明了三条限制README 的 Limitations 章节数组/对象以链表等数据结构存储因此按下标或按 key 直接访问元素比使用迭代器慢对象允许重复 key且 key 的顺序会被保留解析结果是不可变的immutable若要修改必须先做一次mutable copy。这些限制决定了 yyjson 的典型用法解析 - 遍历/查询 - 需要修改时复制为可变文档 - 序列化写出。Fluent Bit 的 JSON 处理正是遵循了这一模式。二、性能画像官方基准与适用前提yyjson 的性能在 README 中给出了两组官方基准benchmark 项目为yyjson_benchmark数据集为twitter.json只测 DOM APIAWS EC2AMD EPYC 7R32, gcc 9.3twitter.jsonparse (GB/s)stringify (GB/s)yyjson(insitu)1.801.51yyjson1.721.42simdjson1.520.61sajson1.16rapidjson(insitu)0.77rapidjson(utf8)0.260.39cjson0.320.17jansson0.050.11iPhoneApple A14, clang 12twitter.jsonparse (GB/s)stringify (GB/s)yyjson(insitu)3.512.41yyjson2.392.01simdjson2.190.80sajson1.74rapidjson(insitu)0.75rapidjson(utf8)0.300.58cjson0.480.33jansson0.090.24从数据可以得出几个客观结论在 DOM API 场景下yyjson 的解析和序列化吞吐都处于第一梯队其中insitu原地解析模式更快simdjson 的On DemandAPI 在字段于编译期已知的场景下更快README 也明确指出该基准只覆盖 DOM API后续还会补充新基准同一份数据在不同平台/编译器下的绝对数值差异很大如 EC2 上 parse 约 1.7 GB/sA14 上约 2.4~3.5 GB/s因此不要跨平台直接对比绝对数字。性能发挥的硬件/编译器前提README 说明要让 yyjson 发挥最佳性能最好具备现代处理器具备较高的指令级并行度、优秀的分支预测器、对非对齐内存访问的低惩罚现代编译器如 clang以及良好的优化器。这一点在 Fluent Bit 的构建脚本中也有呼应CMakeLists.txt中针对 yyjson 单独设置了编译选项——由于 yyjsons O0 reader can exceed the default coroutine stack budgetyyjson 的 O0 读取器可能超出默认协程栈预算在 Debug/RelWithDebInfo 配置下会用-Og强制足够优化来缩小栈帧同时保留调试符号见 CMakeLists.txt。这说明在真实生产级项目里yyjson 的栈使用量与优化级别是需要被认真对待的工程细节。三、Sample Code 精讲读取 JSONREADME 给出了四个完整的示例代码块这是本文必须完整继承的实操核心。先看读取字符串const char *json {\name\:\Mash\,\star\:4,\hits\:[2,2,1,3]}; // Read JSON and get root yyjson_doc *doc yyjson_read(json, strlen(json), 0); yyjson_val *root yyjson_doc_get_root(doc); // Get root[name] yyjson_val *name yyjson_obj_get(root, name); printf(name: %s\n, yyjson_get_str(name)); printf(name length:%d\n, (int)yyjson_get_len(name)); // Get root[star] yyjson_val *star yyjson_obj_get(root, star); printf(star: %d\n, (int)yyjson_get_int(star)); // Get root[hits], iterate over the array yyjson_val *hits yyjson_obj_get(root, hits); size_t idx, max; yyjson_val *hit; yyjson_arr_foreach(hits, idx, max, hit) { printf(hit%d: %d\n, (int)idx, (int)yyjson_get_int(hit)); } // Free the doc yyjson_doc_free(doc); // All functions accept NULL input, and return NULL on error.这段代码展示了 yyjson 最核心的读取链路解析yyjson_read(json, strlen(json), 0)一次性完成解析并返回yyjson_doc *第三个参数是读取标志0表示严格标准模式取根yyjson_doc_get_root(doc)获取根节点yyjson_val *按键查询yyjson_obj_get(root, name)从对象中按 key 取值取值yyjson_get_str/yyjson_get_len/yyjson_get_int系列 API 按类型取出标量值数组遍历宏yyjson_arr_foreach(hits, idx, max, hit)以迭代器方式遍历数组元素释放yyjson_doc_free(doc)归还整个文档。需要注意 README 的提示所有函数都接受 NULL 输入并在出错时返回 NULL这大大简化了调用方的空指针防护逻辑。支持读取更多 JSON 5 特性与自定义分配器除了默认模式yyjson 还提供yyjson_read_opts来带选项解析。README 的第三个示例演示了读取文件并允许注释与尾随逗号// Read JSON file, allowing comments and trailing commas yyjson_read_flag flg YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS; yyjson_read_err err; yyjson_doc *doc yyjson_read_file(/tmp/config.json, flg, NULL, err); // Iterate over the root object if (doc) { yyjson_val *obj yyjson_doc_get_root(doc); yyjson_obj_iter iter; yyjson_obj_iter_init(obj, iter); yyjson_val *key, *val; while ((key yyjson_obj_iter_next(iter))) { val yyjson_obj_iter_get_val(key); printf(%s: %s\n, yyjson_get_str(key), yyjson_get_type_desc(val)); } } else { printf(read error (%u): %s at position: %ld\n, err.code, err.msg, err.pos); } // Free the doc yyjson_doc_free(doc);这里有两个值得展开的知识点读取标志位YYJSON_READ_ALLOW_COMMENTS允许 JSON 中出现//与/* */注释YYJSON_READ_ALLOW_TRAILING_COMMAS允许对象/数组尾部的逗号——这两者都属于 JSON5 特性的按需开启。yyjson 还有YYJSON_READ_ALLOW_INVALID_UNICODE、YYJSON_READ_REPLACE_INVALID_UNICODE、YYJSON_READ_INSITU、YYJSON_READ_STOP_WHEN_DONE等标志下文 Fluent Bit 集成一节会实际用到其中多个。错误处理读取失败时返回NULL通过yyjson_read_err结构体可拿到错误码err.code、错误消息err.msg与出错位置err.pos。对象迭代不同于yyjson_obj_get的按键直接查找yyjson_obj_iter系列yyjson_obj_iter_init/yyjson_obj_iter_next/yyjson_obj_iter_get_val以迭代器顺序遍历这正是 README Limitations 中用迭代器访问比按下标/按键访问更快所对应的 API。四、Sample Code 精讲写出 JSON从零构建可变文档写入 JSON 需要先创建可变文档mutable doc因为解析出的文档是不可变的// Create a mutable doc yyjson_mut_doc *doc yyjson_mut_doc_new(NULL); yyjson_mut_val *root yyjson_mut_obj(doc); yyjson_mut_doc_set_root(doc, root); // Set root[name] and root[star] yyjson_mut_obj_add_str(doc, root, name, Mash); yyjson_mut_obj_add_int(doc, root, star, 4); // Set root[hits] with an array int hits_arr[] {2, 2, 1, 3}; yyjson_mut_val *hits yyjson_mut_arr_with_sint32(doc, hits_arr, 4); yyjson_mut_obj_add_val(doc, root, hits, hits); // To string, minified const char *json yyjson_mut_write(doc, 0, NULL); if (json) { printf(json: %s\n, json); // {name:Mash,star:4,hits:[2,2,1,3]} free((void *)json); } // Free the doc yyjson_mut_doc_free(doc);这段代码的关键链路是yyjson_mut_doc_new(NULL)创建可变文档参数为自定义分配器传 NULL 用默认分配器yyjson_mut_obj(doc)创建对象节点yyjson_mut_doc_set_root将其设为根yyjson_mut_obj_add_str/yyjson_mut_obj_add_int/yyjson_mut_obj_add_val向对象添加字段yyjson_mut_arr_with_sint32(doc, hits_arr, 4)由 C 数组直接构造 JSON 数组yyjson_mut_write(doc, 0, NULL)序列化为压缩minified字符串返回的缓冲区需要调用方free()yyjson_mut_doc_free(doc)释放可变文档。读取-修改-写回文件场景README 的第四个示例演示了完整的数据流读文件 - 可变拷贝 - 过滤 null 字段 - 美化写回// Read the JSON file as a mutable doc yyjson_doc *idoc yyjson_read_file(/tmp/config.json, 0, NULL, NULL); yyjson_mut_doc *doc yyjson_doc_mut_copy(idoc, NULL); yyjson_mut_val *obj yyjson_mut_doc_get_root(doc); // Remove null values in root object yyjson_mut_obj_iter iter; yyjson_mut_obj_iter_init(obj, iter); yyjson_mut_val *key, *val; while ((key yyjson_mut_obj_iter_next(iter))) { val yyjson_mut_obj_iter_get_val(key); if (yyjson_mut_is_null(val)) { yyjson_mut_obj_iter_remove(iter); } } // Write the json pretty, escape unicode yyjson_write_flag flg YYJSON_WRITE_PRETTY | YYJSON_WRITE_ESCAPE_UNICODE; yyjson_write_err err; yyjson_mut_write_file(/tmp/config.json, doc, flg, NULL, err); if (err.code) { printf(write error (%u): %s\n, err.code, err.msg); } // Free the doc yyjson_doc_free(idoc); yyjson_mut_doc_free(doc);这里的核心 API 与写标志值得总结yyjson_doc_mut_copy(idoc, NULL)把不可变文档深度拷贝为可变文档——这是修改的前提yyjson_mut_obj_iter_remove(iter)在迭代过程中安全删除当前键值对写标志YYJSON_WRITE_PRETTY输出带缩进的美化格式YYJSON_WRITE_ESCAPE_UNICODE对非 ASCII 字符做\uXXXX转义yyjson_mut_write_file与yyjson_write_err文件写入 API 与写错误结构体err.code非 0 即失败。至此yyjson 的解析immutable doc→ 查询/遍历 → mutable copy → 修改 → 序列化完整生命周期已经打通。五、在 Fluent Bit 中的真实集成从源码看 yyjson 的实际用法yyjson 被 Fluent Bit 作为默认 JSON 后端集成接下来我们从源码确认它在真实项目中的调用方式。1. 构建集成默认开启、独立编译选项顶层 CMakeLists.txt 中# yyjson option(FLB_YYJSON Enable yyjson backend ON) if(FLB_YYJSON) add_subdirectory(${FLB_PATH_LIB_YYJSON} EXCLUDE_FROM_ALL) FLB_DEFINITION(FLB_HAVE_YYJSON) if (TARGET yyjson AND NOT FLB_COVERAGE AND CMAKE_C_COMPILER_ID MATCHES GNU|Clang|AppleClang|Intel) # yyjsons O0 reader can exceed the default coroutine stack budget. # Keep debug symbols, but force enough optimization to shrink the frame. target_compile_options(yyjson PRIVATE $$AND:$COMPILE_LANGUAGE:C,$OR:$CONFIG:Debug,$CONFIG:RelWithDebInfo:-Og ) endif() endif()要点有三通过FLB_YYJSON选项控制默认 ON编译期定义FLB_HAVE_YYJSON宏供源码条件编译使用yyjson 以add_subdirectory方式纳入构建源码位于 lib/yyjson-0.12.0/头文件为 lib/yyjson-0.12.0/src/yyjson.h针对 Debug/RelWithDebInfo 配置强制-Og因为yyjson 的 O0 读取器会超出 Fluent Bit 默认协程栈预算——这是两个项目集成时发现的真实工程问题。2. JSON → msgpackyyjson 作为默认解析后端Fluent Bit 内部以 msgpack 承载数据所有进入管道的 JSON 都需要先被转换为 msgpack。这一转换在 src/flb_pack.c 中由 yyjson 完成src/flb_pack.c 引入yyjson.hpack_json_to_msgpack_yyjson()src/flb_pack.c是核心转换函数其处理流程清晰地体现了 yyjson 的高级用法INSITU 缓冲区准备分配len YYJSON_PADDING_SIZE的缓冲区复制输入并在末尾补零。注释说明这是为了利用 SIMD 而预留 padding——we are already gaining around 50% perf improvement with this implementation compared to the previous one相比旧实现整体获得约 50% 的性能提升流式多值解析循环内用yyjson_read_opts(start, end-start, YYJSON_READ_STOP_WHEN_DONE | YYJSON_READ_INSITU | YYJSON_READ_ALLOW_INVALID_UNICODE | YYJSON_READ_REPLACE_INVALID_UNICODE, NULL, err)解析YYJSON_READ_STOP_WHEN_DONE使单次解析在读完一个完整 JSON 值后即停止配合yyjson_doc_get_read_size(doc)返回的实际消费字节数read_bytes推进start从而支持一个缓冲区内连续多个 JSON 值如日志流递归转换yyjson_val_to_msgpack()src/flb_pack.c按yyjson_get_type(val)的类型分发用yyjson_obj_foreach/yyjson_arr_foreach迭代对象与数组对字符串、布尔、整数区分yyjson_is_sint/ 用yyjson_get_sint、yyjson_get_uint与浮点yyjson_get_real分别打包为 msgpack 对应类型对外暴露flb_pack_json_yyjson()与flb_pack_json_recs_yyjson()两个入口src/flb_pack.c。3. JSON 格式化输出yyjson 负责美化与紧凑化在 src/flb_json.c 中yyjson 同样被用于 JSON 序列化与美化flb_json_write_pretty()src/flb_json.c在定义了FLB_HAVE_YYJSON时优先调用render_msgpack_document_yyjson(..., FLB_TRUE)输出美化 JSON失败再回退到旧实现flb_json_prettify()src/flb_json.c直接使用yyjson_doc *document、yyjson_read_err read_error与yyjson_write_flag flags完成解析输入 JSON - 美化输出的流程。可以看到在 Fluent Bit 中 yyjson 承担了两条关键路径JSON 进入管道时的解析转换以及数据输出时的 JSON 序列化。凡是涉及 JSON 的输入插件如 in_http、in_tail 的 JSON 解析、in_forward 的 JSON 模式等和输出插件如 out_http、out_es 的 JSON body 构造等最终都会落到这两条路径上。六、内置示例与测试资源yyjson 库目录本身带有完整的源码、头文件与测试资源仓库内还有一份打包发布目录lib/yyjson-0.12.0/以及lib/yyjson-0.12.0下的大量 JSON 测试数据文件。此外仓库根目录的基准测试 benchmarks/pack_json.c 也涉及 JSON 打包路径的压测可结合 src/flb_pack.c 的转换实现一起阅读。对想在 Fluent Bit 环境中验证 yyjson 行为的开发者建议路径是阅读 lib/yyjson-0.12.0/src/yyjson.h 查看完整 API 与标志位定义以 src/flb_pack.c 的pack_json_to_msgpack_yyjson为范例观察真实项目中标志位组合INSITU STOP_WHEN_DONE 宽容 Unicode的使用方式在 Fluent Bit 配置中通过parser json或 HTTP 输入插件投递 JSON用 debug 日志flb_debug([yyjson-msgpack] read error ...)观察解析行为。七、版本路线图与许可证README 的 TODO for v1.0 清单展示了项目成熟度的演进轨迹已完成的项包括文档页、GitHub CI/codecov、valgrind/sanitizer/fuzzing 测试、JSON Pointer 查询与修改、RAW类型读写、限制实数输出精度的选项、JSON5 支持未完成项包括两个 JSON 文档 diff 函数、性能优化文档、ABI 稳定性保证。许可证方面yyjson 以MIT 协议发布这也是它能够被 Fluent BitApache 2.0 生态以子库形式内置的前提之一。结语从 README 的定位与示例到 Fluent Bit 源码中的实际调用yyjson 向我们展示了小而快的 C 语言 JSON 库如何在真实的生产级数据管道中发挥作用单头单源文件、严格 RFC 8259 合规、可选的 JSON5 扩展、immutable/mutable 双文档模型以及 insitu STOP_WHEN_DONE 等高级解析标志带来的吞吐优势。如果你正在为嵌入式、日志处理或流式数据场景选择 JSON 库yyjson 的这套设计——尤其是它在 Fluent Bit 中实测带来的约 50% 解析性能提升——是极具参考价值的工程样本。【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考