Hive SQL复杂场景实现(2) —— 实时/最大在线人数

本文同时发表于我的个人博客http://xinyuwg.com,访问该链接以获得详细信息与更好的阅读体验。
本文为原创内容,未经允许请勿转载。

背景

这个问题一开始是一个同事问我的,能不能用SQL求某一天哪个时刻进行中的订单量最多,这个数是多少?我寻思挺有意思,就细想了一下。其实思考下可以发现,如果要求一段时间内的同时处于服务过程中的最大订单量,相当于也要知道每个时刻的数量,所以求最大和求实时是等同的。

这个问题在不同的业务场景下有不同的意义,比如一个游戏的同时在线人数,比如一个服务器的实时并发数,比如一个仓库的货物积压数量等等。

一般而言对于需要知道并发数的业务场景都会通过实时监控来完成,而不会通过sql进行离线计算。但本着深挖sql潜能的想法,如下提供一个不切实际的解法与一个真实可行的方法。

数据

假设我们的业务场景是回溯某一天的某游戏的最大同时在线人数,并有表connection_detail记录用户每一次上线和下线时间:

user_id login_time logout_time dt
213142 2019-01-01 12:21:22 2019-01-01 13:45:21 20190101
412321 2019-01-01 13:35:11 2019-01-01 16:01:49 20190101

解法1:(join关联)

我们先思考一个问题,假设我们实时有一个变量online_cnt记录着当前在线人数,那么什么情况下这个数会发生变化呢?聪明的你一定想到是当有用户登录或者有用户登出的时刻。说到这思路应该就清晰了,我们需要去求每一个用户登入或登出时刻的在线人数。从另一个角度,我们需要知道每当一个用户登录或登出的时刻,用多少人还在线上。


select 
    max(online_cnt) as max_online_cnt
from
(
    select 
        b.`timestamp`,
        count(1) as online_cnt --该时刻的在线人数
    from 
    (
        select
            `timestamp`
        from 
        (
            select
                unix_timestamp(login_time) as `timestamp`, --将时间转换为unix时间戳
            from
                connection_detail
            where
                dt = '20190101' --限定某一天
            union all
            select
                unix_timestamp(logout_time) as `timestamp`,
            from
                connection_detail
            where
                dt = '20190101'
        )a --取出每个并发数变化的时刻
        group by
            `timestamp` 
    )b --去重,可以思考下不去重会有什么影响
    left join 
    (
        select
            unix_timestamp(login_time) as login_timestamp,
            unix_timestamp(login_time) as logout_timestamp, --将时间转换为unix时间戳
        from
            connection_detail
        where
            dt = '20190101'
    )c
    --此处的关联为笛卡尔积
    where
        b.`timestamp`>=c.login_timestamp
        and b.`timestamp`<=c.logout_timestamp
    group by
        b.`timestamp`
)d

不愿吐槽这个方法,一是逻辑上比较绕,二是在真实的业务数据下根本跑不出来,假设原表中有n行数据,则在计算过程中数据将一度膨胀到O(n^2)。不知道为什么我会先想到这个方法。

解法2 (sum() over()):

我们先抛开sql,来考虑实时计算中我们怎么处理该问题。是不是我们会实时记录着一个变量online_cnt,当一个用户登录时加1,用户下线时减1?

再假如我让你用python离线处理这个问题你会怎么做。应该先把登录时间和登出时间这两列打散成行,然后进行排序,最后一行行读入,当读到一个登录时间时online_cnt加1,读到登出时间时online_cnt减1。

回到sql,我们来尝试用SQL实现上述逻辑。我们给登录时间加一个数值标记1,登出时间加标记-1。然后对排序后的数据求和该字段,最终得到我们的结果。

select
    max(max_index)
from 
(
    select
        sum(index) over(order by `timestamp`) as max_index --排序后第一行到本行的和
    from
    (
        select
            order_id,
            unix_timestamp(login_time) as `timestamp`,
            1 as index
        from
            connection_detail
        where
            dt = '20190101'
            and is_td_finish = 1
        union all
        select
            order_id,
            unix_timestamp(logout_time) as `timestamp`,
            -1 as index
        from
            connection_detail
        where
            dt = '20190101'
    )a  --将登录时间和登出时间多列成多行
)b

可能阻碍大家想到这一逻辑的点在于sum() over()这一用法,该窗口函数对每一行数据实现了计算第一行到该行的求和计算,具体介绍网上很多,不熟悉的同学可以百度一下。该代码对于千万量级的数据sparksql计算了65秒,属于一个可以接受的范围。

理解了上述代码的同学可以发现过程中我们一度得到了每个时刻的在线人数(子查询b)。对这一数据进行可视化可以直观了解服务器的负载变化情况。

你可能感兴趣的:(SQL)