请教诸位一个 redis 统计访问量增速的问题

2021-04-10 18:46:20 +08:00
 waibunleung

自己搞了个小程序,需要统计一下某些内容的访问量增速,比如某个视频 10 分钟内的访问量,在内存有限的情况下(最多 8g),用 redis 来做的话,怎么做比较好?因为存在时间窗口的问题,不知道采用哪种方法比较好。

关于用户,没登录什么的,所以也不会有什么用户 id 之类的东西

  1. 有序集合,毫秒时间戳为 score,但是如果比较多访问的话,内存占用比较大,实际上我只想要一个数而已
  2. 普通的 key,但是怎么去除 10 分钟前的数据?每分钟存一次访问量数据的话,要统计一个视频的 10 分钟访问量还好,但是要统计多个的时候,就要多次读取 redis
  3. 有没有更好的办法? 成本有限,加内存什么的就先不考虑了吧
3871 次点击
所在节点    程序员
49 条回复
waibunleung
2021-04-10 23:37:40 +08:00
@opengps 嗯嗯,这种可以考虑一下
waibunleung
2021-04-10 23:56:59 +08:00
@vebuqi 看懂了大部分,但疑问是下一个 10 分钟的时间窗口,是另起新的 key 统计吗?还是将旧的清零?比如 10:59 分 过渡到 11:00 的时候,此时 bitmap 上 0 这个 field 还是存储着 10:50 的统计?还是怎么样
angryfish
2021-04-11 09:46:05 +08:00
你这小程序多大的量,还担心数据库写入太频繁。用户访问一次,写入一条记录,大多数情况下没什么问题
keakon
2021-04-11 10:32:46 +08:00
总视频数、每 10 分钟总的访问量说下量级吧,应用场景是怎样的,感觉你想得太多了。

比如「我想做一个管理后台…」,你管理后台每秒能刷几千次啊,还能被刷爆?

而如果是面向公众用户的场景,你的需求也不需要「实时、精确地计算出访问增量」,每分钟统计一下就行了。

最后,这种情况比较常见的做法是使用时间轮,比如选一个比 10 大又能被 60 整除的数,例如 12 。将当前的分钟数对 12 取模,然后每个 key 记录这一分钟的数据。每到一个新的一分钟,就把 11 分钟前的 key 删了。要获取数据时,就取当前和之前的 10 个 key 。
至于数据结构选啥,根据你的视频量级、视频数是否恒定、id 是否连续等而定,不同的方案内存差别很大。比如一楼提到的 view_count_{vid}_{minute},这种实现不用看也知道内存扛不住。
xuanbg
2021-04-11 10:34:00 +08:00
用 hash 类型存储,每个文件一个 key 。这个 key 下面每 10 分钟新增一个 hash key,在这个时间段里,每访问一次这个 hash key 的 value +1 。
vebuqi
2021-04-11 11:18:25 +08:00
@waibunleung

单个视频的访问量用两个 key 来存储,过期时间设置成两个周期,也就是 20 分钟
写入时,每次都对当前周期内的 key 写入
读取时,读取上个周期的后半部分和这个周期的前半部分

如:
现在是 10:56,则目前数据库中的 key 有两个,v:${id}:cnt:bit:1040 (过期时间还有 4 分钟) 和 v:${id}:cnt:bit:1050 (过期时间还有 14 分钟),1040 代表 10:4X 周期内的计数,10:50 代表 10:5X 周期内的计数

若 10:56 新增 10 个,则对 v:${id}:cnt:bit:1050 内第 6 个域 incr 10 即可
若读取最近 10min 的访问量,则取前一个 key 的后 3 个域( 10:47 、10:48 、10:49 )和当前 key 的前 7 个域( 10:50 ~ 10:56 ),然后求和

这样的话就避免了你提到的问题,但增加了一倍的存储(两个 key )

