在线统计是很多 SNS 的常见功能。最近在优化 V2EX 的过程中,实现了一种新的方式,性能不错,分享给大家。
dbsize()
就可以获得,不会遇到 keys()
可能带来的性能问题 1
songjiaxin2008 2016-08-12 17:11:55 +08:00
等于说并不是非常精确的统计(因为 TTL )吗?可能 WebSocket 可以规避这个问题。
|
2
nigelvon 2016-08-12 17:14:05 +08:00
思路不错~学习了~
|
3
holyghost 2016-08-12 17:17:55 +08:00
思路不错
|
4
qiayue 2016-08-12 17:22:19 +08:00
@songjiaxin2008 在线人数一般都是统计当前这一刻往前一段时间(如 15 分钟或者 30 分钟)的人数
|
5
southwolf 2016-08-12 17:23:05 +08:00
@songjiaxin2008 论坛并不需要特别精确的在线统计,大多数时候都不需要。 websocket 还是太昂贵了
|
6
wy315700 2016-08-12 17:35:28 +08:00
dbsize
(error) ERR protocol is not supported 一部分云服务并不支持这条命令 |
7
kn007 2016-08-12 17:40:49 +08:00
单纯只做在线统计会不会比较浪费?
其他功能呢? |
8
latyas 2016-08-12 17:57:00 +08:00
这是用户每次刷新页面都会更新 ttl 的节奏?
|
9
http2 2016-08-12 18:04:35 +08:00
哈哈,我也用的这个方法。
|
10
KiseXu 2016-08-12 18:09:15 +08:00
每一次刷新页面,更新 TTL ,这个数据库不但可以统计在线总人数。还可以判断一条记录是否存在来判断用户的在线状态。
|
11
dangyuluo 2016-08-12 18:11:11 +08:00
也算是一个新思路。
|
12
scott1743 2016-08-12 18:37:59 +08:00
dbsize 亮了,单独用 redis 的一个数据库来存 session 会不会更方便?
|
13
changshu 2016-08-12 18:39:17 +08:00 1
redis 清理 ttl 过期元素不是即时的, dbsize 会偏大一点, 印象里 keys 虽然性能糟糕, 但会清理过期元素.
感觉 sorted sets 的方案更折中一点. |
14
est 2016-08-12 18:43:54 +08:00 2
用 sorted set 的 zrange 性能更好。还可以看谁最后上线啥的。
|
16
ooonme 2016-08-12 20:34:00 +08:00 via iPhone
我们是用 web 日志算的,有几分钟延迟还 ok
|
17
workwonder 2016-08-12 21:16:44 +08:00 via Android
elasticsearch 吗?
|
18
Jaylee 2016-08-12 21:56:54 +08:00 1
正确的做法应该是用 setbit
|
20
yyfrankyy 2016-08-12 22:29:28 +08:00
只是为了数字的话为何不用 setbit
|
21
est 2016-08-13 00:17:44 +08:00
@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 加速,那么效率也一般。 |
22
fork3rt 2016-08-13 07:40:00 +08:00 via iPhone
TTL 过期时间是多少呢?
|
23
wujunze 2016-08-13 10:19:45 +08:00
思路不错 可以试试
|
25
huangz 2016-08-13 13:19:19 +08:00 14
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 |
27
changwei 2016-08-13 15:28:51 +08:00
@scott1743 我也有同样的疑问, session 是有过期时间的,为什么不考虑直接统计 session 数据库的 dbsize 呢?
|
28
tairan2006 2016-08-13 15:44:15 +08:00
如果要求不精确的话,从 log 端做流数据处理的时候异步统计更简单吧
|
30
fengjianxinghun 2016-08-13 21:19:19 +08:00 via iPhone
@huangz 不错,期待大作
|
31
owt5008137 2016-08-13 21:30:17 +08:00 via Android
这个想法很有意思呀
|
33
codingbody 2021-05-12 23:31:25 +08:00
@huangz 老哥请教一下,有没有什么办法做到,统计 session 持续时间每超过 30min 次数(持续时间在 30min 内记 1 次,60min 内记 2 次,有点类似一个 session 每超过 30min 相当于是一个新的 session,只是 session id 没有变),目的是为了计费使用。
这里有详细一点的描述: https://v2ex.com/t/776514 |