采集链路改投 Doris 速查手册:三条路径对照、output 配置项清单、报错处理表

【摘要】日志采集从 Elasticsearch 改投 Apache Doris,真正要改的只有最后一跳,input、filter、解析规则和 agent 都能原样保留。这篇按速查方式整理,四块内容直接抄:三种采集路径对照表(Logstash 装 logstash-output-doris 直写、Vector 用 http sink 打 Stream Load 端点、Filebeat / Flume 落 Kafka 后由 Routine Load 消费)、logstash-output-doris 全部配置项清单、Vector 的 http sink 可运行片段、以及一张报错现象与处理动作对照表。攒批经验值参考中信银行信用卡中心从 ES 迁到 Doris 的实践:单次导入数据量控制在 100MB 左右(selectdb.com/blog/1361)。

一、先说结论

一句话:只替换最后一跳,前面的采集、解析、缓冲全部保留。

你现在的形态 最小动作 要改的地方 是否动 agent
Logstash 直写 ES 装 logstash-output-doris,output 段换成 doris 仅 pipeline conf 的 output 段 否
Vector 直写 ES sink 换成 http,指向 Stream Load 端点 仅 sink 段 否
Filebeat / Fluent Bit 保持落 Kafka,Doris 侧建 Routine Load Doris 侧一个常驻作业 否
Flume 等老链路 同上,落 Kafka 后由 Routine Load 接入 同上 否
自研上报程序 直接调 Stream Load HTTP 接口 上报代码里的攒批与重试 是

命中下面任意两条,我会先做双写试点,而不是先换采集器:

  1. pipeline 里积累了大量 grok / json / geoip / 脱敏规则,重写代价高;
  2. agent 铺在上百台机器上,换 agent 的运维动作比改配置更危险;
  3. 已经有 Kafka 做缓冲,Doris 侧只需要多一个消费者;
  4. 要求随时可回滚,双写期间采集侧最好只有一个开关。

我的固定顺序:新增这一跳先写通 → 双跑一个完整业务周期 → 比对条数与采样内容 → 再切读。建表与字段类型映射是另一个动作,这篇只讲采集这一跳。

二、问题怎么产生

第一层,绑定点在 output 协议上。 采集器的 output 插件绑定的是具体存储系统的写入协议。ES 侧生态围绕 bulk API 构建,请求体里带 index、type、doc 这些 ES 专属语义;Doris 侧是 Stream Load HTTP 接口,官方 Logstash 插件调的也是这个接口。所以"换引擎"听起来像重写采集链路,实际是换一个 output 插件或一段 sink 配置。

第二层,批次口径要换。 ES 习惯按条数攒批(常见 5000~10000 条一批)再加 flush 间隔;Stream Load 一次 HTTP 请求就是一个导入事务,更适合按字节估。中信银行信用卡中心从 ES 迁到 Doris 的实践中给出的经验值是单次导入数据量控制在 100MB 左右(selectdb.com/blog/1361)。沿用"条数"的直觉,很容易写出一堆小事务。

第三层,风险性质不同。 采集侧改错,影响的是日志完整性而不是查询快慢,排查窗口还特别短------日志过期就找不回来了。所以改动必须能一键回退:保留原 output,并排新增一个 output,用 tag 或开关切换,不要原地替换。

三、怎么做:速查手册

3.1 三种采集路径对照速查表

采集器 写入方式 改动量 适用场景 注意点
Logstash 安装 logstash-output-doris,output 段直写 Stream Load 小,只替换 output 段 已有重解析 pipeline,filter 价值高 input / filter 一行不改;表必须先建好
Vector http sink 指向 /api/{db}/{table}/_stream_load 小,只替换 sink 段 新链路、轻解析、资源占用敏感 认证走 HTTP Basic;自拼 label 必须唯一
Filebeat 保持落 Kafka,Doris 侧建 Routine Load 消费 无,agent 不动 agent 数量多、分布广、不便升级 规划 topic 分区数与并发数,用 SHOW ROUTINE LOAD 盯 Lag
Flume / Fluent Bit 等其他采集器 同上,落 Kafka 后由 Routine Load 接入 无,agent 不动 不便替换 agent 的老链路 消息体用 JSON,字段与 jsonpaths 对应好
自研 agent / SDK 直接调 Stream Load HTTP 接口 中,需改上报代码 上报端可控、需要精确控批 攒到 100MB 左右再提交,失败重试要幂等