再复杂一点的方案,单 key 中前 N 位用 1 个单独的域,记录上次写入时间,每次写入前,根据当前时间和上次写入时间判断是否要重置之前的某个 /某些域,然后再进行写入+(重置)的操作。这样的话,存储能降到一个 key,但写入时多了一次操作,时间换空间了,适用于 写少读多的场景,所以最好是聚合写
luzhh
2021-04-11 13:12:12 +08:00
每次请求写到 log 里用 shell 统计都很快的。先实现出来,然后在看满足不满足性能要求和功能需求,然后在看怎么优化改进方案。
waibunleung
2021-04-11 13:27:53 +08:00
@angryfish 日活目前在 1w 多有,预计会增长至 10w
waibunleung
2021-04-11 13:31:56 +08:00
@keakon 就算用一楼的办法,每分钟也只是同一个普通的 key 。而且也会删掉,”不用看也知道内存扛不住“ 是不是太武断了些....我觉得抗住是没有问题的...
waibunleung
2021-04-11 13:32:51 +08:00
@xuanbg 那这样子就没有了时间滑动窗口了,我也不会问这个问题了
waibunleung
2021-04-11 13:33:43 +08:00
@luzhh 是的,今天想到了这个,直接解析 nginx 的 log 也是个办法
luzhh
2021-04-11 13:48:16 +08:00
或者将每一条视频播放请求发到队列用 flink 的滑动窗口去统计,你那服务器的配置干这个完全不在话下。
xuanbg
2021-04-11 14:22:42 +08:00
@waibunleung 你要怎样滑动?以什么为单位?要以分钟为单位滑动,按分钟计数,按秒滑动就按秒计数。不还是一样的道理吗?
siweipancc
2021-04-11 15:35:11 +08:00
@vebuqi 最近有个类似的需求,感谢
billlee
2021-04-11 16:28:58 +08:00
说个 100K QPS 级的方案:

1. 在内存(或 redis )里记数,定时 (比如 10s) flush 到持久存储 (mysql 或 influxdb) 并 reset 内存中的计数器
2. 查询的时候就是指定时间范围 SUM() 聚合了
3. 定期清理持久存储中的历史数据
4. 如果需要保留长时间历史数据,需要在持久存储那端做降采样

关键是 1. 写入的时候不要每次请求都落盘 2. 查询的时候要能用上 rdbms 的查询能力
liuhan907
2021-04-11 17:49:53 +08:00
如果是单纯的统计增速,那用时序数据库不是很合适么?比如用开源版的 influxdb 。
waibunleung
2021-04-12 09:59:08 +08:00
@xuanbg 换成一分钟之后,那这不就是一楼说的那种做法吗?
xuanbg
2021-04-12 10:02:40 +08:00
@waibunleung 楼主你有没有搞清楚自己的需求?需求确定了,数据结构自然就能确定了。数据结构确定了,存储方案自然就能确定了。即使有问题,至于连问题是什么都说不清吗?
waibunleung
2021-04-12 10:48:53 +08:00
@billlee 嗯嗯,有想过这个方法,历史数据可以放到冷数据表,但是这种方法如果要查更长的近 x 分钟时间的数据,视频越多,存储量就越多了,不知道你这个量级,一个固定时间内(比如 1 个小时),持久存储里面的记录数有多少?存储结构是怎么样的?
比如 10s flush 一次到 db 的话,一分钟就相当于有 6 条记录了?
waibunleung
2021-04-12 10:53:42 +08:00
@xuanbg 聊场外就没意思了,需求描述得挺清楚的了,就是要在内存有限的情况下用 redis 统计近 10 分钟的数据,做法不同数据结构不同,你后面说的这种做法和一楼二楼说出来的没有本质的区别,问题也在描述的地方说了,存在滑动窗口的问题,你的第一条回复本质是固定的时间窗口,我只是回复你这样做的话不满足我的需求,请问怎么看出我不了解需求和数据结构的问题呢?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/769758

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX