喵喵宠物医院
86.09M · 2026-04-08
本文整理自丰巢大数据开发工程师 宋友刚 在 Doris Summit 2025 中的演讲。
丰巢的业务覆盖快递、广告、洗护和到家四大板块。围绕这些核心业务,上层运行着大量应用系统,每天持续产生海量日志。当前日志写入规模已超过 100 万/秒、40TB/天,这对日志平台的实时写入 、稳定查询 和存储成本都提出了极高要求。
原有日志平台基于典型的 ELK 架构构建。这套系统虽然具备较强的检索能力,但在高并发和大规模数据写入场景下,逐渐暴露出一系列结构性问题,已经难以支撑当前业务需求。

这些问题主要体现在以下几个方面:
在原有架构中,使用的是一套规模较大的 Elasticsearch 集群:由 13 台 96C 256G 节点和 5 台 48C 128G 节点组成的异构集群。日志每天磁盘占用约 40TB ,写入峰值约为 80 万条/秒。
为缓解写入压力,我们新增采购了 10 台配置为 112C 512G 的高性能机器,并基于这批机器搭建了一套新的 Elasticsearch 集群进行测试。测试结果显示:
但当前业务高峰期日志写入已达到 120 万条/秒 ,仍存在明显缺口,继续扩容无法从根本解决问题。
团队之所以开始关注 Doris 的重要原因是:公司本身已经有业务集群在使用 Apache Doris。这意味着团队对 Doris 并不陌生,也有机会更低成本地评估其在日志分析场景下的适配性。随后,我们在同样的硬件条件下搭建了 Doris 集群进行对比测试。结果如下:
可以看到,在写入能力和存储成本两个关键指标上,Doris 均显著优于 Elasticsearch。
在查询性能方面,由于当时的核心目标是优先解决写入瓶颈,我们选取了几个典型高频场景进行验证:基于关键字检索最近一小时、最近三小时和最近一天日志。测试共执行 5 次并取平均值,用于评估两者在基础查询场景下的表现结果显示,Doris 在典型场景下整体优于 Elasticsearch,能够更快返回查询结果。
结合测试结果与实际使用体验,我们总结了 Doris 在日志分析场景中的几项核心优势:
综合上述对比及测试结果,决定引入 Apache Doris 替换之前架构中的 ELK。基于 Doris 提供日志的统一采集、清洗、计算、存储、检索、监控和分析等多项服务,在整体架构上,保留了原有的日志采集链路,即 Filebeat → Kafka,重点对后半段进行了重构。新的架构调整为:

