干广电数据这行快八年了,从最早拿Excel拉机顶盒回传日志、人工拼收视日报,到后来带队搭Hadoop集群、做数据可视化平台,中间踩的坑能写一本小册子。广电这个行业做数据可视化,和互联网公司做大屏完全是两码事:数据源杂、口径乱、样本不全、领导要看的和运营要看的不是一张图,而且大部分场景对实时性没那么多执念,日报、周报、月报才是主战场。这篇就把我这些年做广电大数据可视化的完整思路摊开讲——从数据源梳理、指标口径定义、数仓分层建模,到ECharts大屏实现、集群容量估算、上线后天天被问"这个数为什么和昨天不一样"的排查方法。不管你是刚入行做数据开发的新人,还是正在找数据可视化项目练手的学生,或者被临时抓来接手广电大屏的运维老哥,都能从里面抄到能直接用的东西。
1. 广电大数据到底长什么样:先搞清楚你手里有什么料
做可视化最容易犯的错,是先挑图表、先想配色,最后才去对数据。我见过太多项目,大屏UI做得跟科幻电影一样,上线三天就没人用了,因为图上的数字业务不认。所以在动手写第一行SQL之前,先把数据源摸清楚,这一步花的时间越久,后面返工越少。
1.1 四类核心数据源与它们的脾气
广电体系里的数据,按我实际接触的情况,大致分四类,每一类的采集方式、数据质量、更新频率都不一样,做可视化时的处理策略也完全不同。
第一类是播出与EPG数据。这是最"干净"的一类,来自播出系统和节目单系统,本质是结构化的节目排期表:频道、节目名称、开始时间、结束时间、节目类型、时长。它的特点是准确、稳定、体量小,一天全国几十个频道的排期也就几千条记录。但它的坑在于人工维护——节目临时调整、插播、延后,EPG如果没同步,你算出来的时段归属就是错的。我的习惯是每天凌晨跑一次EPG和实际播出日志的差异比对,差异超过阈值就告警。
第二类是终端回传数据,也就是机顶盒、IPTV终端、OTT盒子回传的行为日志。这是广电数据里体量最大、最有价值、也最脏的一类。典型字段包括设备号、账号、频道号、事件类型(开机/切台/关机/点播/暂停)、事件时间戳、终端型号、区域编码。单台终端一天能产生几十到几百条记录,百万级终端就是亿级记录量。脏点在哪?设备号重复、时间戳漂移、区域编码为空、切台事件和开机事件乱序到达、时移回看和直播混在一起。这些不处理直接上大屏,报表口径必然打架。
第三类是BOSS与计费数据。用户订购关系、套餐、缴费记录、停机复机状态。这类数据在关系型数据库里,结构规范,但涉及用户隐私,出库到分析层之前必须做脱敏处理,账号ID要做哈希映射,绝对不能把手机号、身份证号直接落到大屏上。这一点不是技术问题,是合规红线,团队里必须有人盯。
第四类是运维与客服数据。网络质量指标(丢包率、信噪比、光功率)、设备告警、客服工单。这类数据流式特征强,适合做实时监控大屏,而不是做经营分析大屏。我一般把它们拆成两个独立的可视化产品,不要混在一个页面上,否则页面信息密度过高,看的人抓不住重点。
1.2 广电大屏和互联网大屏的根本差别
很多人拿互联网那套数据指标体系往广电上套,结果处处别扭。差别主要在三个地方。第一,分析单元是"户"不是"人"。一个家庭可能有多台终端、多个账号,回传上来的设备数是终端数,但业务关心的是家庭数。你得先做设备到家庭的归并,归并规则通常靠BOSS里的账号关系或者同IP同区域加设备绑定关系来推,这一步做不干净,用户规模指标就会虚高两到三成。
第二,时效性要求分层明显。领导看的经营看板,T+1更新足够;运营做节目编排优化的,需要小时级;运维盯网络质量的,才需要秒级。我早期吃过亏,把三个场景的需求全塞进一套实时链路,Kafka、Flink全上,结果成本和维护复杂度爆炸,实际上80%的图表根本不需要实时。后来改成"离线为主、准实时为辅、实时只做告警"的三层策略,集群规模直接砍了一半。
第三,可视化受众分三种视角。管理层视角讲趋势和对比,图要少、要大、要一眼看懂;运营视角讲明细和下钻,需要能点进去看某个频道某个时段的曲线;运维视角讲异常和定位,需要能按区域、按设备型号筛。这三种视角对图表类型的要求完全不同,硬塞进一个大屏必然失败。
2. 整体架构设计与技术选型:别一上来就堆重型方案
架构这件事,原则只有一条:用能被你和你的团队维护得起的最简方案。我见过一个地市级项目,数据量一天不到50GB,结果上了十几个组件的全家桶,最后运维的两个人谁也说不清数据从哪流到哪。
2.1 从采集到展示的五层链路
我目前用的链路稳定跑了三年,结构是五层。
采集层:终端回传日志走消息队列(Kafka)落盘,EPG和BOSS数据走定时抽数(Sqoop或者自写的JDBC抽取任务),网络质量数据走Flume采集。关键设计是Kafka的Topic按日期分区,保留7天,方便回溯重放。
存储层:明细数据落HDFS,用Parquet列式存储,按天分区。维度表放Hive外部表或者直接放MySQL,量小、更新少、需要频繁关联的放关系库更快。
计算层:离线用Spark SQL或者Hive on Spark做T+1批处理;准实时用Spark Structured Streaming做小时级聚合;实时告警用Flink做窗口统计。注意这里我刻意避开了"什么都用Flink"的诱惑,批处理仍然用Spark,因为大部分数据开发同事对Spark SQL更熟,维护成本低。
服务层:聚合结果落MySQL或者ClickHouse。这里的选择很关键——如果大屏查询需要多维组合、秒级响应,ClickHouse几乎是最优解;如果只是固定几张报表,MySQL加缓存就够。我一般把ADS层的宽表落到ClickHouse,因为是列存,做聚合查询比MySQL快一到两个数量级。
展示层:前端统一用ECharts,配合大屏框架做自适应。后端提供一个统一的查询接口服务,按"页面-图表"的粒度封装成接口,不让前端直接拼SQL。
2.2 选型对比:自研、开源、商业BI怎么选
| 方案 | 适合场景 | 优势 | 代价 |
|---|---|---|---|
| 商业BI工具 | 报表需求固定、业务人员自助取数 | 上手快、拖拽出图、权限体系成熟 | 授权费高、大屏定制能力弱、数据量大时性能受限 |
| 自研ECharts大屏 | 定制化大屏、领导驾驶舱 | 视觉自由度最高、性能可控、无授权成本 | 前端工作量集中在细节上,维护需要专人 |
| 开源可视化平台 | 中等规模、需要自助分析 | 免费、组件丰富、能二次开发 | 部署运维复杂、版本升级容易踩坑 |
| 纯Python出图 | 内部周报、分析师自用 | 开发最快、和pandas无缝 | 不适合做交互式大屏 |
我实际的选择通常是混合:领导驾驶舱用自研ECharts大屏,视觉和交互完全可控;业务部门的自助取数用开源BI工具或者轻量的报表服务;分析师的临时分析直接用Python出图。三种场景三种工具,不追求统一。
2.3 数仓分层:ADS层才是大屏的真正数据源
这套分层几乎是行业惯例,但我强调一下每层的职责边界,因为边界不清是后期口径混乱的根源。
- ODS层:原始数据落地,不做任何清洗,保留全量字段。这一层只增不改,出问题可以回溯。
- DWD层:清洗、去重、脱敏、标准化。设备号做哈希,时间统一到时区,区域编码补全,事件类型做枚举映射。这里最关键的产出是用户行为明细事实表,一行一个事件。
- DWS层:按主题做轻度聚合。比如"频道-小时-区域"粒度的收视汇总,"设备-日"粒度的活跃汇总。这一层的目标是让下游不用重复做同样的group by。
- ADS层:面向具体图表做宽表。一张ADS表对应大屏上的一到两个图表,字段名直接和图表需求对齐。宁可多几张表,也不要让前端做复杂关联。
提示:ADS层表名建议带上业务域和更新频率前缀,比如
ads_rating_channel_hour_d、ads_user_active_region_m,团队协作时一眼就知道能不能用。
3. 指标口径与数据建模:这一步错了,大屏全废
技术实现可以慢慢调,口径错了是灾难性的——业务看到数字不对,整个平台的可信度就崩了。我在项目里坚持一件事:所有指标必须有书面定义,包括公式、数据来源、过滤条件、更新频率,写进字典表,谁改谁签字。
3.1 收视类指标的公式与计算口径
这是广电最核心的一组指标,公式本身不复杂,难的是口径统一。
到达率(Reach):统计周期内,至少观看过某频道/某节目一次的户数,除以统计范围内的总户数。注意"至少一次"这个定义意味着要去重,SQL里就是count(distinct household_id)。
收视率(Rating):统计周期内,某频道/节目的总收视时长,除以(总户数 × 统计周期时长)。这个是加权概念,不是简单计数。
市场份额(Share):某频道收视时长,除以所有频道收视时长之和。分母是"正在看电视的人",不是全部用户,这一点经常被混淆。
人均收视时长:总收视时长除以到达户数,反映的是粘性而不是覆盖。
时长怎么算?这是个实操难点。回传数据里是离散事件,一次完整的收视行为是"切台进入-切台离开"这样一个区间。我的处理方式是在DWD层用窗口函数按设备号+时间排序,把相邻的"进入"和"离开"事件配对,算出区间时长。对于跨天的问题,按自然日切分区间,超长的异常区间(比如超过12小时没关机)要做截断处理,否则会污染时长指标。
-- 按设备配对计算单次收视时长(简化版) select device_id, channel_id, start_time, end_time, unix_timestamp(end_time) - unix_timestamp(start_time) as duration_sec from ( select device_id, channel_id, event_time as start_time, lead(event_time) over ( partition by device_id order by event_time ) as end_time, event_type from dwd_user_event where dt = '${bizdate}' and event_type in ('switch_in', 'switch_out') ) t where event_type = 'switch_in' and end_time is not null and unix_timestamp(end_time) - unix_timestamp(start_time) between 1 and 43200;上面那个between 1 and 43200就是12小时截断,实测能过滤掉大部分异常数据。
3.2 用户画像标签怎么落地
广电做画像,标签体系不用追求互联网那种几千个标签的规模,落地二三十个高价值标签就够用了。我一般分四类:
- 基础属性:区域、入网时长、套餐档位、终端型号。
- 行为属性:日均观看时长、活跃时段、开机频次、点播偏好。
- 内容偏好:最爱看的频道TOP3、节目类型偏好(新闻/影视/少儿/体育)、是否偏好回看。
- 价值属性:ARPU分层、付费点播频次、流失风险等级。
标签计算放DWS层,用宽表的方式按户一行输出。这里有个经验:标签更新周期不要都设成日更。基础属性月更就够,行为属性日更,价值属性周更。全部日更会让计算资源浪费得厉害,而且标签抖动会让人困惑——今天用户是"高活跃",明天变成"中活跃",运营看了会来问你系统是不是坏了。解决方法是在标签上加一个"稳定性窗口",连续3天满足条件才切换状态。
3.3 时间维度和区域维度最容易踩的坑
时间维度上有三个坑我反复遇到。第一,时区。如果数据源里有UTC时间,一定要在DWD层统一转成业务时区,不要留到前端转。第二,时移回看。用户晚上11点点播昨天的节目,这条记录的时间应该是观看时间而不是节目播出时间,但统计"节目收视"时又要按节目时间归属。这两个口径要分开建两张表。第三,直播延时。同一个节目在不同区域的播出时间可能差几秒到几分钟,做跨区域对比时要做时间对齐,否则曲线会错位。
区域维度上的坑主要是编码不统一。BOSS系统里是一套区域编码,网络管理系统里是另一套,终端回传里可能是最粗的行政区划。必须在DWD层建一张区域映射维表,把三套编码打通,映射不上来的数据单独归档,不要默认丢掉——我见过丢了12%的数据导致某地市报表明显偏低的案例。
4. 可视化大屏实现:从布局规范到ECharts实操
前端这块我踩的坑最多,因为代码写对了但效果不对的情况太常见。大屏做得好不好,一半看数据,一半看视觉。
4.1 大屏布局与视觉规范
我做大屏有几条硬性规矩,团队里新人都要先背下来。
一是"三秒原则"。任何一张图,用户三秒内看不懂它在说什么,就该改。这意味着标题要写清楚"什么指标+什么范围+什么时间",比如"全市数字电视日均开机户数(近30天)",而不是只写"开机户数"。
二是数字要大,图要少。领导驾驶舱上,核心KPI用大号数字卡片展示,一屏不要超过8个图。我见过一屏塞20个图表的大屏,实际使用中根本没人看。
三是配色统一且有语义。不要彩虹色乱刷,同一含义用同一颜色。增长用一套色、下降用另一套,但要避开大面积红绿(部分人群色觉差异,看不清)。背景用深色系,图表主体色相控制在3个以内,辅助色用于高亮。
四是分辨率适配。大屏通常跑在超宽屏上(比如3×3拼接或者3840×1080),布局要用相对单位(百分比、rem、vw/vh),不要写死像素。字体最小不低于14px,拼接屏实际观看距离远,太小了看不清。
4.2 ECharts关键配置与可直接复用的代码
下面这段是我常用的暗色系柱状图配置,做过大屏的可以直接拿去改。
const option = { backgroundColor: 'transparent', title: { text: '各区域日均开机户数', subtext: '统计周期:近7天 单位:万户', textStyle: { color: '#d7e6ff', fontSize: 18, fontWeight: 500 }, subtextStyle: { color: '#7f96b8', fontSize: 12 } }, grid: { top: 80, left: 60, right: 30, bottom: 50, containLabel: true }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, backgroundColor: 'rgba(12,30,56,0.9)', borderColor: '#2b6cb0', textStyle: { color: '#e6f0ff' } }, xAxis: { type: 'category', data: [], axisLabel: { color: '#9db3d0', fontSize: 13, interval: 0, rotate: 0 }, axisLine: { lineStyle: { color: 'rgba(140,170,210,0.3)' } } }, yAxis: { type: 'value', axisLabel: { color: '#9db3d0', fontSize: 13 }, splitLine: { lineStyle: { color: 'rgba(140,170,210,0.12)' } } }, series: [{ type: 'bar', barWidth: '42%', data: [], itemStyle: { borderRadius: [4, 4, 0, 0], color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: '#4facfe' }, { offset: 1, color: '#1a4f8a' } ] } }, label: { show: true, position: 'top', color: '#cfe4ff', fontSize: 12 } }] };配置里有几个细节值得说。grid的containLabel: true一定要开,否则坐标轴标签会被裁掉,这是新手最常见的白边问题。axisLabel的interval: 0让类目名全部显示,区域名多的时候要配合rotate: 30否则会重叠。渐变色用linear类型比纯色更有质感,但两个色值不要太跳。
4.3 数据量大时的渲染优化
大屏卡顿通常不是ECharts本身的问题,是数据量和更新频率的问题。我的优化顺序是这样的。
第一步,先看数据量。如果一个系列超过2000个点,折线图基本就不用看了,改做聚合或者加sampling: 'lttb'。ECharts内置的LTTB降采样特别适合长时间序列,视觉上几乎无损。
第二步,减少重绘。大屏轮播刷新时,不要每秒都调setOption重建全部配置,用setOption(option, false)做增量更新,并且把不变的配置项(坐标轴、标题)分离出去只设一次。
第三步,处理分页轮播。表格类内容用dataZoom或者手写轮播索引,不要用CSS动画直接改DOM。我见过用CSStransform做滚动列表的,60条数据就掉到20帧,改成JS定时替换内容后稳定60帧。
第四步,控制定时器。一个页面上如果有5个图表各自每秒刷新,加上背景动画,浏览器会累。我的做法是统一用一个定时器,按顺序错峰刷新不同图表,间隔控制在3到5秒,业务上完全够用。
注意:定时器一定要在组件销毁时清理,
setInterval不清理的话,路由切换几次之后内存就上去了,大屏长时间运行会直接崩掉,这是生产环境最常见的稳定性事故。
5. 部署与运维:集群容量怎么估,数据质量怎么盯
很多人觉得大屏上线就完事了,实际上运维才是真正的长跑。
5.1 集群容量估算的实操算法
容量估算不用算得很精确,但必须有个量级判断。我给你一套我常用的算法。
假设地市规模:100万终端,每终端每天产生500条行为日志,单条日志平均0.5KB(JSON文本)。
- 日增原始数据:100万 × 500 × 0.5KB = 250GB
- Parquet列存压缩比按5:1算:约50GB/天
- 保留90天:50GB × 90 = 4.5TB
- HDFS三副本:4.5TB × 3 = 13.5TB
- 加上中间表、维度表、临时表,整体乘以1.5的系数:约20TB
- 生产环境建议按60%水位规划,实际采购容量:33TB左右
内存和CPU怎么估?离线批处理作业,一般按"单核处理10GB压缩数据/小时"这个经验值算。50GB日增,要求3小时内跑完,至少需要2个core,但考虑Spark的shuffle和并发作业,我一般按8到16个core配置。内存按core数的4倍GB给,也就是32到64GB。这套算法不精确,但足够你写采购申请和做初期规划。
准实时链路(小时级)用的是Structured Streaming,资源需求比离线低,但要注意微批的触发间隔设置——间隔太短小文件多,间隔太长延迟高。我通常设5到10分钟。
5.2 调度与数据质量监控
调度用Airflow或者DolphinScheduler都行,我倾向DolphinScheduler,可视化配置、对国内团队友好、上手快。关键是要建立依赖关系:ODS抽取完成 → DWD清洗 → DWS聚合 → ADS宽表 → 刷新缓存 → 通知大屏。中间任何一步失败,下游不启动,并且要有人收到告警。
数据质量监控我通常埋三类规则:
| 规则类型 | 具体做法 | 触发动作 |
|---|---|---|
| 完整性 | 关键字段非空率低于95% | 告警,阻断下游 |
| 时效性 | 分区数据未在约定时间产出 | 告警 |
| 波动性 | 核心指标同比或环比偏离超过30% | 告警,人工确认 |
| 唯一性 | 主键重复率高于0.1% | 告警 |
| 一致性 | 明细汇总与报表口径不一致 | 阻断并回滚 |
波动性规则最有用,也最容易误报。节假日、重大赛事、系统割接都会造成真实波动,所以这条规则我一般设成"告警但不阻断",让人来判断。
6. 常见问题与排查技巧实录
这部分是我这些年被问最多的问题,整理成速查表,遇到直接查。
6.1 "这个数和昨天不一样"怎么排查
这是每天都要面对的问题,我总结了一个固定排查顺序,照着走基本十分钟内能定位。
第一步,确认是不是口径变了。翻指标字典的修改记录,看最近48小时有没有人改过公式、过滤条件或者数据源。改口径不改文档,是团队里最常见的混乱源头。
第二步,确认数据是否完整产出。看ODS到ADS每一层分区的记录数,和历史均值对比。如果某一层少了,往上游追。我遇到过好几次是新终端型号上线后日志格式变了,清洗规则没跟上,导致这部分数据被静默丢弃。
第三步,看是否有重复数据。终端回传在网络抖动时可能重发,如果DWD层去重规则写得松,就会重复计数。检查方式是看主键(设备号+事件时间+事件类型)的唯一性。
第四步,看维度关联是否丢失。比如区域映射维表更新后,某些编码映射不上了,join之后变成null被过滤掉,数字就会掉。这种情况最隐蔽,因为每一步SQL都"成功"了。
第五步,看时间边界。跨天任务如果用的是dt = '${bizdate}'而数据延迟到凌晨2点才落完,就会漏掉尾巴。解决方案是把调度时间从凌晨1点推到凌晨3点,或者用事件时间而不是处理时间做过滤。
6.2 大屏卡顿、白屏排查表
| 现象 | 可能原因 | 排查方法 | 处理方案 |
|---|---|---|---|
| 首次加载慢 | 接口返回数据量过大 | 看Network面板接口大小和耗时 | 后端分页或聚合,单个接口返回不超过500条 |
| 页面卡顿掉帧 | 定时器过多或数据点过多 | Performance面板录制看长任务 | 统一定时器,折线图开启LTTB采样 |
| 白屏 | JS报错导致渲染中断 | 看Console第一条报错 | 给每个图表加try-catch,单个图表失败不影响全局 |
| 长时间运行后崩溃 | 内存泄漏 | 看内存曲线是否持续上涨 | 清理定时器和事件监听,重载页面兜底 |
| 图表模糊 | canvas分辨率未适配DPR | 检查devicePixelRatio | 初始化时传入devicePixelRatio: window.devicePixelRatio |
| 饼图标签重叠 | 扇区过小或标签未防重叠 | 看小比例扇区 | 合并小项为"其他",开启labelLayout防重叠 |
补一个小技巧:大屏部署在拼接屏上时,浏览器建议用Kiosk模式启动,并在URL上加一个版本参数,配合自动刷新策略,比如每4小时重载一次页面,能有效规避长时间运行积累的内存问题。
6.3 常见误区清单
做广电数据可视化这几年,我看到的高频误区有这些,值得提前避开。
误区一:先做大屏再补数据治理。顺序反了,一定是先有稳定的数据链路,再做可视化。反过来做,就是不停地改图表去迁就脏数据,做到最后没人信。
误区二:追求全指标覆盖。一张大屏塞30个指标,结果每个都没人看。我通常的做法是先上线5到8个核心指标,跑一个月,看点击率和反馈,再迭代。
误区三:忽略数据权限。同一个平台,不同角色看到的数据范围应该不同。地市只能看本地市,省级能看全省,这个必须做在接口层,不能只做在前端隐藏。
误区四:不做离线兜底。实时链路一旦挂掉,大屏就空了。我的方案是所有实时指标同时保留一条T+1的离线结果,实时数据不可用时自动降级显示离线数据并打上标记。
7. 小成本起步:从练手项目到生产系统的两条路
有不少人是通过课程实训或者毕设项目接触数据可视化的,我经常被问"我这个项目能不能算企业级"。说实话,能不能算,差别不在于图表好不好看,而在于有没有处理真实的数据复杂度和运维场景。
7.1 个人练手的最小可用方案
如果你想在有限资源下做一个完整的广电风格数据可视化项目,我建议这样搭:用Python生成一份模拟的机顶盒回传日志(可以是CSV,几十万行),字段包括设备号、区域、频道、事件时间、事件类型。用pandas做清洗和聚合,把结果写进SQLite或者MySQL。前端用ECharts做一个单页大屏,包含四个图表:区域开机户数柱状图、频道收视份额饼图、近30天活跃趋势折线图、节目类型偏好横向条形图。整个项目跑在一台笔记本上完全够。
关键是要把数据清洗的环节做出来,而不是直接拿干净数据画图。比如在生成数据时故意加入5%的重复记录、3%的空区域编码、2%的时间戳异常,然后写清洗逻辑处理掉。这一步做扎实了,面试时讲起来完全不一样,因为你真的踩过坑。
7.2 从demo到生产要跨过三个门槛
我面试过不少简历上写着"数据可视化项目"的同学,问了三个问题基本就分层了。第一,你的数据量多大,什么存储,查询延迟多少?能答出具体数字和选型理由的,说明真的跑过。第二,你的指标口径怎么定的,有没有文档?这是工程意识和业务意识的体现。第三,如果半夜数据没产出,你怎么知道?这一问是区分"会画图"和"能维护系统"的分水岭。
对应的三个门槛就是:存储与查询的工程能力(从CSV到列存数据库)、指标治理能力(从随手写到有字典)、可观测能力(从人肉检查到自动监控告警)。跨过这三个,你的项目就真的能拿出去讲。
最后分享一个我自己的习惯:每做一个新的数据可视化项目,我都会先写一份"数据字典",把每个指标的名称、公式、数据来源表、更新频率、负责人写清楚,放在项目根目录。这份文档看起来不起眼,但它是整个项目最值钱的东西——三个月后你自己回来看,或者新人接手,全靠它。我踩过最惨的一次坑,就是接手一个没有字典的项目,为了搞清"活跃用户"到底是按开机算还是按有观看行为算,翻了三天代码。从那以后,字典先写,图后画。