1. 广电大数据到底特殊在哪:先把场景拆明白
广电这块的数据,跟我们平时在互联网公司看到的那套用户行为数据,骨子里是两回事。互联网讲的是点击、曝光、转化,广电讲的是开机、换台、停留、回看。听起来差不多,但数据的产生方式、粒度、时效要求完全不一样。机顶盒是一台台部署在家庭里的终端设备,它没有浏览器,没有App那种灵活的埋点能力,很多行为数据是靠固件里的回传模块定时上报的。这就决定了广电大数据的第一特性:数据来源被设备能力卡死,你能采到什么,很大程度取决于机顶盒型号和固件版本。省级网络公司动辄几百万到上千万台终端,横跨十年以上的设备代际,新旧机器的回传能力天差地别,这是做广电数据可视化首先要接受的现实。
第二个特性是时效分层极端明显。直播收视这种数据,运营部门要求你分钟级甚至秒级看到频道份额的变化,晚八点黄金档一个节目崩了,得马上知道。但用户画像、留存分析这类数据,T+1 跑完全够了。一个平台里同时存在"准实时"和"离线"两条完全不同的链路,这跟纯互联网数仓的架构思路是有分歧的。你要是拿一套纯离线调度去硬扛直播看板,铁定翻车;反过来把画像也塞进实时链路,成本又高得离谱。
第三个特性是指标口径的行业惯性。收视率、到达率、人均收视时长、频道份额这些词,广电行业内部有约定俗成的算法,甚至跟传统抽样调查时代的定义有历史延续性。你新做一套大数据口径,如果跟老口径差太远,业务方第一时间就不信任你。所以做广电数据可视化,先把口径对齐,再谈技术实现,这个顺序不能反。
我接触到的大多数广电大数据项目,需求方其实就是几拨人:频道编排的想优化节目单,广告经营的想证明投放价值,网络运维的想盯住终端在线率和故障分布,领导层想在大屏上一眼看到全省态势。这四拨人的诉求完全不同,共同点只有一个——都要看图,不看表。这就是为什么"数据可视化"在这个领域不是锦上添花,而是刚需。你给他们一堆 CSV,没人看;你给他们一块能自动刷新的可视化大屏,会议室的讨论效率立刻不一样。
2. 整体架构设计:从机顶盒到大屏的完整链路
2.1 五层架构的选型逻辑
广电大数据的链路,我一般拆成五层:采集层、接入层、存储计算层、服务层、可视化层。这个分层不是为了好看,而是为了让每一层的故障能隔离。采集层出问题,最多是数据延迟,不至于把大屏拖垮;可视化层崩了,底层的数据资产还在。
采集层就是机顶盒上的回传模块。说实话,这一层你能动的东西最少,因为改固件要走灰度、要走审批,周期极长。所以常规做法是在上报网关上做尽可能多的加工和补全,把设备端当"哑终端"对待。网关通常用 Nginx 做接入,后面挂一个 Java 或者 Go 写的上报服务,负责协议解析、字段清洗、服务端打时间戳。这里有个关键设计——时间戳一定要用服务端时间,后面我会专门讲为什么。
接入层选型上,Kafka 基本是标配。它的价值在于削峰填谷。广电的数据有个很典型的潮汐特征:凌晨两点到早上六点几乎没量,晚上七点到十一点是洪峰,两个时段能差十几倍。如果采集端直接写数据库,晚高峰直接把库打爆。Kafka 把洪峰缓冲下来,下游按自己的节奏消费,这是最朴素也最有效的解耦。
存储计算层分两条线。实时线用 Flink 消费 Kafka,做窗口聚合,结果写进 ClickHouse 或者 Apache Doris 这类 OLAP 引擎,供大屏秒级查询。离线线走 HDFS + Hive,用 Spark 跑 T+1 的全量指标,结果回写到同一套 OLAP 里或者单独建离线结果表。你可能会问,为什么实时和离线不合并成一套?答案是成本。全量历史明细放 ClickHouse,存储成本会让人肉疼,而放 Hive 便宜十倍不止。分工明确,各干各擅长的事。
服务层就是一层薄薄的 API 网关加缓存。大屏的查询接口统一从这里出去,热点数据用 Redis 挡一道。这里必须强调一点:可视化层的接口一定要做聚合,绝不能把明细透传给前端。我见过太多项目,前端直接查 500 万行明细再在前端做聚合,大屏一刷新浏览器就转圈,这种设计一律推倒重来。
可视化层现在的主流是 ECharts 配 Vue 或 React,大屏场景会额外用到 ECharts GL 做三维地球和飞线效果。企业级数据可视化平台也有现成的商业方案,但对于广电这种指标口径特殊的行业,我倾向于自研或者基于开源二次开发,把口径牢牢攥在自己手里。
2.2 集群部署策略与容量估算
广电大数据的集群部署策略,核心矛盾是成本和数据保留周期。原始明细数据保留多久?我的经验是:原始日志保留 90 天足够,聚合后的分钟级指标保留 1 年,天级指标和画像保留 3 年。为什么要这么切?因为运维排障一般看最近一个月,业务复盘看最近一年,用户画像看长期趋势。按这个梯度设计存储,能省下一大笔。
容量估算得拿真实数字说话。假设一个省级平台有1000 万台终端,每台机器每天产生约150 条有效事件(开机、心跳、换台、点播、关机),那就是15 亿条/天。每条原始事件压缩前大约 200 字节,压缩后按 1/4 算,日增原始数据约75GB,加上副本系数 3,HDFS 实际占用约225GB/天。保留 90 天,大概20TB。这个规模用十来台普通配置的服务器就能撑住,不需要盲目堆机器。
Kafka 的容量要按峰值算,不能按均值。还是 1000 万终端,晚八点黄金档假设有25%的终端同时在线,也就是 250 万台,每台每分钟回传 1 条事件,折算下来约4.2 万 TPS。这个数字必须留冗余,我一般按峰值再乘以3 倍来配,也就是目标吞吐12 万 TPS。单个 Kafka 分区的写入吞吐在普通机械盘上大约 1 万 TPS 量级,那么分区数至少12 到 16 个,实际我会开24 个,给未来增长和突发留空间。分区数不是越多越好,太多会增加元数据管理和重平衡的开销,这个度要拿捏。
Flink 的并行度跟 Kafka 分区对齐是最省心的,24 个分区配 24 个并行度,上下游天然对齐,不会出现某个算子空转。如果开了窗口聚合,记得把 watermark 的延迟设成能容忍乱序的程度,广电设备时钟漂移严重,乱序数据很常见,watermark 设 30 秒到 1 分钟都算正常。这里就有个坑:watermark 设太大,实时性变差;设太小,窗口外的迟到数据被丢弃,指标就会偏低。我的做法是允许迟到并输出到侧输出流,把迟到数据单独存一份,人工核对时能看到丢了什么,而不是无声无息地消失。
3. 数据采集与数仓建模的实操要点
3.1 采集层的埋点与回传设计
机顶盒埋点这件事,最大的难点不是技术,是设备多样性。同一批机顶盒可能来自五六个厂商,固件版本几十种,上报字段的名字、编码、单位都可能不一样。你可以这样理解:这就像收集几十个方言区的人写的日记,格式各异,你得先统一成普通话。
我的处理原则是在接入层做标准化映射,而不是在设备端改。具体做法是维护一张字段映射表,按"厂商 + 固件版本"维度匹配,把各家报上来的原始字段名翻译成统一字段。举个例子,有的机器把频道号叫channel_id,有的叫ch_no,有的叫service_id,映射表里全部归一成channel_id。这样设备端不用动,网关侧加一张配置表就能解决大部分脏活。
-- 字段标准化映射表结构示例 CREATE TABLE dim_device_field_mapping ( vendor_code VARCHAR(32), -- 厂商编码 firmware_ver VARCHAR(32), -- 固件版本 src_field_name VARCHAR(64), -- 原始字段名 std_field_name VARCHAR(64), -- 标准字段名 transform_rule VARCHAR(256), -- 转换规则,如单位换算 valid_from DATE -- 生效日期 );回传频率也要设计。开机、关机这种状态变更类事件,实时上报;心跳类的,五分钟一次足够;换台这种高频事件,如果每次都实时上报,量会爆炸,通常做法是本地缓存 30 秒,合并后上报。这里有个现实取舍:合并上报能大幅省流量省压力,代价是你会丢失 30 秒内的精确换台次数,只能得到"看过哪些频道"。如果你的指标需要精确换台频次,这部分数据就得牺牲存储和带宽去换。
提示:机顶盒本地时钟普遍不准,偏差几分钟甚至几小时都常见。所以事件里的
event_time只能作为参考,真正的分区时间一律用服务端接收时间。我吃过这个亏——某天一批机器固件时钟跳变,按设备时间分区,数据全塞进了一个错误的小时分区里,排查了半天。
3.2 数仓分层与核心指标口径
广电数仓我一般按ODS → DWD → DWS → ADS四层来建。ODS 层就是原始回传落地的落地表,除了标准化字段,尽量别加工。DWD 层做清洗、去重、会话切割。会话切割是关键,什么是"一次收视会话"?我的定义是同一终端、同一频道、连续观看,中间切换间隔不超过 30 秒,算一次会话。超过 30 秒就断成两次。这个 30 秒不是拍脑袋,是因为机顶盒回传合并窗口就是 30 秒,跟你自己采集的节奏对齐才不会自相矛盾。
DWS 层做轻度聚合,比如按"天 + 频道 + 区域"汇总收视时长。ADS 层就是给大屏用的结果表,直接对应一个图表。这样分层的好处是,口径逻辑都压在 DWD 和 DWS 里,ADS 只是搬运,改口径不用动前端。
核心指标的口径,我用表格给你列清楚,这些是广电业务里最容易扯皮的地方:
| 指标名称 | 计算口径 | 常见坑 |
|---|---|---|
| 开机率 | 当日有开机行为的终端数 / 总有效终端数 | 分母要把长期离网但未销户的终端剔除,否则分母虚高 |
| 频道份额 | 某频道收视时长 / 所有频道收视时长 | 分母是否含回看和时移,行业内有争议,必须写清楚 |
| 到达率 | 看过该频道至少 1 分钟的终端数 / 总终端数 | "1 分钟"这个门槛各家不同,要对齐 |
| 人均收视时长 | 总收视时长 / 有收视行为的终端数 | 分母到底是全部终端还是活跃终端,差一倍 |
| 点播转化率 | 点播成功次数 / 进入点播页次数 | 页面进入事件如果没埋,这个指标就废了 |
这张表看着简单,实际项目中每一条都能引发一场会议争论。我的建议是每个指标在维表里配一段口径说明,跟代码一起版本管理,谁改了口径都要留痕。业务方问起来,能直接甩出定义和历史变更记录,省得反复解释。
3.3 用户画像标签的落地
广电的用户画像,跟互联网电商的画像逻辑不太一样。电商画像冲着转化去,广电画像更多冲着收视偏好和生命周期管理去。标签体系我通常分成三类:基础属性标签(区域、终端型号、入网时长)、行为标签(偏好频道类型、观看时段偏好、点播活跃度)、状态标签(活跃、沉默、流失预警)。
行为标签的生成,核心是权重衰减。一个人三个月前爱看体育,最近天天看电视剧,那他的偏好标签应该以近期为主。我一般用时间衰减公式:近期行为权重按e^(-λt)衰减,t 是天数差,λ 取 0.02 到 0.05 之间。取 0.02 的话,30 天前的行为权重还有约 0.55,取 0.05 的话只剩 0.22。具体取多少,取决于你希望画像反映多久的偏好——想反映近期趋势就取大一点,想反映稳定偏好就取小一点。这个参数没有标准答案,得拿业务反馈来调。
标签算出来之后,别急着上大屏。先做小范围验证,挑几个已知特征的区域做对照,看看标签跟实际是否吻合。我见过团队直接全量上线,结果"高价值用户"标签圈出来一堆只开机不看的僵尸终端,就是因为权重没处理,把历史行为算得太重了。
4. 核心指标计算与可视化实现
4.1 收视率与活跃度的计算逻辑
收视率的实时计算是广电大数据的皇冠明珠,也是技术难点最集中的地方。先说离线版,思路简单粗暴:把 DWD 层的收视会话表按"频道 + 时间片"聚合,算出每个时间片每个频道的观看终端数,再除以总终端数。这个用 Hive 或 Spark SQL 一把梭就能出来。
-- 离线频道分钟级收视份额计算示例 INSERT INTO ads_channel_minute_share PARTITION (dt='${dt}') SELECT channel_id, time_slice, -- 分钟级时间片 COUNT(DISTINCT terminal_id) AS view_cnt, -- 该分钟观看终端数 ROUND( COUNT(DISTINCT terminal_id) * 1.0 / SUM(COUNT(DISTINCT terminal_id)) OVER (), -- 全场总观看数作分母 4 ) AS share_ratio FROM dwd_view_session WHERE dt = '${dt}' AND duration_sec >= 60 -- 过滤掉误触发的短会话 GROUP BY channel_id, time_slice;实时版就得靠 Flink 了。这里有个绕不开的问题:去重。收视份额的分母是"所有频道观看终端数之和",但同一台终端在同一分钟理论上只能看一个频道,跨频道去重不能简单相加。所以实时链路里我通常用滚动窗口 + 终端维度最新状态来做,窗口内按terminal_id去重,只保留每个终端在该窗口内的最后一个频道状态。Flink 里可以用KeyedProcessFunction维护一个终端状态表,窗口触发时把所有终端的最新频道状态拿出来统计。这个状态如果太大(千万级终端),得配好 RockDB 状态后端和 TTL,不然内存扛不住。
活跃度指标相对好算。DAU 就是当日有任一有效事件的去重终端数,MAU 同理。但这里有个细节:周活跃和月活跃的口径要对齐,别一个算自然周一个算滚动 30 天。我之前接手的一个项目,两块报表一个用滚动 7 天一个用自然周,数字永远对不上,业务方天天来问,最后发现是口径问题,代码本身没错。这种坑纯属沟通问题,但代价极大。
4.2 ECharts 大屏的关键图表落地
大屏这块,门面工程,做得好不好直接决定项目在领导心里的印象分。广电大屏的经典布局是:中间一个三维地图或者雷达态势,两侧对称摆放趋势折线、排行条形、实时滚动列表。KPI 数字用大字号卡片,顶部放时间实时刷新。
ECharts 的配置有几个实战要点。第一,数据更新的方式。大屏要定时刷新,但绝对不能用chart.setOption(option)反复整体刷新,那样会有内存泄漏和动画抖动。正确做法是用setOption的增量更新,只传变化的部分:
// 增量更新折线图数据,保留图表实例 function updateTrend(chart, newData) { chart.setOption({ series: [{ data: newData }] }, { notMerge: false, // 关键:增量合并,不重建 lazyUpdate: true // 延迟渲染,避免频繁重绘 }); }第二,数据刷新频率和接口响应的匹配。实时看板 5 秒刷一次是常见的,但前提是你的接口能在 200 毫秒内返回。如果接口要 2 秒,你设 5 秒刷新就会不停堆积请求,浏览器用着用着就卡死。我的经验是刷新间隔至少是接口平均响应时间的 10 倍,接口 200 毫秒就 5 秒刷,接口慢到 1 秒就退到 15 秒或 30 秒刷。
第三,WebSocket 还是轮询。实时性要求高的用 WebSocket 推送,但推送频率要控制。别一有数据变化就推,那样前端渲染压力大。通常做法是服务端按固定节拍(比如 3 秒)批量推一次,前端拿到后合并渲染。我见过后端每条数据都推一次,结果高峰期一秒钟推几百条,浏览器直接卡崩。
注意:大屏上如果放了地图,千万别用在线地图底图。会议室网络环境复杂,一旦底图加载失败,整块大屏就是一片空白,非常尴尬。正确做法是把 GeoJSON 底图数据打包进项目本地,彻底摆脱网络依赖。
4.3 可视化平台的后台配置化设计
一个能长期活下去的广电数据可视化平台,一定要配置化,不能每加一个图表就改一次前端代码。我的做法是把大屏拆成"组件 + 数据源"两块。每个图表是一个组件实例,绑定一个数据源 ID,数据源里配置 SQL 或 API 地址、刷新间隔、参数。运营想换一个图表,改配置就行,不用发版。
这套设计的关键在于接口协议的统一。所有可视化数据源返回的 JSON 结构必须是同一个骨架,前端只认这个骨架,具体数值放在data数组里。这样前端图表组件才能通用。统一协议的好处在后期的运维上体现得淋漓尽致——新增图表零代码,调整布局拖拖拽拽就完成,项目能稳稳跑好几年而不是做完就烂尾。
配置表的设计我一般这么搞:
| 配置项 | 说明 | 示例 |
|---|---|---|
| screen_id | 大屏唯一标识 | gd_overview_01 |
| component_type | 组件类型 | line/bar/map/kpi |
| data_source_id | 绑定的数据源 | ds_channel_share |
| refresh_sec | 刷新间隔(秒) | 5 |
| position | 位置和尺寸 JSON | {"x":100,"y":200,"w":400,"h":300} |
| params | 传入参数 JSON | {"region":"全省"} |
有了这张表,整个大屏的布局和数据绑定全部落库,前端启动时拉一次配置,动态渲染。想改布局?改库。想换数据?改库。这套东西一旦搭起来,团队维护成本能降一大截。
5. 常见问题排查与性能优化实录
5.1 大屏卡顿与数据延迟的排查思路
大屏卡顿和数据延迟是运维中最常见的两类故障,我把它们整理成一张速查表,按"现象 → 可能原因 → 排查动作 → 解法"来组织,出了事照着走就行:
| 现象 | 可能原因 | 排查动作 | 解法 |
|---|---|---|---|
| 大屏整体转圈 | 接口慢或超时 | 看接口 P99 耗时 | 加缓存、加预聚合 |
| 单个图表空白 | 数据为空或报错 | 查数据源日志 | 修 SQL、补数据 |
| 数字长时间不动 | 刷新失败或推送断 | 看 WebSocket 心跳 | 重连、降级为轮询 |
| 数据延迟 1 小时以上 | Kafka 积压 | 看消费 lag | 扩分区、加消费并行度 |
| 指标突然暴涨暴跌 | 迟到数据或重复上报 | 查当日数据条数 | 加去重、修 watermark |
| 凌晨数据缺失 | 跨天分区错误 | 查分区时间字段 | 统一用服务端时间 |
这张表里的每一条我都亲身踩过。就说 Kafka 积压这条,最常见的原因是下游消费能力跟不上上游生产。表面看是积压,实际可能是 Flink 算子反压,而反压的根子往往是某个聚合算子状态太大,GC 频繁。这时候光扩分区没用,得去看 Flink 的反压监控,找到瓶颈算子。
排查顺序我一般这么走:先看大屏接口耗时,排除前端问题;再看 OLAP 查询耗时,排除查询问题;然后看 Kafka lag,排除消费问题;最后看上游生产量,排除数据洪峰。逐层往上,别一上来就改代码,先定位再动手。
5.2 实操心得与避坑清单
做了几个广电大数据可视化项目下来,有几条经验是普通文档里不会写、但实际特别管用的。
第一条,把口径写到维表里,跟代码一起管理。听起来很土,但救过我好几次。业务方问"你这个份额怎么算的",我能直接翻出维表里的定义和变更历史,而不是去翻半年前的代码。省下来的时间全是可以用来干正事的。
第二条,大屏的刷新频率宁慢勿快。新手总想让大屏"越实时越好",五秒一刷甚至一秒一刷。实际用下来,除了直播监控这种特殊场景,大部分业务看板 30 秒到 1 分钟刷一次完全够用。刷太快,服务器压力大,浏览器也累,还容易在会议室网络不好时翻车。稳定性比实时性重要一百倍。
第三条,所有对外展示的数字,都必须能追溯到明细。大屏上的数字一旦被质疑,你得能在几分钟内给出它的来源明细。我的做法是每个指标结果表都保留一份"明细抽样",随机抽 100 条明细可以随时展示。这样业务方质疑时,你能当场演示"这个数是怎么来的",信任感一下子就建立起来了。
第四条,永远给实时链路准备降级方案。实时计算链路复杂,出故障的频率比离线高得多。我的方案是:实时链路挂掉时,大屏自动切到"最近一次成功离线结果 + 标记数据延迟时间",而不是直接报错白屏。用户看到"数据更新至 20:15",比看到一个红叉舒服多了,也更能容忍几分钟的延迟。
第五条,别迷信大集群。广电的数据量看着吓人,15 亿条一天,但真正算下来需要的算力并没有想象中那么夸张。我见过团队上来就申请几十台服务器,结果一半在空转。先用小集群扛住,等真扛不住了再加,这个策略在广电场景里屡试不爽。数据量的增长是渐进的,架构的扩展能力比一次性堆硬件更重要。
第六条,文档和监控一起做。大屏上线那天不是项目的终点,是起点。上线前必须配好监控:接口成功率、数据新鲜度、Kafka lag、任务运行状态。我见过太多项目,上线当天风风光光,三个月后没人敢动,因为不知道哪里会崩。监控配上,报警规则定好,才能睡得安稳。
最后分享一个我最近在用的小技巧。大屏上的数字刷新如果太频繁会让人眼花,其实可以在前端做平滑过渡动画,数字从旧值滚动到新值,看着很舒服,也能掩盖刷新时的视觉跳变。ECharts 的数字卡片配合animationDuration稍微调长一点,效果就出来了。这种小细节业务方未必说得出来,但用起来就是觉得"高级",这就是可视化的价值——让数据看起来可信、看起来舒服,数据才真正被人用起来。