1. 项目概述:为什么“定时增量导入HDFS数据到Druid”不是一句口号,而是数据团队每天睁眼就要解决的硬需求
你有没有遇到过这样的场景:某天凌晨三点,监控告警突然炸开——Druid集群的实时任务延迟飙升到4小时,Dashboard上关键业务指标变成一片灰色,下游报表系统集体报错。运维同事在群里发了个“正在排查”,而你作为数据平台负责人,翻着日志发现根源竟然是:上游HDFS里昨天新增的2TB分区数据,因为调度脚本漏配了一个参数,压根没进Druid;更糟的是,补跑时直接全量重刷,把整个历史维度表锁死两小时,连带影响了其他五个业务线的实时看板。
这不是虚构故事,是我在某中型互联网公司支撑广告归因分析平台时踩过的真坑。Druid —— 定时增量导入HDFS数据到Druid,这短短十一个字背后,藏着数据工程师最真实的生存逻辑:它不是“又一个ETL工具链演示”,而是连接离线数仓与实时分析能力的生命线。HDFS是数据的粮仓,存着清洗好、按天/小时分区的结构化事实表;Druid是前台的作战指挥室,要求毫秒级响应、高并发查询、亚秒级数据可见性。两者之间若靠人工搬运或粗暴全量同步,等于在粮仓和指挥室之间用自行车运弹药——看似能动,实战必崩。
核心关键词“Druid”“HDFS”“定时增量”必须掰开揉碎理解:Druid本质是为OLAP优化的列式时序数据库,强依赖数据分段(segment)的不可变性与时间分区;HDFS则是典型的批处理存储,天然支持大文件、高吞吐写入,但缺乏事务与细粒度更新能力;“定时增量”则直指痛点——我们不要每天凌晨把过去30天数据全刷一遍,只要把“今天新增的那几个小时分区”精准、原子、可回溯地塞进Druid对应的时间窗口。这背后涉及数据一致性校验、segment生命周期管理、任务失败自动断点续传、资源隔离防雪崩等一整套工程实践,远非配置几个JSON参数就能搞定。
适合谁来读这篇?如果你正面临以下任一情况,这篇文章就是为你写的:
- 你刚接手一个已上线但问题频发的Druid数据链路,每次发布新报表都得手动调参、重启任务;
- 你的团队还在用Shell脚本+HiveQL拼接Druid索引任务,没人敢动调度逻辑;
- 你发现Druid控制台里segment数量每月暴涨50%,但查询性能却在下降,怀疑是小文件泛滥;
- 你被业务方追问“为什么昨天的数据要等到今天下午才查得到”,而你心里清楚——根本不是Druid慢,是上游导入流程卡在了HDFS路径扫描环节。
接下来的内容,不讲概念,不堆术语,只拆解我亲手落地过三套不同规模(日均50GB、500GB、2TB)HDFS→Druid增量链路的真实方案。从为什么选HadoopIndexTask而非Kafka Indexing Service,到如何用一段Python脚本自动识别HDFS新增分区并生成精确的interval参数,再到怎样让Druid Coordinator在segment发布瞬间就触发缓存预热——所有细节,包括那些官方文档里绝不会写的“踩坑现场”,都会摊开来讲。
2. 整体架构设计与技术选型深度拆解:为什么放弃“看起来更酷”的方案,死磕HadoopIndexTask
在动手写第一行代码前,我和团队花了整整两周做方案推演。当时摆在桌面上的主流路径有三条:一是用Druid原生的Kafka Indexing Service,把HDFS数据先灌进Kafka再消费;二是用Spark Streaming写自定义Sink;三是坚持用Druid官方推荐但被很多人诟病“笨重”的HadoopIndexTask。最终我们锁定了第三条,并为此重构了整个调度体系。这个选择不是妥协,而是基于对数据特征、运维成本、故障恢复确定性的综合权衡。
先说为什么坚决不用Kafka Indexing Service。表面看它最“实时”,数据从HDFS落到Kafka Topic,Druid实时消费,延迟理论上能做到秒级。但问题出在数据源头——HDFS里的数据是按天/小时分区的静态文件,比如/data/impression/dt=2024-06-15/hour=14/下有12个Parquet文件,总大小87GB。如果硬要走Kafka,就得先用Spark或Flink把这87GB文件读出来、解析成JSON/Avro流,再逐条发往Kafka。这带来三个致命问题:第一,Kafka集群瞬时吞吐压力巨大,我们测试时单Topic峰值TPS超200万,Broker磁盘IO直接打满;第二,数据格式转换丢失原始分区信息,Druid无法按dt字段做时间分区,导致segment跨天混杂,查询性能断崖下跌;第三,也是最要命的——一旦Kafka消费端(Druid MiddleManager)挂掉,重放offset时会重复导入同一份HDFS数据,而Druid对重复segment没有去重机制,结果就是Dashboard上同一小时的数据出现双倍计数,业务方投诉电话直接打到CTO办公室。
再看Spark Streaming方案。它灵活性高,可以自定义checkpoint、状态管理、Exactly-Once语义。但我们实测发现,一个简单的“读HDFS Parquet → 转Druid JSON → 写IndexTask”流水线,在YARN资源紧张时,Streaming Job的微批处理延迟波动极大,有时1分钟的batch要跑3分钟,导致Druid segment发布时间飘忽不定。更麻烦的是,当需要回溯修复某天数据时,你得手动停掉Streaming Job,改代码指定起始offset,再重新提交——这在生产环境是高危操作,稍有不慎就引发全量重刷。而我们的SLA要求“任何单日数据修复必须在15分钟内完成,且不影响当日新增数据导入”。
最终选定HadoopIndexTask,正是因为它把“不确定性”全部关进了笼子。它的核心逻辑是:以HDFS路径为输入源,以Druid datasource和interval为输出目标,整个过程完全由MapReduce/YARN驱动,天然具备容错、重试、进度跟踪能力。最关键的是,它支持firehose配置中的static模式,即明确指定要处理的HDFS文件列表(而非通配符扫描),这意味着我们可以用外部脚本精准控制“本次只导入/data/impression/dt=2024-06-15/hour=14/下的12个文件”,杜绝了路径误匹配、小文件泛滥等问题。而且,HadoopIndexTask生成的segment自带version字段(如2024-06-15T14:00:00.000Z/2024-06-15T15:00:00.000Z_2024-06-15T14:30:00.000Z),Druid Coordinator能据此严格按时间顺序加载,保证查询结果的单调性。
当然,HadoopIndexTask不是银弹。它最大的槽点是“启动慢”——每次任务都要拉起YARN Application,光AM容器初始化就要30秒。但我们通过两个关键改造把它变成了优势:第一,将HadoopIndexTask封装成“准实时”服务,用轻量级HTTP Server监听调度指令,收到请求后立即提交YARN任务,避免了传统Cron Job的固定间隔等待;第二,利用Druid的realtimedatasource特性,让HadoopIndexTask只负责“历史数据归档”,而最新1小时数据仍由Kafka Indexing Service承接,形成混合架构。这样既保住了实时性底线,又让HDFS导入彻底摆脱了“定时任务不准”的宿命。
提示:很多团队一看到HadoopIndexTask就皱眉,觉得它“过时”。但请记住,Druid的设计哲学是“为确定性牺牲一点速度”。在金融、广告、IoT等对数据准确性零容忍的领域,一个能精确控制输入范围、失败可秒级重试、结果可100%验证的批处理任务,远比一个TPS更高但语义模糊的流式任务更可靠。
3. 核心细节解析与实操要点:从HDFS路径识别到Druid segment生成的七道生死关
真正让“定时增量导入”落地的,从来不是某个炫技的框架,而是对每一个数据流转环节的死磕。我把整个流程拆解为七个不可跳过的实操关卡,每一关都对应一个真实踩过的坑,以及我们最终沉淀下来的硬核解法。这些细节,官方文档不会写,开源社区讨论帖里也难找,全是血泪换来的经验。
3.1 关卡一:HDFS路径的智能识别——别再用*通配符,用时间戳白名单精准狙击
绝大多数失败的增量导入,根源都在第一步:怎么知道“今天该导入哪些HDFS路径”?新手常犯的错误是写个Cron Job,每天凌晨跑hdfs dfs -ls /data/impression/dt=*/hour=* | grep "2024-06-15",然后把结果喂给Druid。这看似合理,实则埋雷无数:HDFS目录可能有临时文件(_SUCCESS、.tmp)、测试数据(dt=test)、甚至上游写错的日期(dt=2024-06-32)。我们曾因此导入过一份dt=9999-01-01的脏数据,导致Druid Coordinator疯狂尝试加载不存在的时间段,CPU飙到95%持续两小时。
我们的解法是构建HDFS路径白名单生成器。核心逻辑不是“扫描所有目录”,而是“根据调度时间反推合法路径”。假设调度系统在每天02:00触发任务,目标是导入T-1日(即昨天)的完整小时数据。那么合法路径必须满足:dt字段等于yesterday_date,hour字段在00到23之间,且该路径下存在非零大小的Parquet文件。我们用一段Python脚本实现:
from datetime import datetime, timedelta import subprocess import re def generate_hdfs_paths(target_date_str): # target_date_str format: "2024-06-15" paths = [] for hour in range(24): hour_str = f"{hour:02d}" hdfs_path = f"/data/impression/dt={target_date_str}/hour={hour_str}/" # 检查路径是否存在且有有效数据文件 cmd = f"hdfs dfs -ls {hdfs_path} | grep -E '\\.parquet$' | awk '{{if($5>1024) print $8}}'" result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if result.stdout.strip(): paths.append(hdfs_path) return paths # 调用示例:生成2024-06-15的所有有效路径 valid_paths = generate_hdfs_paths("2024-06-15") print(valid_paths)这段脚本的关键在于awk过滤:$5>1024表示只认大小超过1KB的Parquet文件,自动剔除空文件、临时文件。生成的valid_paths列表,就是HadoopIndexTask真正的输入源。我们把这个脚本集成进Airflow DAG,作为第一个task,输出结果存入XCom,后续task直接读取。这样,哪怕上游HDFS不小心多写了一个dt=2024-06-15/hour=99的测试目录,它也绝不会出现在导入列表里。
3.2 关卡二:Druid interval的动态计算——时间窗口不是拍脑袋,而是数学题
HadoopIndexTask配置中,interval参数决定数据写入哪个时间窗口,格式如2024-06-15T00:00:00.000Z/2024-06-16T00:00:00.000Z。新手常直接写死这个值,比如“每天导入就用T00:00/T00:00”。但问题来了:如果某天上游数据延迟,dt=2024-06-15的数据直到03:00才写完,而你的任务02:00就跑了,interval却还是06-15T00:00/06-16T00:00,结果Druid会把这份迟到的数据强行塞进昨天的窗口,导致时间线错乱。
我们的解法是让interval随HDFS路径动态生成。既然我们已经拿到了/data/impression/dt=2024-06-15/hour=14/这样的路径,就可以用正则提取dt和hour,再计算出精确的ISO8601时间区间:
import re from datetime import datetime, timedelta def path_to_interval(hdfs_path): # 从路径提取 dt 和 hour dt_match = re.search(r'dt=(\d{4}-\d{2}-\d{2})', hdfs_path) hour_match = re.search(r'hour=(\d{2})', hdfs_path) if not (dt_match and hour_match): raise ValueError(f"Invalid HDFS path: {hdfs_path}") dt_str, hour_str = dt_match.group(1), hour_match.group(1) # 构建开始时间:dt + hour + :00:00.000Z start_dt = datetime.strptime(f"{dt_str} {hour_str}:00:00", "%Y-%m-%d %H:%M:%S") end_dt = start_dt + timedelta(hours=1) # 格式化为ISO8601 start_iso = start_dt.strftime("%Y-%m-%dT%H:%M:%S.000Z") end_iso = end_dt.strftime("%Y-%m-%dT%H:%M:%S.000Z") return f"{start_iso}/{end_iso}" # 示例:/data/impression/dt=2024-06-15/hour=14/ -> 2024-06-15T14:00:00.000Z/2024-06-15T15:00:00.000Z print(path_to_interval("/data/impression/dt=2024-06-15/hour=14/"))这个函数确保每个HDFS路径都映射到唯一、精确的时间窗口。更重要的是,它让“增量”有了数学意义:interval不再是人为划定的模糊范围,而是HDFS文件物理写入时间的严格镜像。当业务方质疑“为什么14点的数据在15:30才查到”,你可以直接展示path_to_interval的计算过程,证明数据可见性完全由上游写入时间决定,而非Druid本身。
3.3 关卡三:partitionSpec的魔鬼细节——小文件杀手与查询性能救星
HadoopIndexTask的partitionSpec配置,决定了一个HDFS路径下的数据会被切分成多少个Druid segment。新手常忽略它,用默认的{"type": "dynamic"},结果就是:一个87GB的hour=14目录,被切成上千个小segment(每个几十MB),Druid Coordinator要加载几千个文件,内存爆满,查询时遍历segment列表耗时超2秒。
我们的解法是强制按数据量做均匀分片。partitionSpec设为:
{ "type": "hashed", "numShards": 8, "partitionDimension": "ad_id" }这里numShards=8是经过压测确定的黄金值:单个segment控制在1-2GB,既避免小文件泛滥,又保证并行度。partitionDimension选ad_id而非timestamp,是因为广告数据天然存在ad_id热点(头部广告曝光量占80%),用哈希分片能让数据在8个shard间均匀分布。我们对比过dynamic和hashed:前者在2TB数据量下,segment数量达12,000+,Coordinator JVM heap稳定在95%;后者仅1,500个segment,heap维持在40%左右,查询P95延迟从1.8秒降至0.3秒。
注意:
hashed分片要求partitionDimension字段必须存在且非空。我们在HDFS数据落库前,加了一道Spark SQL校验:SELECT COUNT(*) FROM table WHERE ad_id IS NULL,结果大于0则阻断导入流程,强制上游修复数据质量。这是用工程手段倒逼数据治理的典型案例。
3.4 关卡四:tuningConfig的资源精算——别让YARN容器成为瓶颈
HadoopIndexTask的tuningConfig里,maxRowsInMemory、maxRowsPerSegment、numShards等参数,表面看是调优,实则是YARN资源的精确预算。我们曾因maxRowsInMemory=1000000(默认值)导致Map Task频繁OOM:一个Mapper要缓存100万行数据在内存,而YARN分配的Container只有2G内存,GC风暴直接让任务卡死。
我们的解法是用数据特征反推参数。先用hdfs dfs -du -s /data/impression/dt=2024-06-15/hour=14/查出该目录总大小(假设87GB),再用head -n 10000 /tmp/sample.parquet | wc -l估算单行平均字节数(假设256B),得出该小时数据约3.4亿行。然后按公式计算:
maxRowsInMemory = 总行数 / 并行度 / 10(留足10倍缓冲)→340000000 / 8 / 10 ≈ 4.25MmaxRowsPerSegment = 总行数 / numShards→340000000 / 8 = 42.5M
最终tuningConfig设为:
{ "type": "hadoop", "maxRowsInMemory": 4250000, "maxRowsPerSegment": 42500000, "numShards": 8, "partitionsSpec": { "type": "hashed", "numShards": 8, "partitionDimension": "ad_id" } }这套参数让每个Mapper内存占用稳定在1.5G以内,YARN Container 2G内存绰绰有余,任务成功率从82%提升至99.97%。
3.5 关卡五:segmentGranularity的业务对齐——别让Druid替你做时间聚合
segmentGranularity决定Druid segment的最小时间粒度,常见值有HOUR、DAY、MONTH。新手常设DAY图省事,结果发现查“每小时曝光量”时,Druid要扫描一整天的segment,性能极差。我们的解法是让segmentGranularity与业务查询模式严格对齐。
广告归因场景中,90%的查询是“近7天每小时数据”,所以我们设segmentGranularity: "HOUR"。但这带来新问题:一个dt=2024-06-15/hour=14/路径,按理应生成一个14:00-15:00的segment,但如果上游数据写入跨了两小时(比如13:55开始写,14:05写完),HadoopIndexTask可能把部分数据塞进13:00-14:00,部分塞进14:00-15:00,破坏时间连续性。
我们的解法是在HDFS路径层做时间对齐。上游数据写入时,强制要求:hour=14目录下的所有文件,其event_time字段必须落在[2024-06-15T14:00:00, 2024-06-15T15:00:00)区间。我们用Spark写入前加校验:
val df = spark.read.parquet("input_path") val validDF = df.filter( $"event_time" >= "2024-06-15T14:00:00.000Z" && $"event_time" < "2024-06-15T15:00:00.000Z" ) // 如果validDF.count < 原df.count * 0.95,则告警这样,segmentGranularity: "HOUR"就不再是Druid的配置项,而是数据契约的一部分。查询时,Druid只需加载一个segment,P95延迟稳定在200ms内。
3.6 关卡六:druidBeam的版本锁定——一次依赖升级引发的三天瘫痪
Druid的HadoopIndexTask底层依赖druid-beam库,用于序列化/反序列化。某次我们升级Druid集群到0.23.0,顺手把druid-beam从1.2.0升到1.5.0,结果所有HadoopIndexTask提交后卡在ACCEPTED状态,YARN日志显示ClassNotFoundException: org.apache.beam.sdk.values.PCollection。
排查三天才发现:Druid 0.23.0内置的druid-beam是1.3.0,而我们打包的fat jar里是1.5.0,版本冲突导致类加载失败。官方文档对此只字未提,GitHub issue里也无人讨论。
我们的解法是永远用Druid发行版自带的beam版本。我们写了个自动化脚本,在部署HadoopIndexTask前,先从Druid安装目录lib/下提取druid-beam-*.jar,再用jar -tf检查其MANIFEST.MF里的Implementation-Version,最后在打包脚本中强制指定该版本:
# 从Druid lib目录获取beam版本 BEAM_JAR=$(ls $DRUID_HOME/lib/druid-beam-*.jar | head -1) BEAM_VERSION=$(jar -tf $BEAM_JAR | grep MANIFEST.MF | xargs -I {} jar -xf $BEAM_JAR {} && grep "Implementation-Version" META-INF/MANIFEST.MF | cut -d' ' -f2) # 打包时指定版本 mvn clean package -Ddruid-beam.version=$BEAM_VERSION这个看似琐碎的步骤,让我们在后续三次Druid大版本升级中,HadoopIndexTask零故障。
3.7 关卡七:Coordinator的segment加载策略——让新数据秒级可见
即使HadoopIndexTask成功生成segment,业务方仍可能抱怨“数据导入完了,但Dashboard上还是旧的”。根源在Druid Coordinator的加载策略:默认情况下,Coordinator每隔一分钟扫描一次segments表,发现新segment后才触发加载。这一分钟延迟,在实时性要求高的场景就是灾难。
我们的解法是主动推送加载指令。HadoopIndexTask成功后,我们不依赖Coordinator轮询,而是直接调用Druid Coordinator API:
curl -X POST "http://coordinator:8081/druid/coordinator/v1/datasources/impression/segments" \ -H "Content-Type: application/json" \ -d '["2024-06-15T14:00:00.000Z_2024-06-15T15:00:00.000Z_2024-06-15T14:30:00.000Z"]'更进一步,我们写了preLoad脚本,在segment生成后、正式发布前,就调用/druid/coordinator/v1/loadrulesAPI,为该segment预设加载规则,确保它一发布就被分配到足够多的Historical节点。实测下来,从HadoopIndexTask结束到数据在SQL Console可查,时间从60秒压缩到1.2秒。
4. 实操过程与核心环节实现:从Airflow调度到Druid控制台验证的端到端记录
现在,我们把前面所有细节串起来,还原一个真实的“定时增量导入”实操现场。以某次修复dt=2024-06-15数据为例,全程记录每一步操作、命令、返回结果及背后的意图。这不是理想化的教程,而是带着机油味的实战笔记。
4.1 步骤一:Airflow DAG触发与路径发现(02:00:00)
Airflow Scheduler在02:00准时触发DAGdruid_impression_hourly_import。第一个taskdiscover_hdfs_paths执行我们前述的Python脚本:
# Airflow task code def discover_paths(**context): target_date = (context['execution_date'] - timedelta(days=1)).strftime("%Y-%m-%d") paths = generate_hdfs_paths(target_date) # 即3.1节的函数 context['task_instance'].xcom_push(key='hdfs_paths', value=paths) logging.info(f"Discovered {len(paths)} valid HDFS paths for {target_date}") # 日志输出: # INFO discover_paths: Discovered 23 valid HDFS paths for 2024-06-15 # INFO discover_paths: ['/data/impression/dt=2024-06-15/hour=00/', ..., '/data/impression/dt=2024-06-15/hour=22/']注意:hour=23/目录未被发现,因为上游数据写入延迟,02:00时该目录下文件大小为0。这印证了我们路径发现逻辑的有效性——它不是盲目扫描,而是基于数据存在的客观事实。
4.2 步骤二:动态生成HadoopIndexTask JSON配置(02:00:15)
第二个taskgenerate_task_config读取XCom中的hdfs_paths,为每个路径生成独立的HadoopIndexTask配置。以hour=14/为例,生成的JSON如下(精简关键字段):
{ "type": "index_hadoop", "spec": { "dataSchema": { "dataSource": "impression", "parser": { "type": "hadoopyString", "parseSpec": { "format": "parquet", "timestampSpec": {"column": "event_time", "format": "iso"}, "dimensionsSpec": { "dimensions": ["ad_id", "campaign_id", "user_id"] } } }, "metricsSpec": [ {"name": "impression_count", "type": "count"}, {"name": "click_count", "type": "longSum", "fieldName": "click_flag"} ], "granularitySpec": { "type": "uniform", "segmentGranularity": "HOUR", "queryGranularity": "NONE", "intervals": ["2024-06-15T14:00:00.000Z/2024-06-15T15:00:00.000Z"] } }, "ioConfig": { "type": "hadoop", "inputSpec": { "type": "static", "paths": "/data/impression/dt=2024-06-15/hour=14/" } }, "tuningConfig": { "type": "hadoop", "maxRowsInMemory": 4250000, "maxRowsPerSegment": 42500000, "numShards": 8, "partitionsSpec": { "type": "hashed", "numShards": 8, "partitionDimension": "ad_id" } } } }这个JSON被存入S3的druid-task-configs/2024-06-15/目录,供后续调用。关键点在于inputSpec.type: "static"和intervals的精确计算,确保任务只处理目标路径,且时间窗口严丝合缝。
4.3 步骤三:提交HadoopIndexTask到Druid Overlord(02:00:30)
第三个tasksubmit_to_overlord执行curl命令提交任务:
curl -X POST "http://overlord:8090/druid/indexer/v1/task" \ -H "Content-Type: application/json" \ -d @/tmp/task_config_20240615_14.jsonOverlord返回200,body中包含taskId:
{"task":"index_hadoop_impression_2024-06-15T14:00:00.000Z_2024-06-15T15:00:00.000Z_2024-06-15T14:30:00.000Z"}此时,打开Druid Console的Tasks页面,能看到该任务状态为RUNNING,点击查看详情,MapReduce Job ID清晰可见,便于YARN层面追踪。
4.4 步骤四:YARN层面监控与资源确认(02:01:00)
我们立刻登录YARN ResourceManager UI,搜索Job ID,确认:
- Application Master已启动,状态
ACCEPTED→RUNNING; - Map Task数量为8(匹配
numShards=8); - 每个Map Container内存为2048MB,vCores为2,与
tuningConfig预设一致; - Shuffle阶段无
Failed记录,说明网络和磁盘IO正常。
这一步是判断任务是否“健康启动”的黄金标准。如果这里卡在ACCEPTED,一定是YARN资源不足或权限问题;如果Map Task大量失败,则需检查HDFS路径权限或Parquet文件损坏。
4.5 步骤五:HadoopIndexTask日志分析(02:05:00)
任务运行5分钟后,我们SSH到MiddleManager节点,查看日志:
# 查找最新日志 ls -t /opt/druid/var/log/peon/ | head -1 # 输出:2024-06-15T02:05:12.123Z-index_hadoop_impression_2024-06-15T14:00:00.000Z_2024-06-15T15:00:00.000Z_2024-06-15T14:30:00.000Z.log # 查看关键指标 grep "Processed" /opt/druid/var/log/peon/2024-06-15T02:05:12.123Z-*.log # 输出:Processed 42,500,128 rows in 187.324 secondsProcessed 42.5M rows与我们预估的maxRowsPerSegment=42.5M完全吻合,证明分片逻辑生效。同时,日志末尾有:
Successfully built segment [impression_2024-06-15T14:00:00.000Z_2024-06-15T15:00:00.000Z_2024-06-15T14:30:00.000Z] with 8 shards这表示8个shard全部生成完毕,准备发布。
4.6 步骤六:Coordinator主动加载与缓存预热(02:08:00)
任务状态变为SUCCESS后,load_segmenttask立即执行:
# 1. 主动加载segment curl -X POST "http://coordinator:8081/druid/coordinator/v1/datasources/impression/segments" \ -H "Content-Type: application/json" \ -d '["impression_2024-06-15T14:00:00.000Z_2024-06-15T15:00:00.000Z_2024-06-15T14:30:00.000Z"]' # 2. 预热缓存(调用Historical节点API) curl -X POST "http://historical:8083/druid/v2/caches/refresh" \ -H "Content-Type: application/json" \ -d '{"dataSource":"impression","interval":"2024-06-15T14:00:00.000Z/2024-06-15T15:00:00.000Z"}'这两步做完,打开Druid Console的Datasources页面,impressiondatasource的Segments标签页中,新segment状态从WAITING变为LOADING,再变为AVAILABLE,整个过程耗时1.8秒。
4.7 步骤七:终端验证与业务闭环(02:08:05)
最后一步,用Druid SQL Console执行验证查询:
SELECT FLOOR(__time TO HOUR) AS hour, SUM(impression_count) AS imp, SUM(click_count) AS clk FROM impression WHERE __time >= TIMESTAMP '2024-06-15 14:00:00' AND __time < TIMESTAMP '2024-06-15 15:00:00' GROUP BY 1返回结果:
hour | imp | clk -------------------------|---------|----- 2024-06-15 14:00:00.000 | 42500128| 1275003imp=42,500,128与日志中`Processed 4