Livid
403.84D
566.44D
V2EX  ›  Redis

关于用 Redis 做在线人数统计

  •  1
     
  •   Livid ·
    PRO
    · Aug 12, 2016 · 25965 views
    This topic created in 3738 days ago, the information mentioned may be changed or developed.

    在线统计是很多 SNS 的常见功能。最近在优化 V2EX 的过程中,实现了一种新的方式,性能不错,分享给大家。

    1. 使用一个单独的 Redis 数据库
    2. 每个在线用户是一条带有 TTL 的记录,在每次 Session 开始时写入这条记录到 Redis
    3. 需要统计当前有多少人在线的话,只需要在这个数据库上用 dbsize() 就可以获得,不会遇到 keys() 可能带来的性能问题
    33 replies  •  2021-05-12 23:31:25 +08:00
    songjiaxin2008
    songjiaxin2008
    Aug 12, 2016
    1
    等于说并不是非常精确的统计(因为 TTL )吗?可能 WebSocket 可以规避这个问题。
    nigelvon
    nigelvon
    Aug 12, 2016
    2
    思路不错~学习了~
    holyghost
    holyghost
    Aug 12, 2016
    3
    思路不错
    qiayue
    qiayue
    Aug 12, 2016
    4
    @songjiaxin2008 在线人数一般都是统计当前这一刻往前一段时间(如 15 分钟或者 30 分钟)的人数
    southwolf
    southwolf
    Aug 12, 2016
    5
    @songjiaxin2008 论坛并不需要特别精确的在线统计,大多数时候都不需要。 websocket 还是太昂贵了
    wy315700
    wy315700
    Aug 12, 2016
    6
    dbsize
    (error) ERR protocol is not supported


    一部分云服务并不支持这条命令
    kn007
    kn007
    Aug 12, 2016
    7
    单纯只做在线统计会不会比较浪费?
    其他功能呢?
    latyas
    latyas
    Aug 12, 2016
    8
    这是用户每次刷新页面都会更新 ttl 的节奏?
    http2
    http2
    Aug 12, 2016
    9
    哈哈,我也用的这个方法。
    KiseXu
    KiseXu
    Aug 12, 2016
    10
    每一次刷新页面,更新 TTL ,这个数据库不但可以统计在线总人数。还可以判断一条记录是否存在来判断用户的在线状态。
    dangyuluo
    dangyuluo
    Aug 12, 2016
    11
    也算是一个新思路。
    scott1743
    scott1743
    Aug 12, 2016
    12
    dbsize 亮了,单独用 redis 的一个数据库来存 session 会不会更方便?
    changshu
    changshu
    Aug 12, 2016❤️1
    13
    redis 清理 ttl 过期元素不是即时的, dbsize 会偏大一点, 印象里 keys 虽然性能糟糕, 但会清理过期元素.

    感觉 sorted sets 的方案更折中一点.
    est
    est
    Aug 12, 2016❤️2
    14
    用 sorted set 的 zrange 性能更好。还可以看谁最后上线啥的。
    Livid
    Livid
    MOD
    OP
    PRO
    Aug 12, 2016
    15
    @est 没有用 sorted set 的原因是需要定期清理,如果不希望里面有太久远的数据的话。
    ooonme
    ooonme
    Aug 12, 2016 via iPhone
    16
    我们是用 web 日志算的,有几分钟延迟还 ok
    workwonder
    workwonder
    Aug 12, 2016 via Android
    17
    elasticsearch 吗?
    Jaylee
    Jaylee
    Aug 12, 2016❤️1
    18
    正确的做法应该是用 setbit
    julyclyde
    julyclyde
    Aug 12, 2016❤️1
    19
    @changshu
    @est
    zrange 有性能问题,不但这个 set 会慢,还会拖累整个 redis 实例
    yyfrankyy
    yyfrankyy
    Aug 12, 2016
    20
    只是为了数字的话为何不用 setbit
    est
    est
    Aug 13, 2016
    21
    @Livid @julyclyde

    假设 NOW() 得到当前 UNIX TIMESTAMP 时间戳, EXPIRE 是 session 过期时间,有如下方法:

    某用户 uid 上线了,刷新 session :

    zadd("ONLINE_USERS", uid, NOW() + EXPIRE)

    得到在线用户数:

    方法 1 :
    zcount("ONLINE_USERS", NOW(), 9999999999) # 时间复杂度: O(log(N))
    zremrangebyscore("ONLINE_USERS", 0, NOW()) # 时间复杂度: O(log(N)+M),可以异步,或离线进行

    方法 2 :

    zremrangebyscore("ONLINE_USERS", 0, NOW()) # 时间复杂度: O(log(N)+M)。可以定时任务执行。
    zcard("ONLINE_USERS") # 时间复杂度: O(1)

    @livid 那个方法是 O(N),
    @Jaylee 的 setbit ,如果没有用 CPU 指令 __POPCNT 或者 SSE2 加速,那么效率也一般。
    fork3rt
    fork3rt
    Aug 13, 2016 via iPhone
    22
    TTL 过期时间是多少呢?
    jinfeng333
    jinfeng333
    Aug 13, 2016
    23
    思路不错 可以试试
    nowcoder
    nowcoder
    Aug 13, 2016
    24
    @julyclyde 请问 zset 是什么性能问题?
    huangz
    huangz
    Aug 13, 2016❤️14
    25
    Livid 的这个思路很有趣!

    最近我也在研究这方面的问题,提供一些备选方案让大家参考。


    方案 1 :使用有序集合
    --------------------------------------

    每当用户上线时,执行以下操作:

    ZADD("online_users", <user_id>, <current_timestamp>)

    想要知道有多少人在指定的时间区间(比如一天或者一周)内上线过,那么可以使用时间区间的起始时间戳和结束时间戳作为参数,调用 ZCOUNT 命令:

    ZCOUNT("oneline_users", <start_timestamp>, <end_timestamp>)

    判断用户的 session 是否过期可以通过以下方法:

    user_online_timestamp = ZSCORE("online_users", <user_id>)
    return (user_online_timestamp+SESSION_EXPIRETIME) < now()

    其中 SESSION_EXPIRETIME 为 SESSION 的有效期秒数。

    这个方案的优点是信息齐全,能够通过有序集合的特性方便地执行区间操作( O(logN)),也可以快速地获取指定用户的登录时间( O(1))。缺点是耗费的内存比较大,并且需要手动删除有序集合中已经过期的用户信息。


    方案 2 :使用 HyperLogLog
    --------------------------------------

    用户上线:

    PFADD("online_users", <user_id>)

    获取在线人数:

    PFCOUNT("online_users")

    这个方案的优点是非常节约内存,无论网站的用户数量有多大,一个 HyperLogLog 都只消耗 12 KB 内存。当然,这个方案的缺点也非常明显:

    1. 它无法获取用户的具体登录时间。
    2. 因为 HyperLogLog 是一个概率算法,所以它无法准确地判断一个用户是否在线。

    以上缺点都可以通过增加一个储存用户登录时间的 Hash 来解决,不过这一样一来,需要消耗的内存也会增加。


    方案 3 :使用 bitmap
    ---------------------------------------

    上线:

    SETBIT("online_users", <user_id>, 1)

    检查指定的用户是否上线:

    GETBIT("online_users" <user_id>) == 1

    统计在线人数:

    BITCOUNT("online_users")

    这个方案最有趣的地方,就是可以对多个 bitmap 执行聚合计算,从而计算出诸如“有多少个人连续一周都上线了(全勤)”、“这周一共上线了多少个人”、“有多少人今天上线了但是昨天没上线”等问题:

    BITOP("AND", "one_week_both_online", "day_1_online", "day_2_online", ..., "day_7_online") # 计算一周都上线的人

    BITOP("OR", "one_week_online_total", "day_1_online", ..., "day_7_online") # 计算这周一共有多少人上线

    这个方案储存一个用户的在线信息只需要使用一个二进制位,对于用户数为 100 万的网站来说,使用这一方案只需要花费 125 KB 内存,而储存 1000 万的用户信息只需要花费 1.25 MB 。

    虽然 bitmap 节约内存的效果不及 HyperLogLog ,但是使用 bitmap 可以准确地判断一个用户是否上线。对于想要尽量节约内存,但又需要准确地知道用户是否在线,又或者需要对用户的在线信息进行聚合计算的应用来说,这个方案是最佳之选。


    结语
    ---------------------------------------

    好的,关于统计在线用户的备选方案就介绍到这里,希望这些方案会给大家带来帮助和启发。

    最后打个小广告,我正在写一本名为《 Redis 使用教程》的书,里面不仅对用户 SESSION 储存、用户在线统计等问题给出了详细的解法,还提供了实际可运行的 Python 代码,上面给出的一些方案在书中也有介绍,有兴趣的朋友可以关注一下: RedisGuide.com 非常感谢!

    huangz
    2016.8.13
    br00k
    br00k
    Aug 13, 2016
    26
    @huangz 学习了 ^ ^
    changwei
    changwei
    Aug 13, 2016
    27
    @scott1743 我也有同样的疑问, session 是有过期时间的,为什么不考虑直接统计 session 数据库的 dbsize 呢?
    keenkiller
    keenkiller
    Aug 13, 2016
    28
    如果要求不精确的话,从 log 端做流数据处理的时候异步统计更简单吧
    yangff
    yangff
    Aug 13, 2016
    29
    @changwei session 过期时间更长
    fengjianxinghun
    fengjianxinghun
    Aug 13, 2016 via iPhone
    30
    @huangz 不错,期待大作
    owt5008137
    owt5008137
    Aug 13, 2016 via Android
    31
    这个想法很有意思呀
    julyclyde
    julyclyde
    Aug 15, 2016
    32
    @nowcoder 100w 元素的时候 zrange 慢
    Reminders
    Reminders
    May 12, 2021
    33
    @huangz 老哥请教一下,有没有什么办法做到,统计 session 持续时间每超过 30min 次数(持续时间在 30min 内记 1 次,60min 内记 2 次,有点类似一个 session 每超过 30min 相当于是一个新的 session,只是 session id 没有变),目的是为了计费使用。

    这里有详细一点的描述: https://v2ex.com/t/776514
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   3607 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 103ms · UTC 10:07 · PVG 18:07 · LAX 02:07 · JFK 05:07
    ♥ Do have faith in what you're doing.