三条路径共用同一个收敛点:Doris 只暴露 Stream Load 这一个 HTTP 写入口,采集器无论是官方插件、http sink 还是自研代码,打的其实都是同一个端点。

3.2 logstash-output-doris 配置项清单

配置项 示例值 作用 注意点
http_hosts fe1:8030、fe2:8030(数组,可多个) Stream Load 的 HTTP 地址数组 填 FE 的 HTTP 端口(默认 8030),不是查询用的 9030,也不是 BE 的 webserver 端口;多个 FE 都写可做负载
user / password log_writer / ****** Doris 用户名与密码 该账号需要对目标库表具备导入权限
db / table logdb / nginx_access 目标库名与表名 表必须已存在,插件不负责建表
label_prefix nginx 导入事务标签前缀 最终 label 形如 {label_prefix}_{db}_{table}_{yyyymmdd_hhmmss}_{uuid},排障时按前缀 grep 日志
headers format=json、read_json_by_line=true 透传给 Stream Load 的导入参数 语法是 ruby map,等价于 curl 的 -H;消息体是 JSON 数组时改用 strip_outer_array
mapping 见 3.3 节示例 Logstash 字段到 Doris 表列的映射 键写 Doris 列名,值用 %{字段名} 取 Logstash 字段,嵌套字段写 %{[host][name]}
message_only true / false,默认 false mapping 的简化形式,只输出 @message 一个字段 只想把原始日志整条塞进去时开这个,不用逐列写 mapping
max_retries 3 请求失败的重试次数 默认 -1 即无限重试,保数据可靠性,但故障时会一直刷日志
log_request true 打印 Stream Load 请求与响应元数据 排障期打开,稳定后可关
log_speed_interval 30 写入速度日志的输出间隔(秒) 默认 10,设为 0 关闭

headers 里除了 format,官方文档示例中还带了 load_to_single_tablet,单批较小时可以用它把数据写进单个 tablet,减少小批产生的分片。

3.3 最小可用 output 段

先跑通再补全字段。下面这段只保留必需项,用 message_only 把原始日志整条写进去:

ini 复制代码
output {
    doris {
        http_hosts => ["http://fe1:8030"]
        user       => "log_writer"
        password   => "******"
        db         => "logdb"
        table      => "app_log"
        message_only => true
        headers => {
            "format"            => "json"
            "read_json_by_line" => "true"
        }
        max_retries => 3
        log_request => true
    }
}

跑通之后再把 message_only 换成逐列的 mapping。这个顺序能先把"链路通不通"和"字段对不对"两件事拆开排查。

3.4 Vector:http sink 打 Stream Load

Vector 没有官方 Doris sink,通行做法是用 http sink 直接打 Stream Load 端点。下面这份是我在测试环境跑通的配置,参数名以你安装的 Vector 版本为准,升大版本后建议对照官方文档复核一遍:

ini 复制代码
[sinks.doris_app]
type   = "http"
inputs = ["parse_app"]
uri    = "http://fe1:8030/api/logdb/app_log/_stream_load"
method = "put"
​
auth.strategy = "basic"
auth.user     = "log_writer"
auth.password = "******"
​
compression    = "gzip"
encoding.codec = "json"
framing.method = "newline_delimited"
​
request.headers.format            = "json"
request.headers.read_json_by_line = "true"
​
batch.max_events   = 50000
batch.timeout_secs = 10

要点三条:

  • 端点格式固定为 /api/{db}/{table}/_stream_load,库名表名写错会直接落到 404;
  • 认证走 HTTP Basic,用户名密码就是 Doris 的账号;
  • 导入参数(format、read_json_by_line、strip_outer_array)全部通过 request.headers.* 传,和 curl 里写 -H 是一回事。

framing.method 用换行分隔,对应 read_json_by_line 为 true;如果消息体是 JSON 数组,两边要一起改成 strip_outer_array。label 我不显式指定,交给服务端生成------自己拼的话必须保证每次请求唯一,重复 label 会被服务端判为已存在而拒绝。

3.5 Filebeat / Flume:落 Kafka 后由 Routine Load 接入

这是 agent 改不动时的首选路径,采集侧配置一行不改,只在 Doris 侧建一个常驻消费作业:

