基于本文回答
0
评论

计算每个商品详情页的跳出率。定义为:用户进入该商品详情页后,未进行任何其他页面跳转(如加购、点击推荐、返回搜索等)便直接关闭了页面的会话占比

面试题:计算商品详情页的跳出率

1. 题目背景与定义

在电商数据分析中,商品详情页的跳出率是一个衡量商品吸引力及流量质量的核心指标。
本题定义:用户进入某商品详情页后,未进行任何其他页面跳转(如加购、点击推荐、返回搜索等)便直接关闭了页面的会话占比。
即:在一个会话(Session)中,如果用户访问了某商品的详情页,且该次访问是该会话的最后一次操作(后续无任何行为),则视为一次“跳出”。

跳出率计算公式:
商品 A 的跳出率=以商品 A 详情页为终点(后续无行为)的会话数访问过商品 A 详情页的总会话数\text{商品 A 的跳出率} = \frac{\text{以商品 A 详情页为终点(后续无行为)的会话数}}{\text{访问过商品 A 详情页的总会话数}}


2. 输入数据:用户行为日志表 user_behavior

session_id (会话ID) user_id (用户ID) page_name (页面名称) item_id (商品ID) event_time (事件时间)
s01 u01 detail 101 2023-10-25 10:00:00
s01 u01 cart 101 2023-10-25 10:01:00
s02 u02 detail 101 2023-10-25 10:05:00
s03 u03 index NULL 2023-10-25 10:10:00
s03 u03 detail 101 2023-10-25 10:11:00
s04 u04 detail 102 2023-10-25 10:20:00
s05 u05 detail 102 2023-10-25 10:30:00
s05 u05 detail 101 2023-10-25 10:32:00
s06 u06 detail 101 2023-10-25 10:40:00

注:page_name 为 'detail' 表示商品详情页,此时 item_id 记录商品ID;其他页面如 'cart'(购物车)、'index'(首页)的 item_id 可能为具体值或 NULL。


3. 期望输出结果

item_id (商品ID) total_visits (总访问会话数) bounce_visits (跳出会话数) bounce_rate (跳出率)
101 5 4 80.00%
102 2 1 50.00%

结果解析:

  • 商品 101:在会话 s01, s02, s03, s05, s06 中被访问过(共5个会话)。其中 s02, s03, s05, s06 中,101详情页是会话的最后一步(无后续行为,计为跳出);s01 后面有 'cart' 行为(未跳出)。因此跳出率 = 4 / 5 = 80%。
  • 商品 102:在会话 s04, s05 中被访问过(共2个会话)。其中 s04 访问后无后续(跳出);s05 访问 102 后跳转到了 101(未跳出)。因此跳出率 = 1 / 2 = 50%。

4. SparkSQL 核心解法

sql
WITH pv_with_next AS (
    SELECT
        session_id,
        page_name,
        item_id,
        event_time,
        -- 使用窗口函数获取当前会话中下一次事件的时间
        LEAD(event_time) OVER (PARTITION BY session_id ORDER BY event_time) AS next_event_time
    FROM user_behavior
)
SELECT
    item_id,
    COUNT(DISTINCT session_id) AS total_visits,
    SUM(CASE WHEN next_event_time IS NULL THEN 1 ELSE 0 END) AS bounce_visits,
    CONCAT(
        ROUND(
            SUM(CASE WHEN next_event_time IS NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(DISTINCT session_id), 
            2
        ), 
        '%'
    ) AS bounce_rate
FROM pv_with_next
WHERE page_name = 'detail'
GROUP BY item_id;

5. 面试官视角:SparkSQL 深度分析与调优

在面试中,写出 SQL 只是第一步,能够深入分析执行原理、潜在瓶颈并给出调优方案,才是决定能否拿到 Offer 的关键。

分析一:窗口函数 LEAD 的物理执行过程
  1. Shuffle(洗牌阶段)OVER (PARTITION BY session_id ORDER BY event_time) 会触发 Spark 的 Shuffle 过程。Spark 会根据 session_id 的 Hash 值将数据重分区,确保相同 session_id 的所有数据流向同一个 Executor 的同一个 Task。
  2. Sort(排序阶段):在 Task 内部,数据会按照 event_time 进行内存或磁盘排序。
  3. Scan(滑动窗口扫描):排序完成后,Spark 能够高效地通过指针移动,直接获取当前行的下一行(LEAD)数据,避免了自连接(Self-Join)的高昂代价。
分析二:数据倾斜(Data Skew)风险与应对

在实际电商场景中,上述 SQL 面临巨大的数据倾斜风险:

  • 问题起因:某些爬虫程序或公共账号可能产生极其庞大的 session_id(一个会话包含数十万条记录),导致 Shuffle 时特定 Task 负载过重,出现 OOM (Out of Memory) 或严重的拖尾(Straggler)现象。
  • 面试加分解决方案
    1. 前置过滤:在 WITH 子句前,过滤掉异常活跃的会话(如单会话 PV > 1000 的爬虫)。
    2. 减少参与窗口计算的数据量:如果只需要判断是否为最后一步,可以在做窗口计算前,先过滤掉明显不影响结果的日志(但此题需要看“下一次行为是否存在”,故前置过滤无用行为需谨慎)。
    3. 两阶段聚合(针对 Group By item_id 的倾斜):如果某个爆款商品的 item_id 极其庞大,导致最后的 GROUP BY item_id 倾斜。可以先对 item_id 引入随机两阶段聚合,或者利用 Spark 的 Adaptive Query Execution (AQE) 自动倾斜合并功能。
分析三:替代方案对比(Window vs Group By)

如果不允许使用 LEAD 窗口函数,通常会采用 JOIN 方案:

  1. 先找出每个 session_idMAX(event_time) 作为会话终点。
  2. 再用原表与终点表进行 LEFT JOIN
  • 对比结论:Spark 官方推荐使用窗口函数。因为 JOIN 方案需要两次扫描原表,并引入一次大表 Join(产生额外的 Shuffle),而 LEAD 窗口函数只需要一次 Shuffle 和一次线性扫描,效率明显优于 JOIN 方案。
右滑查看面试常问