SparkSQL如何优化最近N天下单金额最大值

需求背景

已知用户订单金额表user_order_amt_1d,计算最近N天用户下单金额最大值(N = 3、30、180)

user_id(用户id)

amt(当日下单金额)

dt(时间分区)

001

10000

20251101

002

20000

20251101

003

30000

20251101

001

1000

20251102

002

20000

20251102

003

35000

20251102

.........

......

......

初级解决方案

  • 这个需求本身并不难,只需分别求出最近3天用户金额最大值最近30天用户金额最大值最近180用户金额最大值,然后进行关联即可
spark.sql(
  s"""
     |select
     |  t1.user_id,
     |  t1.max_amt_3d,
     |  t2.max_amt_30d,
     |  t3.max_amt_180d
     |from (
     |select user_id, max(amt) as max_amt_3d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-2}'
     |group by user_id
     |) t1
     |JOIN (
     |select user_id, max(amt) as max_amt_30d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-29}'
     |group by user_id
     |) t2
     |on t1.user_id = t2.user_id
     |JOIN (
     |select user_id, max(amt) as max_amt_180d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-179}'
     |group by user_id
     |) t3
     |on t1.user_id = t3.user_id
     |""".stripMargin).show

存在的问题

1、【数据量大】同一个数据源被读取了多次,数据量剧增

2、【Shuffle多】多张表之间JOIN,存在多次Shuffle

3、【扩展性差】如果业务需要计算最近90天或者360天用户下单金额最大值时,需重新修改代码

进阶解决方案

  • 针对上述问题1和问题2,对SQL可进行如下优化:
spark.sql(
  s"""
     |select
     |  user_id,
     |  max(case when dt >= '${dt-2}' then amt else 0 end) as max_amt_3d,
     |  max(case when dt >= '${dt-29}' then amt else 0 end) as max_amt_30d,
     |  max(case when dt >= '${dt-179}' then amt else 0 end) as max_amt_180d
     |from user_order_amt_1d
     |where dt <= '$dt' and dt >= '${dt-179}'
     |group by user_id
     |""".stripMargin).show

存在的问题

  • 此方案,数据源只会被读取一次,同时避免了多张表的JOIN操作,但是 user_order_amt_1d是亿级用户表,我们每日扫描最近180天的用户数据,显然读取的数据量仍然很大,进行shuffle仍然效率低下

终极解决方案

  • 如何避免每日读取最近180天的用户数据?
  • 我们可以保存昨日计算的状态,然后和今日数据进行合并,具体操作如下:
  • 设计一个用户下单金额累计表,存储最近180天下单金额的数组
  • 使用array_max函数+slice函数即可获取最近N天下单金额中的最大值(N <= 180)
// 1、存储最近180天下单金额
spark.sql(
  s"""
    |insert overwrite table user_order_amt_td partition(dt = '$dt')
    |select
    | t1.user_id,
    | case when size(t1.amt_180d_arr) >= 180 then concat(array(t2.amt), slice(t1.amt_180d_arr, 1, 179))
    |   else concat(array(t2.amt), t1.amt_180d_arr)  end as amt_180d_arr
    |from (
    |select user_id, amt_180d_arr
    |from user_order_amt_nd
    |where dt = '${dt-1}
    |) t1
    |join (
    |select user_id, amt
    |from user_order_amt_1d
    |where dt = '$dt'
    |) t2
    |on t1.user_id = t2.user_id
    |""".stripMargin).show
// 2、求解最近3、30、180天下单金额最大值
spark.sql(
  s"""
    |select
    | user_id,
    | array_max(slice(amt_180d_arr, 1, 3)) as max_amt_3d,
    | array_max(slice(amt_180d_arr, 1, 30)) as max_amt_30d,
    | array_max(amt_180d_arr) as max_amt_180d
    |from user_order_amt_td
    |where dt = '$dt'
    |""".stripMargin).show

思考

以上三种方案都未能解决扩展性差的问题,我们是否可以在不改动业务代码的前提下,完成近N天用户下单金额最大值的扩展呢?(N为任意值)

如果当前架构解决不了,那就加一张表,假设还解决不了,怎么办?

下期我会详细分享如何通过配置化的方式来求解最近N天下单金额最大值

#面试问题记录##数据人的面试交流地#
大数据开发面试笔记 文章被收录于专栏

包括大数据篇、计算机语言篇、计算机基础篇、算法刷题篇、面试经验篇等五大篇章: 大数据篇包括框架原理、源码解析、调优技巧、大数据场景题、项目实战、数仓理论等模块;计算机语言篇包括Java、Linux、大厂常考SQL面试题等模块;计算机基础篇包括计算机网络、操作系统、数据库、数据结构等模块;算法刷题篇包括大厂高频算法题、刷题速成计划等模块 面试经验篇包括BAT、美团、字节、快手、京东等大厂的面经合集

全部评论
这个原题是再哪
点赞 回复 分享
发布于 今天 20:14 陕西
mark
点赞 回复 分享
发布于 今天 10:46 北京

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务