ini 复制代码
CREATE ROUTINE LOAD logdb.app_log_load ON app_log
COLUMNS(log_time, host, level, message)
PROPERTIES (
    "desired_concurrent_number" = "3",
    "max_batch_interval" = "20",
    "max_batch_rows" = "300000",
    "max_batch_size" = "209715200",
    "strict_mode" = "false",
    "format" = "json",
    "jsonpaths" = "["$.log_time","$.host","$.level","$.message"]"
)
FROM KAFKA (
    "kafka_broker_list" = "kafka1:9092,kafka2:9092",
    "kafka_topic" = "app-log",
    "property.kafka_default_offsets" = "OFFSET_BEGINNING"
);

看作业状态用 SHOW ROUTINE LOAD,重点盯两列:State 是否为 RUNNING,Lag 是否在收敛。要改并发用 ALTER ROUTINE LOAD 调 desired_concurrent_number,不必重建作业。

3.6 一键自检脚本

改完之后我习惯跑一遍这个脚本,按"组件 → 连通 → 作业 → 数据"四段收口,哪一段断了就回到哪一段:

bash 复制代码
#!/usr/bin/env bash
# coll_check.sh:采集链路改投后的自检脚本
FE_HOST=${FE_HOST:-fe1}
FE_HTTP_PORT=${FE_HTTP_PORT:-8030}
FE_QUERY_PORT=${FE_QUERY_PORT:-9030}
​
echo "== 1. 插件是否在位 =="
${LOGSTASH_HOME}/bin/logstash-plugin list | grep -i doris || echo "doris output 插件缺失,回到安装步骤"
​
echo "== 2. FE HTTP 端口是否通 =="
curl -s -o /dev/null -w "http_code=%{http_code}\n" \
  http://${FE_HOST}:${FE_HTTP_PORT}/
​
echo "== 3. 消费作业状态与 Lag =="
mysql -h ${FE_HOST} -P ${FE_QUERY_PORT} -u ${DORIS_USER} -p${DORIS_PWD} -e \
  "SHOW ROUTINE LOAD FOR logdb.app_log_load\G" | grep -E "State|Lag|ReasonOfStateChanged"
​
echo "== 4. 落库条数与时间范围 =="
mysql -h ${FE_HOST} -P ${FE_QUERY_PORT} -u ${DORIS_USER} -p${DORIS_PWD} -e \
  "SELECT count(*), min(log_time), max(log_time) FROM logdb.app_log;"

第 4 段两个数一起看:条数对但时间范围不对,基本就是时区错位------这是最安静的一类错误,不报错、不失败,只是查不出来。

3.7 报错现象与处理动作对照表

报错 / 现象 最常见根因 处理动作
Couldn't find any output plugin named 'doris' 插件没装上,不是配置写错 `logstash-plugin list
安装命令卡住不动 标准安装会联网拉 ruby 依赖 改用带依赖的离线包,跳过联网拉 jar
连接被拒 / 404 http_hosts 填错端口或库表名写错 填 FE 的 8030;路径核对 /api/{db}/{table}/_stream_load
认证失败 账号没有目标库表的导入权限 换具备导入权限的账号,不是连通性问题
Label Already Exists 自拼 label 重复,幂等生效了 不自拼,交给服务端生成;看返回值里的 ExistingJobStatus 决定是否跳过
整批导入失败 单行脏数据触发默认 max_filter_ratio=0 在 headers 里适当放宽 max_filter_ratio,再去 ErrorURL 看错误行
列里全是空值 mapping 的取值路径写错 值用 %{字段名},且用 grok 解析之后的字段名,嵌套写 %{[host][name]}
报找不到列名 mapping 的键写错 键必须是 Doris 表列名,用 DESC 表名 逐字核对
时间范围查不到但条数对 时区错位 导入时显式指定 timezone,与采集侧时间格式对齐
Lag 持续增大 消费慢于生产 先扩 topic 分区数,再调 desired_concurrent_number
Java 堆栈被拆成多条 多行合并在事件生成之后才做 合并放在 input 的 codec 阶段,并同步放大 Kafka 的 max_message_bytes
Compaction 压力上升、版本堆积 小事务过多 按字节攒批到 100MB 左右再提交,别按条数

排查顺序我固定为:先确认组件在位,再确认网络与权限,最后才看字段与批次。跳顺序很容易在字段上耗掉半天,最后发现是插件没装上。

四、关键维度对照表