新架构上线后,在写入性能、存储成本和查询效率等方面均取得了明显提升。**写入速度提升约 2 倍、查询速度提升约 6 倍、存储空间减少约 50% **。
在表结构设计上,我们重点考虑了分区、分桶、索引和压缩策略,因为这些设置会直接影响写入稳定性、查询性能以及存储成本。
1、分区策略:按小时分区
分区粒度需要结合实际数据规模来确定。以当前每天约 18TB 的数据量来看,如果按天分区,分桶数可能达到数千个,管理和写入压力都会非常大。因此,我们最终选择了按小时分区,以降低单分区压力,更适合日志这种高频写入场景。
2、分桶策略:使用 RANDOM 分桶
在分桶方式上,我们采用了 RANDOM 分桶,并将单桶大小控制在 5GB 左右。 Doris 针对日志场景优化的一种方式是单 Tablet 导入,有助于减少导入开销并提升写入效率。
3、索引设计:按查询模式建立倒排索引
索引方面,我们根据实际查询场景对关键字段建立了倒排索引。对于需要全文检索的字段,开启分词;对于仅用于等值查询的字段,则不启用分词,以避免额外开销。
4、链路延迟监控:增加写入时间字段
为了更准确地监控日志链路延迟,我们额外增加了 doris_ingest_time 字段,用于记录日志写入 Doris 的时间。通过对比 log_time 和 doris_ingest_time,可以直观看到日志从产生到入库的延迟情况。
5、压缩策略:使用 ZSTD
在压缩算法上,我们采用了 ZSTD,在保证查询性能的同时,有效降低了日志存储空间占用。
示例表结构如下:
CREATE TABLE `applogs_all` ( `log_time` datetime(3) NOT NULL COMMENT "日志时间,精确到毫秒", `app_name` varchar(256) COMMENT "应用名称", `service_name` varchar(256) COMMENT "服务名称", `log_level` varchar(10) COMMENT "日志级别", `log_content` text COMMENT "日志正文内容", `trace_id` varchar(256) COMMENT "全局追踪ID", `thread_name` varchar(256) COMMENT "线程名称", `bl_no` tinyint COMMENT "业务线编号", `doris_ingest_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT "数据入库时间", INDEX idx_log_content (`log_content`) USING INVERTED PROPERTIES("parser" = "unicode"), INDEX idx_trace_id (`trace_id`) USING INVERTED)DUPLICATE KEY(`log_time`,`app_name`)PARTITION BY RANGE(`log_time`)()DISTRIBUTED BY RANDOM BUCKETS 180PROPERTIES ( "compression"="zstd");在日志场景下,系统优化的首要目标始终是保障写入稳定。因此,我们将优化重点放在写入链路和 Compaction 控制上。
在 Flink SQL 作业侧,我们主要做了三项优化:
sink.batch.size 和 sink.batch.interval 控制批次大小与写入频率在 Compaction 侧,我们主要围绕三个方向进行控制:
在查询层面,我们重点解决了两个典型问题。
MATCH 查询的语义是匹配任何一个关键词。对此,我们通过查看分词结果,指导研发使用双引号把查询包围起来使用短语查询 MATCH_PHRASE 匹配所有关键字或前缀,提高准确性。
MATCH 依赖分词匹配,而 LIKE 直接进行子串匹配。对于 "clientMobile"、"smartphone" 这类字段,LIKE 往往比 MATCH 召回更多。因此,在实际使用中,我们并未机械依赖倒排索引,而是根据字段特征和查询目的选择更合适的查询方式。

主要从资源隔离、查询治理、权限控制和监控告警四个方面做了稳定性保障。
在资源隔离上,Doris 提供了 Resource Group、Workload Group 等多种机制。结合现阶段的使用情况,我们主要基于 Workload Group 对查询资源进行隔离和限制,重点是避免查询任务挤占写入资源。
具体策略上,内存采用软限制,因为当前整体内存相对充足;CPU 采用硬限制,并将查询与写入资源按 1:1 进行分配,以确保高峰期写入的稳定性。IO 原本也尝试过进行硬限制,但实际观察发现,限制后查询性能下降较为明显,而当前 IO 尚未成为主要瓶颈,因此暂时放开,后续再根据运行情况持续评估。
CREATE WORKLOAD GROUP IF NOT EXISTS wg_applogs_select PROPERTIES ( "memory_limit"="50%", "enable_memory_overcommit"="true", "cpu_hard_limit"="50%", "read_bytes_per_second" = "104857600" ...);实际使用中,经常会出现用户写 SQL 时遗漏时间范围或过滤条件的情况,这类查询很容易演变为全表扫描的大查询,不仅影响执行效率,也会拖慢其他正常查询,影响整体使用体验。为此,我们增加了大查询拦截机制,优先在查询规划阶段进行识别和拦截,尽量将风险控制在执行之前。运行时拦截后续会继续完善。
-- 拦截无where条件的查询CREATE SQL_BLOCK_RULE block_no_wherePROPERTIES ( "sql" = "(?i)^.*\\bFROM\\s+\\bapplogs_all\\s+((?!WHERE).)*$", ...);-- 拦截没有指定log_time的SQLCREATE SQL_BLOCK_RULE block_no_log_time PROPERTIES ( "sql"="(?i)^.*`?\\bapplogs_all\\b`?\\s+\\bWHERE\\b(?!.*\\blog_time\\s*[><=]*.*\\blog_time\\s+BETWEEN.*AND.*).*$", ...);-- 拦截没有指定app_name的查询CREATE SQL_BLOCK_RULE block_no_app_name PROPERTIES ( "sql"="(?i)^.*`?\\bapplogs_all\\b`?\\s+\\bWHERE\\b(?!.*\\bapp_name\\s*(=*in\\s*\\()).*$", ...);在权限控制方面,考虑到 Doris 支持行级权限控制,选择在日志表中增加"业务线 "字段,并在 Flink 写入时通过关联维表补齐该字段,再基于该字段实现按业务线的访问控制。这样既能满足隔离需求,也避免了多表管理的复杂度。查询时,单业务线场景可直接使用等值过滤,跨业务线场景则可以通过 IN 方式实现。
-- 单个业务线CREATE ROW POLICY row_policy_test ON applogs_all AS PERMISSIVE TO ROLE applogs_test_role USING (bl_no = 1);-- 跨业务线CREATE ROW POLICY row_policy_test2 ON applogs_all AS PERMISSIVE TO ROLE applogs_test_role2 USING (bl_no in (1,2));在监控告警方面,我们重点关注两类问题:Kafka 消费积压 和 日志链路延迟。
对于前者,主要通过监控 Kafka 的消费堆积情况,在超过阈值时触发企微告警,便于及时发现消费异常。
对于后者,曾遇最新日志无法查询,但日志文件实际存在,Kafka 侧也没有明显积压。进一步通过 show partitions 排查后发现,同一时间存在写入多个分区的情况,因此怀疑问题出在生产端延迟。针对这一情况,我们通过对比 log_time 与 doris_ingest_time 两个时间字段来判断日志延迟,并基于该指标进行告警。
通过上述措施,使平台在高并发场景下能够更稳定地运行,也为后续更复杂的查询治理和可观测性建设打下了基础。
目前,基于 Doris 的日志平台已经接入应用日志,整体平稳运行,写入速度提升约 2 倍 、查询速度提升约 6 倍、存储空间减少约 50%。
后续我们计划逐步接入审计日志、Nginx 日志等更多数据源,持续完善日志体系建设,并推动平台向更完整的可观测性系统演进。
未来,我们希望进一步基于 OTel + Grafana 打通监控告警、链路追踪与日志分析能力,形成统一的运维观测闭环。下一步的重点工作之一,是基于日志中的 Trace ID 实现与追踪系统的双向联动:既支持从日志跳转到追踪,也支持从追踪回溯到日志,从而提升问题定位和故障排查效率。
",