最近把一个体育赛事直播推荐系统从零到一完整跑通了。技术选型用了Hadoop+Spark+Hive这套经典的大数据组合,最终交付的是包含离线数仓、协同过滤推荐和Web展示的完整项目。很多人一听到“推荐系统”就觉得算法很复杂,但在这类大数据毕业设计或入门级项目里,真正决定成败的反而不是算法,而是你对数据的处理链路是否清晰:日志从哪来、存在哪、怎么算、结果怎么被读取。
这篇文章适合两类人:一类是正在做大数据方向毕业设计,打算把Hadoop、Spark、Hive串成一套真实系统的人;另一类是刚入行数据平台,想了解离线数仓与推荐系统怎么配合工作的新人。我会以这套体育赛事直播推荐系统为例,把项目从业务拆解、数仓建设、Spark计算到踩坑调优的完整过程写出来,全部是可复现的方案。
1. 体育直播推荐到底在推荐什么?先拆业务再谈技术
1.1 体育直播推荐和普通视频推荐的本质差异
我在做这个项目之前,对“推荐系统”的认知主要停留在电商场景:用户看了A商品,系统推B商品,核心逻辑是“你可能喜欢什么”。但体育直播和电商、短视频的推荐逻辑差异很大,这些差异直接决定了技术方案怎么做。
体育直播最大的特点是强时效性。一场英超比赛一旦结束,这场直播的回放价值会断崖式下降。用户打开App时,最需要的是“现在正在直播的、我可能感兴趣的热门场次”,而不是一堆历史比赛。所以推荐系统不能只按历史行为匹配偏好,还必须把“赛事状态”和“当前热度”作为重要特征。
其次是粉丝凝聚性。体育用户通常有明确的球队主队:喜欢湖人的人大概率会看湖人比赛,支持曼联的人不会错过曼联焦点战。这种“内容标签”比电商场景更清晰,适合做基于内容的召回。但另一方面,体育赛事数量有限,尤其是冷门联赛,数据稀疏问题非常严重,很多用户可能只看过一两次比赛,纯协同过滤会拿不到足够样本。
第三点,也是我在项目答辩时反复提到的:体育直播的转化目标不是“购买”,而是“观看时长”和“回访率”。这意味着推荐效果不能只看点击率,还要关注用户是否真的点进直播间并停留。但为了降低项目复杂度,我没有一开始就引入实时观看时长统计,而是先按“点击、收藏、分享”这类行为做了加权评分。
如果把业务需求翻译成技术问题,可以概括成三件事:
- 知道用户关注什么球队、喜欢什么赛事类型。
- 知道哪些比赛正在直播、哪些比赛热度最高。
- 把“用户偏好”和“赛事内容”匹配起来,产出TopN推荐列表。
这三件事分别对应了用户画像建模、赛事热度统计和推荐结果生成,也是后面Hive数仓和Spark计算的主线。
1.2 从原始日志到“可计算特征”的转化链路
业务需求拆清楚之后,我会习惯性画一条数据流转链:日志采集 -> 数据落地 -> 数据清洗 -> 特征加工 -> 推荐计算 -> 结果存储 -> Web展示。这条链路看起来简单,但每一步都有取舍。
在这个项目里,原始日志来自前端埋点。用户进入直播间、离开直播间、点击关注球队、点赞、分享、评论,都会生成一条JSON格式的行为日志。这些日志不能直接用于推荐,原因有两个:一是字段杂乱且有脏数据;二是日志是“事件流”,不是“用户画像”,必须经过聚合计算才能变成可用的特征。
举个例子,用户A在一天内看了3场NBA比赛,其中看了湖人45分钟、快船20分钟、勇士5分钟。原始日志记录的是6条“进入直播间”和“离开直播间”事件。我关心的特征是“用户A对湖人、快船、勇士的兴趣权重”,这需要把多天多条日志按球队维度做聚合。
聚合之后,一张用户兴趣表就出来了。再关联赛事表,拿到球队名称、联赛类型、比赛时间,推荐候选集就有了雏形。整个转化链路可以概括为:
行为日志 -> 数仓分层存储 -> 用户画像宽表 / 赛事标签表 -> 召回候选集 -> 排序 -> 推荐结果表。
骨架搭完之后,剩下的就是填肉:具体用什么框架处理每一段,为什么这样选。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Hadoop、Spark、Hive三件套的分工边界与选型依据
2.1 为什么不是直接用MySQL,而是HDFS+Hive?
做毕设或者入门项目,最容易犯的错误是“为了用大数据而用大数据”。体育直播推荐系统的数据量真的有到必须上Hadoop的程度吗?说实话,如果只是几千个用户的数据,单机MySQL完全够用。但这类项目的意义在于让你完整走一遍大数据处理流程,而且如果数据源模拟到每天千万级日志,MySQL就会开始吃紧。
我在项目里模拟了两个月的数据:每天产生约2000万条用户行为日志,按JSON原样落盘后单日约15GB,压缩成Snappy之后大约8GB。这个量级对单机MySQL来说,写入和查询都会非常吃力。更关键的是,推荐计算需要用户维度、赛事维度、时间维度做大量聚合,SQL联表查询在千万级表上效率很低。
HDFS+Hive在这个场景下的优势并不是“快”,而是“稳”和“省”。HDFS把数据分散存储在多节点上,Hive则在文件系统上建立一张张逻辑表。你不必一开始就追求毫秒级响应,先把几十亿行历史数据稳妥地存下来、可离线分析,这才是数仓的定位。
我的选型依据很简单:
| 需求 | 选型 | 理由 |
|---|---|---|
| 海量日志存储 | HDFS | 分布式存储,扩容方便,不支持随机读写但适合顺序读 |
| 离线数据分析 | Hive | SQL化开发,适合复杂聚合和分层建模 |
| 复杂特征计算 | Spark | 内存计算,写复杂逻辑比Hive SQL更灵活 |
| 在线推荐结果 | MySQL/Redis | 只需要存最终TopN列表,单机足够 |
| 搜索 | Elasticsearch | 本项目暂时不需要,后续扩展可考虑 |
所以这套系统的数据流是:日志先落到HDFS,Hive负责数仓建模和分析,Spark负责推荐算法,最终结果回写到MySQL,Web端直接读MySQL展示。
2.2 Spark在数据链路里到底负责哪一段?
有人问:Hive本身也能写SQL做统计和召回,为什么还要用Spark?这确实是我在项目里反复权衡的问题。答案是:Hive适合处理“结构化明确”的聚合分析,但推荐算法里有几个环节用Hive SQL写会非常别扭。
比如协同过滤里的物品相似度计算,需要构造“用户-物品”评分矩阵,然后计算物品两两之间的余弦相似度。你可以用Hive SQL通过自关联实现,但逻辑复杂、执行效率低,而且代码可读性差。换成Spark后,我可以直接用DataFrame转换算子,把每一步逻辑拆成清晰的函数,调试起来也方便。
另外Spark自带MLlib库,里面封装了ALS矩阵分解算法。虽然这个项目最终选了ItemCF(基于物品的协同过滤),但Spark的算法生态确实让我在切换方案时没有额外成本。
在这套系统里,Spark承担的职责是:
- DWS层和ADS层中部分Hive跑不动的复杂指标计算。
- 用户行为评分矩阵的构造。
- 物品相似度计算。
- 推荐结果的排序和过滤。
2.3 集群规模、版本与实测任务量级
如果你们是个人开发或课程设计,不建议一上来就搭5节点甚至10节点集群。我实际用的只是3台服务器(1主2从),每台配置4核8G内存、200G硬盘,跑这套项目完全够用。真实业务里这种配置有点寒酸,但作为毕设演示和调优练兵,反而能逼着你学会资源受限时的处理办法。
软件版本我当时选的是Hadoop 3.3.x、Hive 3.1.x、Spark 3.2.x。三个框架都是主流稳定版,教程多、坑少。需要注意Hive和Spark之间的整合,因为SparkSQL访问Hive元数据需要依赖Hive的Metastore服务,版本不匹配会出一堆莫名其妙的问题。
实测下来,凌晨2点启动的T+1离线任务,包含日志清洗、用户画像聚合、相似度计算、推荐结果生成,全流程大约45分钟跑完。其中Spark部分占了大头,尤其是ItemCF相似度计算阶段,单个stage跑20分钟左右也出现过。这个耗时在离线场景完全可接受。
3. 数据仓库建设:从埋点日志到Hive分层的全流程
3.1 埋点字段设计与JSON日志样例
数据仓库的第一个问题不是建表,而是知道源数据长什么样。体育直播的场景里,前端上报的日志需要覆盖用户、内容、行为、环境四类信息。我定义的公共字段如下:
- user_id:用户ID,用户未登录时上报设备ID并单独标记。
- session_id:会话ID,用于区分一次会话内的连续行为。
- match_id:当前观看或点击的赛事ID。
- anchor_id / team_ids:主播ID或比赛双方球队ID。
- event_type:行为类型,包括view、play、pause、like、share、comment、follow。
- duration:观看时长,单位秒,离开直播间时上报累计时长。
- event_time:事件发生时间,精确到毫秒。
- device_type、channel:设备类型和分发渠道,用于后续分析。
一条实际的日志长这样:
json复制{
"user_id": "u_100023",
"session_id": "s_8f3a21c0",
"match_id": "m_202601",
"anchor_id": "a_11",
"team_ids": ["team_102", "team_108"],
"event_type": "play",
"duration": 1800,
"event_time": "2025-05-20 21:30:12.456",
"device_type": "android",
"channel": "homepage_recommend"
}
如果项目没有现成日志,也可以用脚本模拟生成。我写了一个Java程序按泊松分布随机生成用户行为,配上真实赛事列表,一天能造出几十万用户的行为数据。这样既避免了隐私问题,又能让数仓和推荐链路的数据量足够大。
3.2 ODS/DWD/DWS/ADS四层模型的核心SQL
数仓建模直接决定后续所有处理逻辑的复杂度。我不推荐“建几张表就开干”,而是老老实实按四层模型走:ODS原始数据层、DWD明细数据层、DWS服务数据层、ADS应用数据层。
ODS层只做两件事:把日志文件映射成表,按天分区。这里不要做任何过滤,保留最原始的数据,方便出问题时回溯。
sql复制CREATE EXTERNAL TABLE dwd_ods_event_log (
user_id STRING,
session_id STRING,
match_id STRING,
anchor_id STRING,
team_ids ARRAY<STRING>,
event_type STRING,
duration BIGINT,
event_time STRING,
device_type STRING,
channel STRING
)
PARTITIONED BY (dt STRING)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
STORED AS TEXTFILE
LOCATION '/warehouse/ods/event_log';
注意这里用了JsonSerDe,可以直接把JSON文件映射成表。ODS层在加载数据时需要修复分区:加载昨天的日志文件到对应分区后执行MSCK REPAIR TABLE。
DWD层做清洗和标准化。主要处理三类问题:
- 去重:用户可能因为网络重试上报重复日志,按session_id+event_time去重。
- 字段解析:把时间字符串转成标准格式,提取日期、小时字段。
- 过滤无效数据:比如end_time小于start_time、观看时长为0的异常数据。
我用Hive SQL实现过一版去重逻辑,用ROW_NUMBER()窗口函数按用户、会话、时间排序,保留第一条:
sql复制CREATE TABLE dwd_event_log AS
SELECT
user_id,
session_id,
match_id,
event_type,
duration,
event_time,
dt
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id, session_id, event_time
ORDER BY event_time DESC
) AS rn
FROM ods_event_log
WHERE dt = '2025-05-20'
) t
WHERE rn = 1;
DWS层是重度聚合的地方,以用户和赛事为主体建宽表。比如用户-球队兴趣表,从DWD层把用户对每支球队的观看时长、互动次数聚合起来:
sql复制SELECT
user_id,
team_id,
SUM(duration) AS total_duration,
COUNT(if(event_type = 'like', 1, NULL)) AS like_cnt,
COUNT(if(event_type = 'share', 1, NULL)) AS share_cnt
FROM dwd_event_log
LATERAL VIEW explode(team_ids) t AS team_id
WHERE dt >= date_sub(('2025-05-20'), 30)
GROUP BY user_id, team_id;
ADS层直接面向推荐任务。我建了赛事热度表、用户推荐候选表、用户最终推荐结果表,供Spark读取和Web接口使用。
四层模型看起来多写了几张表,但它的好处是:每一层职责清晰,出问题能快速定位是清洗问题还是聚合问题。答辩的时候把这个图讲清楚,老师会觉得你确实理解了数仓设计,而不是只会建表。
3.3 离线任务调度:shell脚本还是调度框架?
任务调度的核心是“依赖正确、失败可重试、结果可检查”。我的项目里ODS->DWD->DWS->ADS是有严格顺序的,后一层必须等前一层跑完再启动。
最省事的做法是用一个Shell脚本串行执行,跑完一个阶段检查退出码,失败就退出:
bash复制#!/bin/bash
DT=$(date +%F -d "yesterday")
hive -f etl/ods_load.hql --hivevar dt=$DT || exit 1
hive -f etl/dwd_clean.hql --hivevar dt=$DT || exit 1
spark-submit --class com.example.RecommendJob recommend.jar --dt $DT || exit 1
spark-submit --class com.example.ItemCFJob itemcf.jar --dt $DT || exit 1
如果追求更规范的调度,可以引入DolphinScheduler或Azkaban。毕设项目里不强制,但如果你有时间和服务器资源,建议尝试DolphinScheduler,因为它支持可视化DAG编排,展示效果也好。
调度脚本里我强烈建议加两个冷门但实用的步骤:任务结束后检查HDFS分区是否存在、结果表行数是否在预期范围。这两个检查能帮你避免“任务显示成功但实际没有产出数据”的隐形问题。
4. Spark推荐计算:从用户画像到ItemCF落地的核心实现
4.1 基于内容标签的召回:先算用户画像与赛事标签
推荐系统通常分召回、排序两个阶段。这个项目里我用了“基于内容召回 + ItemCF排序”的组合,没有上复杂模型,但效果可解释性很强。
召回阶段要做的第一件事是算用户画像。我从DWS层读取用户-球队兴趣表,但直接聚合的原始数据不能直接用:一个只看过一次比赛的用户,和另一个经常看同一球队的用户,权重应当不同。我设计了一套加权评分规则:
- 观看时长权重:按分钟计算,超过30分钟的完整观看视为强兴趣。
- 行为权重:点击1分、点赞3分、分享5分、关注球队8分。
- 时间衰减:最近7天行为权重1.0,7-30天权重0.5,30天以上权重0.2。
最终用户对球队的偏好分是加权求和的结果。这个逻辑用Spark实现非常直观:
python复制from pyspark.sql import functions as F
df_score = df_interest.withColumn(
"pref_score",
F.col("total_duration") / 60 * 0.6
+ F.col("like_cnt") * 3
+ F.col("share_cnt") * 5
+ F.col("follow_cnt") * 8
)
df_user_feature = df_score.groupBy("user_id", "team_id").agg(
F.sum("pref_score").alias("pref_score")
)
赛事标签表更简单:每场比赛关联球队、联赛、比赛时间、热度值。热度值用“赛前1小时点击数 + 直播间人数 + 互动数”综合计算。这样,每场比赛就可以表示成多个标签:球队标签、联赛标签、时间窗标签。
召回逻辑很好描述:对于每个用户,找出他偏好分最高的前3支球队,再用这些球队匹配最近24小时内正在直播或即将开赛的比赛,过滤掉用户已经看过的场次,得到候选集。这一步保证了推荐结果一定与用户的历史兴趣强相关,同时也保证了时效性。
4.2 Spark实现ItemCF:评分矩阵、共现矩阵与相似度计算
召回之后,候选集可能还是几十场球,需要排序。排序我用了基于物品的协同过滤(ItemCF)。ItemCF的思想是:如果用户喜欢“湖人vs勇士”这场比赛,又喜欢“湖人vs快船”这场比赛,那这两场比赛是相似的。
ItemCF分三步:构造评分矩阵、计算物品间相似度、根据用户历史评分生成推荐分。
第一步,构造“用户-物品”评分矩阵。这里的“物品”我细化到“直播场次+球队关注”级别,更实际的做法是只对“球队”做相似度,再映射到比赛。为了完整性,我先写了通用的物品评分:
python复制# rating: user_id, match_id, rating
df_rating = df_events.groupBy("user_id", "match_id").agg(
F.sum("pref_score").alias("rating")
)
第二步,计算物品共现矩阵。用户对两个物品都产生过行为,就在这两个物品之间记录一次共现。然后以余弦相似度作为衡量标准:
python复制df_item_sim = df_rating.alias("a") \
.join(df_rating.alias("b"),
F.col("a.user_id") == F.col("b.user_id")) \
.filter(F.col("a.match_id") < F.col("b.match_id")) \
.groupBy("a.match_id", "b.match_id") \
.agg(
F.sum(F.col("a.rating") * F.col("b.rating")).alias("numerator"),
F.sqrt(F.sum(F.col("a.rating") * F.col("a.rating")) *
F.sum(F.col("b.rating") * F.col("b.rating"))).alias("denominator")
) \
.withColumn("sim_score", F.col("numerator") / F.col("denominator")) \
.filter(F.col("sim_score") > 0.1)
这个自连接在数据量大时会产生巨大中间结果,也是后面第5节要讲的性能瓶颈点。
第三步,为每个用户生成推荐结果。用户对某场比赛的预测评分,等于他历史看过的比赛评分乘以比赛之间的相似度,求和:
python复制df_rec = df_rating.alias("r") \
.join(df_item_sim.alias("s"),
F.col("r.match_id") == F.col("s.match_id_b")) \
.groupBy("r.user_id", "s.match_id_b") \
.agg(F.sum(F.col("r.rating") * F.col("s.sim_score")).alias("pred_score"))
最后按pred_score降序取TopN,并排除用户已经看过的比赛,写入ADS层推荐结果表。
4.3 为什么选ItemCF而不是ALS?
项目里一开始也尝试过Spark MLlib的ALS矩阵分解。ALS是基于模型的协同过滤,理论上精度可能更高,但在体育直播场景有几个问题:数据非常稀疏,很多冷门赛事几乎没有评分样本;ALS超参数较多,需要交叉验证调参;结果可解释性弱,一旦老师问“为什么推荐这场”,你很难用模型内部机制回答。
ItemCF的优点是对稀疏性容忍度更强,用户行为越多物品相似度越稳定;计算过程透明,每一步都能解释;实现代码量小,适合在毕设里深入拆解。
如果你确实想展示算法进阶,也可以在文档里补充ALS实验对比,但核心链路保持ItemCF就好。推荐系统的项目,完整性和自洽性往往比模型复杂度更能拿到分。
5. 实战中踩得最深的三个坑:小文件、数据倾斜、OOM
5.1 小文件问题:几十万个小文件是怎么来的
跑通第一版流程后,我打开HDFS一看,某个分区下居然有几千个几KB大小的文件。这个问题的直接后果是Spark读取HDFS时task数量爆炸,集群性能被严重浪费,NameNode内存也被无效占用。
小文件的来源主要有三个:日志采集端按小批次写入HDFS;Spark写数据时分区数设置过大;Hive动态分区插入时,每个分区对应多个文件。解决办法分不同阶段:
- Hive层面:对TEXTFILE外的表可以执行
ALTER TABLE ... CONCATENATE合并小文件。 - Spark层面:写数据前用coalesce或repartition控制输出分区数。
python复制df_result.repartition(50).write.mode("overwrite").parquet("/warehouse/ads/rec_result")
经验是:输出文件数大致控制在“数据大小/128MB”左右,50个分区对应大约6GB数据比较合理。
5.2 数据倾斜:热门赛事让整个Spark任务卡死
项目数据处理中碰到的最大拦路虎是数据倾斜。现象很典型:Spark UI里某个stage几乎所有task都是几秒完成,只有一个task跑了半个小时都不结束,最后报OOM或Executor lost。
定位方法看Spark UI的Stage详情,如果某个任务的Shuffle Read或Write大小远远大于其他任务,说明key分布极不均衡。在体育直播场景里,这个热点key大概率是“热门赛事ID”或“头部球队ID”,湖人、皇马这种级别的比赛数据量可能是普通比赛的上百倍。
解决数据倾斜最通用的方法是“加盐”。加盐的核心思想是把热点key拆成多个随机子key,先做部分聚合,再去掉盐做第二次聚合。以按match_id计算观看总时长为例:
python复制from pyspark.sql.functions import rand, concat, lit
df_salted = df_events.withColumn(
"salt", (rand() * 10).cast("int")
).withColumn(
"salted_match_id", concat(F.col("match_id"), lit("_"), F.col("salt"))
)
df_partial = df_salted.groupBy("salted_match_id").agg(
F.sum("duration").alias("partial_duration")
)
df_final = df_partial.withColumn(
"match_id", F.regexp_extract("salted_match_id", "^(.*)_\\d+$", 1)
).groupBy("match_id").agg(F.sum("partial_duration").alias("total_duration"))
对于不能加盐的纯排序场景,可以尝试过滤掉明显热点key、单独处理,或者把广播阈值调大,让关联操作避免shuffle。
5.3 小集群OOM与资源参数调优
3台8G内存的机器跑Spark,默认配置几乎是必炸。核心问题是Spark默认的executor内存和并行度都偏大,多个executor同时申请内存很容易直接把节点挤爆。
我最后实测稳定跑完任务的参数配置如下:
| 参数 | 初始值 | 调后值 | 说明 |
|---|---|---|---|
| spark.executor.memory | 1g | 3g | 单个executor内存上限,不要超过节点可用内存 |
| spark.executor.cores | 2 | 1 | 减少并发,防止CPU和内存一起争抢 |
| spark.driver.memory | 1g | 2g | driver需要收集结果和保存累加器 |
| spark.sql.shuffle.partitions | 200 | 50 | 默认200个shuffle分区在小集群上太多 |
| spark.sql.autoBroadcastJoinThreshold | 10M | 256M | 小表广播,减少大shuffle |
| spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version | 1 | 2 | 降低commit阶段的HDFS压力 |
另外我把Parquet作为默认存储格式,配Snappy压缩,既省空间查询速度也不差。如果集群出现瞬时OOM,还可以把任务拆成几个阶段,每阶段单独提交,效果很明显。
6. 如果让我重新做一遍这套毕业设计,我会在评估和交付上多花这些心思
6.1 效果评估:怎么证明推荐“有点用”
毕设答辩时老师一定会问:“你怎么知道推荐结果是好的?”如果只回答“通过协同过滤算出来的”,会显得很空洞。我建议至少做两个层面:
一是离线命中率实验。将历史数据按时间切分,用前30天数据计算推荐结果,第31天实际发生的观看行为作为验证集。统计用户看到推荐列表里的某场比赛后,是否产生了播放行为,计算命中率。这个数字不是越高越好,而是告诉你推荐链路是否跑通。
二是效果对比。可以做一个简单实验:随机推荐TopN和推荐算法TopN,分别统计模拟用户对两类列表的点击率。哪怕结果只是“从3%提升到8%”,也是一个可量化的说服点。
推荐领域的离线评估指标还有精确率、召回率、AUC等,但个人项目里不必强求全算。抓住“命中率”和“覆盖率”这两个直观指标,配合一个可视化页面展示推荐结果,已经足够讲清楚效果。
6.2 源码、文档和讲解PPT的整理心得
这类项目最后交付的通常不只是代码,还包括源码、LW文档、PPT和讲解。我见很多人把代码堆在一个文件夹里就完事,实际这不是好的交付方式。
源码结构我建议这样组织:
code复制bigdata-recommend-system/
├── docs/ # 需求文档、设计文档、部署文档、测试报告
├── etl/ # HiveSQL脚本、调度Shell脚本
├── spark/ # Spark推荐计算代码
├── web/ # SpringBoot后端 + 前端页面
└── sql/ # 建表语句、初始化数据
LW文档不要从网上抄模板,而是按照“需求分析、架构设计、数据设计、算法设计、系统实现、测试结果、总结反思”的顺序写。重点写清楚每个技术选型的理由和踩坑记录,这部分是老师最认可的内容。
PPT控制在10页左右就够了:第一页背景和目标,第二页总体架构图,第三页数据链路,第四页数仓设计,第五页推荐算法流程,第六页核心界面演示,第七页效果评估,第八页总结与展望。讲解时最重要的不是炫技,而是“为什么这么设计”的思考过程。
6.3 从离线到实时:这套系统还能往哪个方向扩展
这套系统完全是T+1离线模式,但真实体育直播场景对实时性要求很高。如果后续想扩展,最自然的路径是引入Kafka和Spark Streaming。用户行为日志实时写入Kafka,Spark Streaming做窗口统计,每分钟更新一次赛事热度榜,再直接推送到首页接口。这个扩展不会改变离线数仓的主体结构,只是新增一条热数据通道。
再进一步,可以用Flink替代Spark Streaming做更复杂的实时特征计算,把用户近5分钟行为实时更新到Redis。但说实话,这些内容对毕设项目性价比偏低,容易把战线拉长。我建议先把离线链路做到干净、稳定、可解释,再根据精力决定是否加实时模块。
整套项目做完,我最大的体会是:这类系统真正的加分项不是把Hadoop、Spark、Hive这几个词堆在PPT上,而是你能把一条数据链路从头串到尾,并且清楚说出每个环节为什么这样取舍。
最后分享一个自己用着很顺手的细节:所有任务跑完之后,一定要在调度脚本末尾加一段“产出检查”,用HDFS命令查一下分区文件数量,再用Hive查一下结果表行数。之前有几次任务日志显示成功,实际因为上游迟到数据导致结果表是空的,直到早上起来看推荐页面才反应过来。加了检查逻辑之后,这个问题基本杜绝了。做数据项目,宁可多一个无聊的检查,也不要等出了问题才发现少了一张表。