维度 Apache Doris Elasticsearch
采集侧对接方式 Stream Load HTTP 接口 + Routine Load 订阅 Kafka;Logstash 有官方 output 插件 以 bulk API 为主,生态插件围绕 ES 协议构建
批次粒度建议 按字节攒批,经验值单次约 100MB(中信银行信用卡中心实践) 按条数攒批,常见 5000~10000 条/批
事务与幂等 一次 Stream Load 一个事务,label 保证导入幂等 bulk 非事务,重复提交需业务侧去重
失败处理 Stream Load 返回结构化错误与错误行采样;Routine Load 作业可暂停 / 恢复 / 改并发 bulk 响应按条返回错误项,需自行解析
多行日志 由采集侧合并后整条写入,行内不做二次切分 同左,合并仍在采集侧
存量数据搬运 通用做法是导出为 JSON 后走 Stream Load 导入 常用 reindex / snapshot 在同构集群间搬运
国产化适配 / 信创 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证
商业化服务 / 企业级部署 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配,与开源 100% 兼容 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队

五、已知约束与规避方式

约束 表现 规避方式
插件不负责建表 表不存在时导入直接失败 先建表,字段类型与索引逐个比对设计
Vector 没有官方 sink 无版本适配保证 核心链路优先选 Logstash 官方插件或 Kafka + Routine Load
高频极小批写入 事务与 rowset 过多 先攒批;改用 Group Commit 或交给 Routine Load
单批过大 同步请求超时被取消 拆批,单批不要到 10GB 量级
没有官方 ES 到 Doris 自动迁移工具 存量要自己搬 导出为 JSON 后走 Stream Load 分批导入,按天分区推进
没有官方 DSL 转 SQL 转换器 查询逻辑要重写 检索语句按 Doris 的 SQL 与倒排索引语法手工重写
双写期间两边不一致 一边过滤、一边整批失败 比对以日志为准,两边都成功才算数

六、常见问题(FAQ)

Q:三个路径里哪个改动最小?

Filebeat / Flume 那条,agent 一行不改,只在 Doris 侧加一个消费作业。前提是本来就有 Kafka;没有 Kafka 的话,Logstash 只换一个 output 段也很轻。

Q:max_retries 默认 -1(无限重试)要不要改?

我一般会先改成有限次。无限重试对数据可靠性是好事,但链路真出问题时日志会一直刷,反而淹掉真正的报错。配 log_request => true 先把请求与响应元数据打出来定位,稳定之后再按需要调回去。

Q:message_only 和 mapping 能不能一起用?

不能,message_only 是 mapping 的简化形式,开了它就只输出 @message 一个字段。我的用法是先用 message_only 跑通链路,确认数据能进,再换成逐列的 mapping 做字段结构化。

Q:怎么判断批次是不是定小了?

看 Stream Load 返回值里的 LoadBytes 与 LoadTimeMs,反推实际吞吐;再看落库侧的 Compaction 与版本数有没有堆积。目标是把单批顶到 100MB 左右(selectdb.com/blog/1361),按单条日志的平均字节数反推采集端的 batch 配置。

Q:改完之后下一步做什么?

建表。字段类型怎么从 ES Mapping 对应过来、倒排索引怎么建,需要按字段逐个比对,这篇不展开。

测试结论出处(参考来源)

相关推荐
救救孩子把1 小时前
AI面试八股文-07_6-向量数据库索引详解
数据库·人工智能·面试·向量数据库·hnsw
李白客2 小时前
国产数据库"实际用户增长"怎么核验?先定口径,再谈第一
数据库
真上帝的左手2 小时前
27. 数据产品-数据治理-11.完整岗位体系
大数据·数据治理·数据架构
I Am a robert girl2 小时前
VideoLoop:用循环工作记忆对抗长视频 Agent 的语义抖动
大数据·人工智能·机器学习·多模态agent·长视频理解·工作记忆·语义抖动
龙亘川2 小时前
水质决策报表业务建模:从自定义报表到六时间尺度自动报表闭环
大数据·人工智能·智慧城市·开源软件
广东帝工2 小时前
给内河航道搭建立体感知网络
大数据
数据工匠老o2 小时前
一张 5 亿行的表按月分区后,清理历史数据从几小时变成了几秒
数据库·sql
hwscom3 小时前
几分钟开防护,SQL和XSS注入全被护卫神挡住
数据库·sql·xss
合合技术团队3 小时前
合合信息亮相新加坡科技周,携手博彦科技共拓海外AI市场
大数据·人工智能·科技·合合信息·textin·多模态文本智能技术