1. 为什么要在腾讯云上做实时数仓
1.1 项目背景与核心需求
前阵子接到一个项目,要在腾讯云上搭一套大数据实时数仓,起初以为把离线数仓的经验平移过来就行,结果落地过程中踩了不少坑。业务侧原来的数仓是 Hive + Spark 跑 T+1,报表和看板都是前一天的数据。后来产品经理提了一堆实时需求:大屏指标要秒级刷新、运营活动要实时监控投放效果、风控要实时识别异常行为。离线模式根本顶不住,所以必须引入一条“数据从产生到可分析只有几十秒”的链路,这就是实时数仓要解决的核心问题。
项目目标拆下来其实就两条。第一,核心业务指标从分钟级延迟降到 10 秒以内,至少要做到“准实时”。第二,查询不能只支持预设好的汇总指标,业务方还需要临时下钻、按任意维度组合分析,这就意味着底层存储不能只是一堆预先算好的 Redis Key,必须有一个能扛住灵活查询的 OLAP 引擎。
为什么选腾讯云而不是自建机房,当时权衡下来有三个原因:一是团队没有专职大数据运维,托管服务能省掉组件部署、版本升级、故障恢复这些体力活;二是腾讯云的 CKafka、流计算 Oceanus、StarRocks 这些产品之间在同一个 VPC 里天然打通,链路配置非常顺畅;三是后续要做离线回填和交叉校验,直接对接 COS 和 EMR 就能搞定,不用额外写一堆数据同步脚本。
当然,托管不等于不用懂原理。恰恰相反,越是被托管服务包住,越要清楚底层运行逻辑。比如 Flink 的 Checkpoint、状态后端、反压这些概念,如果不理解,出了问题在控制台上看半天也定位不到原因。后面我会结合实操一个一个说。
1.2 技术选型:Kappa 还是 Lambda
实时数仓绕不开两种典型的架构流派:Lambda 和 Kappa。Lambda 是“离线 + 实时”两条链路并行,最终做结果合并;Kappa 是只保留实时一条链路,数据需要重算时通过 Kafka 这类消息队列重放。我们最终没有选纯 Kappa,也没有完全照搬 Lambda,而是用了“以 Kappa 为主、Lambda 为辅”的混合方式。
为什么不全部上 Lambda?原因很简单:维护两套逻辑的成本太高。同一个指标,离线一个口径,实时一个口径,两边数据对不上时排查问题极其痛苦。业务方不会关心你是离线算的还是实时算的,他们只看到数字不对。而且 Lambda 链路里实时和离线需要分别做数据质量监控,资源和人力都是双份。
为什么又不完全 Kappa?因为 Kafka 的存储成本摆在那里,不可能把所有历史数据永久保留。有些指标要回溯 30 天甚至更久,比如活动效果复盘、月度趋势分析,这时候让实时链路重新消费 Kafka 的回放成本太高。所以我们保留了原有的 Hive 离线链路,专门做长周期回溯和结果校验。这样实时链路负责秒级、分钟级的高频指标,离线链路负责日级、月级的历史分析,两条链路通过每天的对账任务互相校验口径。
具体到腾讯云上的组件选型,当时定的是:
- 消息队列:CKafka,负责承接业务 binlog 和埋点日志;
- 实时计算:流计算 Oceanus,支持 Flink SQL,省去自己维护 Flink 集群;
- OLAP 存储:StarRocks,承担实时明细和汇总指标查询;
- 离线旁路:COS + EMR,用于数据回填、长期存储、对账。
这套组合用起来比较顺的关键原因,是各组件之间基本都提供了官方 Connector,Flink SQL 写起来很顺手。但要注意的是,托管服务的版本会跟随云厂商更新,尽量不要在 SQL 里用太新的语法特性,否则每次平台升级都可能出现兼容性问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构设计与核心组件解析
2.1 整体链路拓扑
实时链路整体长这样:业务数据库通过 Canal/Debezium 同步 binlog 到 CKafka,客户端埋点日志通过日志服务直接推送到 CKafka;流计算 Oceanus 上的 Flink SQL 作业消费 CKafka,做数据清洗、维表关联、窗口聚合;结果写入 StarRocks;最后提供给 BI 看板或 API 查询。
这里每个环节都有明确的职责划分。CKafka 是缓冲层,负责削峰填谷,避免业务洪峰直接打到实时计算层。Flink 是计算层,承担最核心的清洗、关联、聚合逻辑。StarRocks 是存储和查询层,既要能支撑高并发点查,也要能跑复杂的多维聚合。还有一个很容易被忽视的环节是维表关联,我们用的是 Redis + HBase 搭配:Redis 放高频小维表,比如用户等级、渠道类型、城市信息;HBase 放大体积的状态维表,比如商品完整画像、历史标签。为什么不把维表全部塞进 Flink 内存?因为大数据量维表会导致状态膨胀,checkpoint 压力大,很容易 OOM,而且维表更新时内存数据没法及时感知。
实际跑起来之后,第一个让我印象深刻的坑是消费倾斜。最开始创建 CKafka topic 时分区数拍脑袋设了个默认值,Flink 并行度也直接用默认值,结果高峰时期有个别分区 lag 一直涨,其他分区很空闲。原因是一个热门店铺的订单量远大于普通店铺,Hash 规则落到固定分区,导致单分区积压。后来我们重新评估了上游数据量分布,把分区数和并行度调整到合理范围,消费倾斜才缓解。所以“分区数 + 并行度”这对参数,真的不能随便填。
2.2 存储引擎选择:ClickHouse 还是 StarRocks
实时数仓的 OLAP 引擎,当时就纠结 ClickHouse 还是 StarRocks。两个都是列式存储,聚合性能都很强,但在实际业务场景里的表现差异还是蛮大的。
ClickHouse 的核心优势是单表聚合性能极致,特别是大宽表场景,吞吐高、压缩比好、存储成本低。它很适合“明细表已经宽到不需要 JOIN”的分析场景,一条 SQL 对所有维度做 group by,速度非常快。但它的短板也很明显:并发能力一般,复杂多表 JOIN 资源占用高,运维上要注意 MergeTree 的 part 数量,part 一多查询性能就会下降。
StarRocks 是 MPP 架构,对标准 SQL 的支持更完整,主键模型可以做 upsert,非常贴合“实时明细要频繁覆盖更新”的场景。比如订单状态从“已支付”变成“已退款”,StarRocks 能根据主键直接更新这一行;同时它也支持高并发点查,适合给业务方直接做 Dashboard。缺点是复杂查询如果写得不合理,内存占用会很大,需要做好分桶和分区裁剪。
我们这个项目最后选了 StarRocks,核心原因是业务查询不只做聚合统计,还要实时查明细、按订单号追踪状态变化。ClickHouse 处理更新场景比较绕,一般要用 ReplacingMergeTree 加版本号,查询时还必须加 FINAL 关键字,否则可能查到重复行。StarRocks 的主键模型天然解决了这个问题,省了很多麻烦。另外腾讯云上的 StarRocks 托管集群会自动处理 FE/BE 部署和监控,运维压力小很多。
2.3 数据一致性与迟到数据处理
实时数仓真正难的从来不是“能不能算出来”,而是“算得准不准”。我们踩过的最大坑是上游数据乱序和迟到。用户先下单,后取消,这两个事件如果顺序颠倒,订单量、成交金额、退款率全都会错。
Flink SQL 里处理乱序的标准做法是 WATERMARK。我们给每个事件都加上业务时间字段,然后用 WATERMARK 声明最大允许乱序时间。比如业务上允许延迟 10 秒,watermark 就设为当前观察到的最大事件时间减去 10 秒。这个值不能拍脑袋,设太大窗口结果出得晚,设太小又会有大量迟到数据被丢弃。我们当时拉了一批线上数据做分位数统计,发现 95% 的事件延迟在 10 秒内,最后把 watermark 时长设成了 15 秒,算是兼顾时效和准确率。
窗口已经触发之后才到的迟到数据怎么办?我们的策略是“容忍但不重算”。也就是说,主窗口计算结果一旦发出,迟到的数据不再回去改旧结果,而是把它们重新格式化后发送到 Kafka 的旁路 topic,由单独一个“迟到补偿作业”去消费,更新 StarRocks 里对应主键的数据。这样做的好处是主链路不会因为少数迟到数据而阻塞延迟,业务指标又能通过补偿机制逐步逼近真实值。代价是实现复杂度增加,但实时数仓做到后期,这种细节才是真正拉开差距的地方。
3. 环境准备与参数配置实操
3.1 腾讯云资源规划与网络配置
在云上搭大数据集群,第一步就是网络规划,这步做不好后面全是麻烦。我们当时把 CKafka、Oceanus、StarRocks 放在同一个 VPC 下的不同子网,通过安全组做访问控制;对外只暴露一台跳板机,其他所有服务都走内网访问。这样既降低了数据泄露风险,也避免了公网流量费用。
安全组配置一定要坚持“最小授权”原则,不要想着把端口全放开省事。比如 StarRocks 的 FE 查询端口 9030、BE 的 8040/8060 端口,只对需要访问的 BI 服务和后端应用开放就行。我看到有些人在网上问“腾讯云如何开放所有端口”,真的不建议这么干。把所有端口暴露到公网上,等于把数据库裸奔,业务没有任何场景需要这样做,反而会引来扫描和爆破。
集群部署策略上,Master 和计算节点要分开规格。StarRocks 的 FE 至少两台,做高可用,避免单点故障;BE 按数据量和查询并发决定。如果只是测试环境,FE 和 BE 可以暂时共用节点,但生产环境建议严格分离。资源预留方面不要“刚好卡到业务峰值”,要给 Flink 的状态管理、checkpoint、临时查询留出余量,否则高峰期稍微抖动一下就会出问题。
3.2 CKafka 主题与分区数设置
Kafka 分区数是个需要认真算的参数。分区数决定了下游消费并行度上限,也影响 Broker 端的元数据开销。不是越多越好,也不是用默认值就行。
我当时用的估算公式很简单:
code复制分区数 = 目标吞吐(MB/s) / 单分区可支撑吞吐(MB/s)
单分区在普通 SSD 实例上大约能支撑 10~20MB/s,还要考虑副本同步损耗。比如有个 topic,峰值写入 200MB/s,副本数 2,保守一点按单分区 10MB/s 算,分区数需要 20 到 30。我们最后定了 24 个分区,Flink 并行度也是 24,消费端基本没有瓶颈。
其他容易忽略的参数也提一下。retention.ms 默认一天,如果下游有重跑需求建议至少设到 3 天;min.insync.replicas 这个参数要结合 producer 的 acks=all 一起配置,避免副本同步不及时导致丢数据;如果单条消息比较大,还要检查 message.max.bytes 上下游是否一致,否则会出现消息写进去了但消费端一直解析报错的情况。
3.3 流计算 Oceanus 作业参数配置
Oceanus 是托管版 Flink,创建作业时除了写 SQL,还要仔细设置资源参数。我们常用的参数配置如下:
- 并行度:尽量和上游 Kafka 分区数保持一致,避免消费不足或并行度浪费;
- Checkpoint 间隔:一般 60 秒左右,太短会增加持久化压力,太长故障恢复时回溯数据多;
- 状态后端:托管默认是 RocksDB,适合大状态;状态小且访问频繁可以用内存,但要注意堆内存限制;
- 状态 TTL:不需要永久保存的状态一定要设置过期时间,比如一天的 UV 去重集合,否则状态无限增长,内存迟早爆掉。
下面是一个 Flink SQL 写 StarRocks 的简版示例,核心目的是展示链路连接方式,实际生产环境要加载对应 connector,并且配置项会更多:
sql复制CREATE TABLE kafka_order (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
status STRING,
event_time TIMESTAMP(3) METADATA FROM 'timestamp',
WATERMARK FOR event_time AS event_time - INTERVAL '15' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'ods_order',
'properties.bootstrap.servers' = 'xxxx',
'properties.group.id' = 'flink_order_group',
'scan.startup.mode' = 'earliest-offset',
'format' = 'json'
);
CREATE TABLE starrocks_sink (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
status STRING,
event_time TIMESTAMP(3)
) WITH (
'connector' = 'starrocks',
'jdbc-url' = 'jdbc:mysql://fe_host:9030',
'load-url' = 'fe_host:8030',
'database-name' = 'dwd',
'table-name' = 'dwd_order_detail',
'sink.properties.format' = 'json',
'sink.properties.strip_outer_array' = 'true'
);
INSERT INTO starrocks_sink
SELECT order_id, user_id, amount, status, event_time
FROM kafka_order
WHERE amount > 0;
这段 SQL 里加了 watermark 处理乱序,同时用 WHERE amount > 0 把脏数据过滤掉。实际生产中还有维表关联、去重、窗口聚合这些更复杂的逻辑,但这个链路雏形已经足够说明问题:数据从 Kafka 到 StarRocks 的实时通道,核心就是一张 source 表、一张 sink 表、一条 insert 语句。
3.4 结果表设计与建表语句
StarRocks 建表前要想清楚用哪种模型。我们有两类结果表,一类是实时明细,比如订单明细,需要覆盖更新,用 Primary Key 模型:
sql复制CREATE TABLE dwd_order_detail (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
status STRING,
event_time DATETIME,
update_time DATETIME
) PRIMARY KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 24
PROPERTIES ("replication_num" = "2");
这类表的好处是相同 order_id 的数据只保留最新一条,Flink 重复写入也能通过主键去重。另一类是实时聚合指标,比如小时级订单汇总。我们没有把它建成聚合模型,而是用明细模型加物化视图,因为业务方经常要换维度看数据,明细模型更灵活。
分桶数计算经验是按照单桶数据量在 1GB 左右来估算,表总数据量除以 1GB 取整。如果分桶太多,查询时 scan 的 task 数量增加,反而影响性能;分桶太少又没法充分利用并发。订单明细表先按 24 个桶,后面根据数据增长再调整。
4. 核心环节实现:从数据接入到指标看板
4.1 数据接入与清洗逻辑
数据接入是实时数仓最容易出问题的一环。上游表结构变更、字段缺失、NULL 和类型不一致,都会在 Flink SQL 里变成运行异常或者脏数据。
我们当时做了一个统一的清洗层,对应离线数仓的 ODS 层。所有进入 Kafka 的数据都先经过一个 Flink SQL 作业,做字段规整、非法值过滤、枚举值映射。比如订单状态字段,上游有 0、1、2,也有 success、fail 这种字符串,我们统一映射成标准枚举;金额字段出现负数或空值,直接过滤掉,同时把异常数据写到旁路 Kafka,方便事后排查。
一个很实用的建议是:清洗作业和指标计算作业分开。清洗作业负责把 ODS 层 JSON 解析成宽表结构,输出到 DWD 层 Kafka;指标作业只消费 DWD 层,逻辑更简单。如果所有清洗逻辑都堆在指标作业里,后期每个计算逻辑调整都会影响核心链路,风险非常大。分层带来的好处是,哪一层出问题就能快速定位,不用在一个巨型 SQL 里翻上下文。
4.2 实时指标计算案例
看一个具体案例:统计每个店铺每分钟的成交金额和订单量。假设 DWD 层的订单流已经清洗好,直接在 Flink SQL 里做窗口聚合:
sql复制CREATE TABLE kafka_dwd_order (
shop_id BIGINT,
order_id BIGINT,
amount DECIMAL(10, 2),
paid_time TIMESTAMP(3),
WATERMARK FOR paid_time AS paid_time - INTERVAL '15' SECOND
) WITH (...);
CREATE TABLE starrocks_sink_agg (
shop_id BIGINT,
window_start TIMESTAMP(3),
window_end TIMESTAMP(3),
order_cnt BIGINT,
order_amount DECIMAL(20, 2)
) WITH (...);
INSERT INTO starrocks_sink_agg
SELECT
shop_id,
TUMBLE_START(paid_time, INTERVAL '1' MINUTE) AS window_start,
TUMBLE_END(paid_time, INTERVAL '1' MINUTE) AS window_end,
COUNT(order_id) AS order_cnt,
SUM(amount) AS order_amount
FROM kafka_dwd_order
GROUP BY shop_id, TUMBLE(paid_time, INTERVAL '1' MINUTE);
这段 SQL 看起来简单,调优点却不少。如果店铺数量很大,每分钟每个店铺一行,写入 StarRocks 的数据量可能不小,此时最好做“两阶段聚合”:先做 10 秒粒度的小窗口聚合,然后在外层做 1 分钟聚合,降低下游写入压力。另外如果某些头部店铺流量特别集中,group by 时会出现数据倾斜,可以在 Key 上加随机盐,但实时指标加盐会破坏最终结果的准确性,需要非常慎重,我们最后没有加盐,而是通过增大并行度来缓解。
4.3 数据校验与监控告警
实时数仓必须做监控,不然半夜数据错了都不知道。我们主要盯三类指标:Kafka 消费 Lag、Flink Checkpoint 状态、StarRocks 导入积压。
- Kafka 消费 Lag:Lag 持续上涨说明 Flink 消费能力不足,需要扩容并行度或优化 SQL;
- Flink Checkpoint:失败频繁通常意味着状态后端压力大,或者作业内存不足;
- StarRocks 导入积压:写入慢会直接影响查询性能。
腾讯云监控里可以直接看 CKafka 消费组 Lag 和 Oceanus 作业指标。我们在 StarRocks 里还做了一个自检表,每分钟统计各结果表新增行数,如果某个表行数突然掉到 0 或异常放大,就触发告警。
离线校验每天做一次,用离线链路跑昨天的聚合结果,和实时结果表做对比,误差超过 1% 就告警。实时和离线口径多少会有差异,所以阈值不要设成 0,否则全是误报。这个对账机制虽然简单,但能发现很多隐蔽问题,比如某个 Flink 任务在深夜发生过重启、导致一段时间数据重复或丢失,如果没有对账,可能好几天都不会被发现。
5. 常见问题与排查技巧实录
5.1 环境与基础服务的高频坑
搭建过程中遇到的一些环境问题,虽然不是实时数仓的核心,但很影响进度。第一个是 Redis 密码修改之后重启不生效。很多人改了 requirepass,然后执行 restart,结果用新密码连不上,老密码反而能连。原因多半是 Redis 配置存在多个 include 文件,或者启动脚本指定了不同的 conf 路径,你改的并不一定是实际加载的那个。排查时先用 redis-cli CONFIG GET requirepass 看当前生效的密码,再确认启动命令里的配置文件路径,最后修改正确文件并重启。
第二个是 Docker 推送镜像到腾讯云容器镜像服务失败。常见原因是没先登录目标仓库,或者镜像 tag 没改成仓库完整路径。正确做法是:
bash复制docker tag my-image:latest ccr.ccs.tencentyun.com/namespace/my-image:v1.0
docker login ccr.ccs.tencentyun.com
docker push ccr.ccs.tencentyun.com/namespace/my-image:v1.0
如果报权限错误,检查是否创建了访问凭证,以及当前账号在该命名空间下有没有读写权限。
第三个是注册腾讯云时提示“网络环境异常”。这个问题通常和本地出口 IP 或浏览器环境有关。建议先换一个网络重试,比如手机热点,或者换 Chrome 无痕窗口,关闭所有浏览器插件,重新走注册流程。不要相信网上所谓的“绕过验证”或“修改 IP”操作,既不安全,也违反平台规则。
5.2 实时链路延迟与数据丢失排查
最常见的故障是看板出数变慢,或者数据少了一块。排查要按链路顺序来,不能跳着看。
第一步查 Kafka lag。如果某个分区 lag 持续增长,大概率是 Flink 某个 subtask 消费不过来,需要检查并行度是否足够,是否存在热点 key 导致倾斜。第二步看 Flink 作业反压和 Checkpoint。如果某个算子反压很高,定位到具体算子,看是维表查询慢还是写 StarRocks 慢。第三步看 StarRocks 导入侧。Flink 写 StarRocks 如果导入失败会重试,BE 磁盘满了会一直失败,最终表现为结果数据变少。
我们的排查流程基本就是“生产者 -> Broker -> 消费者 -> 写入端”顺序看指标。先看源头有没有数据,再看中间有没有堆积,最后看写入端有没有报错。只要确定“数据卡在哪一环”,问题就解决了一半。还有就是数据延迟不能只看消费端时间戳,业务事件时间和处理时间之间的差值才是最真实的延迟指标,建议在 Flink 作业里加上 processing time 和 event time 的差值监控。
5.3 性能优化实战
有几个优化点见效很快。第一,维表关联尽量开启缓存和异步。Flink SQL 维表关联如果不配置,每条数据都会查一次 Redis/HBase,吞吐会非常差。设置 lookup cache 的最大行数和 TTL,把热点维表缓存在本地;同时开启异步访问,并发发送查询请求,吞吐量往往能翻几倍。
第二,StarRocks 分桶数和分区裁剪要重视。我们一开始分桶数设置过高,导入很多小文件,查询反而变慢。后来按数据量重新调整分桶,并给大表做时间分区,让查询只扫描需要的分区,效果很明显。
第三,Flink 作业资源不能只按数据量算,还要看 SQL 复杂度。大状态算子比如去重、窗口聚合非常吃内存,RocksDB 状态下最好给 TaskManager 多留 off-heap 内存。实际操作中,我们调整了每个 TaskManager 的 heap 和 off-heap 比例,用 off-heap 来跑 RocksDB,作业稳定性提升明显。
6. 项目复盘与后续演进
6.1 从0到1的经验总结
如果回到项目起点,我会在开工之前先把数据质量规则定好,而不是等链路搭完再补。实时链路一旦跑起来,再去补清洗逻辑非常麻烦,因为 Kafka 里的消息已经消费过了,重放和回溯都有成本。
另一个体会是托管服务虽然省心,但一定要熟悉底层原理。Oceanus 帮你管好了 Flink,但如果不懂 checkpoint、watermark、反压,出了问题还是无从下手;StarRocks 平台只是帮你部署了集群,表模型、分桶、物化视图还是需要自己设计。托管的是运维,不是架构设计。
对准备找大数据开发岗位的朋友,这类项目经验很适合写进简历:有实时数仓架构、有 Flink SQL、有 OLAP 引擎选型、有数据质量保障。面试官可以顺着任何一个方向往下深挖,关键是你要能说清楚每个选择背后的原因,而不是只会堆名词。
6.2 后续可以扩展的方向
项目上线之后,我计划往几个方向继续迭代。一是流批一体,把离线的 Hive 表换成 Iceberg 或 Hudi 这类数据湖表,让实时和离线共用同一份数据,从根上减少口径不一致。二是加强数据治理,包括血缘关系、指标字典、数据质量规则。实时数仓发展到后期,真正难的不再是技术链路,而是如何让数据可信、可用。
三是有必要把部分复杂逻辑从 Flink SQL 切到 DataStream API。Flink SQL 开发效率高,但某些涉及多流 join、复杂状态管理的场景,用 API 写更灵活。四是可以考虑跨集群容灾,目前核心链路还只有单地域部署,如果预算允许,可以做成双活或备份恢复,降低故障恢复时间。
最后再分享一个小技巧:无论你用腾讯云还是其他云厂商,都要做好成本账单的异常监控。大数据集群一旦跑起来,资源费用很容易超出预期。我们后来给 StarRocks 和 Oceanus 都设置了定时伸缩策略,业务低峰期缩容,高峰期扩容,整体成本降了大概 30%。这个优化比任何技术细节都更直接。
做完这套实时数仓,我最大的感受是:它不是用几个组件拼在一起就完事儿的,而是需要你对消息队列、流计算、OLAP 引擎、数据质量都有足够的理解,才能让整条链路稳定、准确、可用地跑下去。希望这篇记录能给正在做同类项目的朋友提供一些参考,少踩几个我们踩过的坑。
