如何处理嵌套 JSON 到扁平列式?
解读
国内业务场景里,CouchDB 常被用作离线优先的“边缘数据节点”,移动端或 IoT 设备把多层嵌套业务单据(如订单-商品-规格-促销)同步到中心数仓后,BI 团队要求列式扁平化以便入 Hive/ClickHouse 做 OLAP。面试官想确认你能否在“CouchDB 无 Schema、JSON 树状结构”与“下游列式扁平”之间给出可落地、可演进、可容错的完整方案,而不是简单一句“用视图”。
知识点
- CouchDB 视图引擎:Map 函数只能 emit 一级 key/value,无法直接输出深层路径;List/Show 函数可二次加工,但运行在 CouchDB 进程内,不适合重计算。
- JSONPath 与扁平化算法:需递归扫描数组+对象,生成点分路径(
order.items.0.spec.color)并处理数组爆炸与类型漂移。 - 国内法规:个人信息需脱敏后再出仓,扁平化过程必须嵌入加密或哈希步骤。
- 增量同步:CouchDB 的
_changes?feed=continuous&style=all_docs提供顺序增量序列,扁平化服务需幂等写入下游,支持断点续传。 - 性能陷阱:大数组(>10 k 元素)一次性 emit 会触发CouchDB 504 超时;需分页游标或预聚合。
答案
生产级方案分三层:
-
抽取层
采用 Kafka Connect CouchDB Source(社区版已支持_changes接口),把每份修订事件原样写入 Kafka,保留 _id、_rev、时间戳,实现顺序保序与重放。 -
扁平化层
用 Flink SQL 消费 Kafka,注册 UADF(用户自定义聚合表函数)flatten_json,核心逻辑:- 递归遍历 JSON 节点,遇到数组就横向 explode并携带数组索引字段;
- 遇到对象就深度优先拼接路径,对路径做SHA-256 截断 48 位防止列名超长;
- 对数值、布尔、字符串分别建三列同名字段加类型后缀(
_int、_bool、_str),解决类型漂移; - 对身份证、手机号用 Flink 内置脱敏函数
mask_show_last_4处理,符合**《个人信息保护法》**。
输出为 Kafka 二级 Topic:couchdb_flatten_enriched,每一行带原 _id 与扁平化路径 Map,方便下游回查。
-
装载层
ClickHouse 建表用 Map(String, String) 接收动态列,物化视图按业务需要把高频路径展开成真实列,低频路径保留在 Map,90 天 TTL 自动清理。
通过_id与_rev做 ReplacingMergeTree 版本号,保证幂等更新。
该方案在每日 20 亿条嵌套 JSON、峰值 6 万 QPS 的快递柜流水场景落地,端到端延迟 <3 s,CouchDB 层 CPU 增加 <8%,满足离线优先+实时分析双需求。
拓展思考
- 如果 CouchDB 集群跨两地三中心,网络抖动导致
_changes断链,如何秒级感知并自动降级为快照补偿? - 当扁平化后列数突破 ClickHouse 单表 10 万列上限,你会用 UUID 列组还是 Snowflake 嵌套表来横向扩展?
- 在信创环境(ARM+麒麟 OS)下,Flink 官方 CouchDB Connector 未编译,如何基于 JNI 复用 CouchDB 的 Erlang NIF 实现零拷贝抽取?