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

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

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

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

  1. 有序集合,毫秒时间戳为 score,但是如果比较多访问的话,内存占用比较大,实际上我只想要一个数而已
  2. 普通的 key,但是怎么去除 10 分钟前的数据?每分钟存一次访问量数据的话,要统计一个视频的 10 分钟访问量还好,但是要统计多个的时候,就要多次读取 redis
  3. 有没有更好的办法? 成本有限,加内存什么的就先不考虑了吧
3567 次点击
所在节点    程序员
49 条回复
Jooooooooo
2021-04-10 18:50:03 +08:00
每分钟一个 key 呗.

key: view_count_{vid}_{minute}

然后想统计十分钟的就捞十个 key 求和
opengps
2021-04-10 18:55:11 +08:00
视频 id+用途后缀:
一个计数缓存:来了统计请求就自动加一。每分钟转存到归档
一个 hash 归档缓存:每分钟存入一份分钟级别统计。
至于分析,就只从归档 hash 里取值统计,按照分钟 key 去看最近十分钟的结果。统计时候主要依靠 hash 的批量命令操作来减少读取次数
carity
2021-04-10 18:57:15 +08:00
直接搞个数据库存呗,这样你想怎么统计都可以了,mysql pgsql 占用内存也不大
waibunleung
2021-04-10 18:59:54 +08:00
@Jooooooooo 这就是我提到的第 2 种做法了~
但问题是,我想做一个管理后台,在视频列表上,显示每个视频的访问增速,如果我一页拿 20 个视频,每个视频捞 10 个 key,20 个视频就要捞 200 个 key,虽然说用 mget 读取也只要读 20 次....但我视频越多,key 就越多了
waibunleung
2021-04-10 19:00:41 +08:00
@carity 有数据库,但是每次视频被访问就直接写数据库+1 吗?那到时候数据库写压力就很大了
liujuzzz
2021-04-10 19:04:04 +08:00
i
waibunleung
2021-04-10 19:10:27 +08:00
@opengps 每分钟转存到归档,归档指的是 mysql 之类的数据落地的东西?
另外每分钟转存是后台起一个脚本来做这个吗?
hash 归档缓存 就相当于只统计了 T-1 分钟的 访问数,这个倒可以接受
waibunleung
2021-04-10 19:10:54 +08:00
@liujuzzz 这是插眼的意思吗?
Jooooooooo
2021-04-10 19:22:48 +08:00
@waibunleung key 多不要紧, 查询做好分页就行.
ch2
2021-04-10 19:37:57 +08:00
你会不会高估了写压力? redis 都不需要,直接 mongo 一个表存就行了,查询也好查询,反正你这个表结构很简单,就视频 id 跟时间戳或者你再加个 ip,这种简单的数据,mongo 存一亿条也无压力
tianshiyeben
2021-04-10 19:49:24 +08:00
就是监控数据增量变化吧 用 http://www.wgstart.com 有数据监控模块 会定期扫描并生成趋势图表
tianshiyeben
2021-04-10 19:50:23 +08:00
忘了说了 前提是你得从数据库( mysql pgsql 等关系型数据库)用 sql 能捞到数据
waibunleung
2021-04-10 20:08:54 +08:00
@ch2 目前没有配置 mongo,mongo 存一亿条磁盘占用要去到多少呢?不用 redis 纯用 mongo 来承载读写的话,mongo 大概能抗住多大的 qps ?有没有稍微具体一点的数据可以支撑一下?
seth19960929
2021-04-10 20:21:37 +08:00
@waibunleung redis 有 pipe, 一次全部捞出来
waibunleung
2021-04-10 20:22:20 +08:00
@seth19960929 我知道这个
dzdh
2021-04-10 20:50:59 +08:00
硬盘允许的话搞个单机的时序数据库哇。
ch2
2021-04-10 20:51:46 +08:00
@waibunleung #13 如果只存我说的那三个字段,大概不到 5G 。写的话 qps 几千肯定没问题的,读取的话你可以用 redis 缓存结果,1 分钟全表查询一次就能得到统计结果
opengps
2021-04-10 21:22:09 +08:00
@waibunleung 我说的归档就是第一个 key 下产生的分钟级数据,存入第二个 key 下当归档数据,然后分析时候使用第二个 key
vebuqi
2021-04-10 22:47:33 +08:00
遇到过类似的问题,说下解决的方法

假设我们需要存储视频近 10 分钟内的访问量

存储方面,我们可以把 10 分钟,分割成 10 个 1 分钟,使用 Redis 的 bitmap 来存储这 10 个量

在命令方面,redis 的 BITFIELD 命令可以对 bitmap 的多个域同时操作,对每个域支持 GET 、SET 、INCRBY 子命令,可以满足需求

如图:

![image.png]( https://i.loli.net/2021/04/10/LBkiMupGR6ctXoT.png)


我们把 bitmap 分割成 10 个域,每个域代表 1 分钟的访问量,那么每次获取某个视频的访问量时,可以取到这 10 个域的值求和即可

假设每分钟视频的访问量的上限是 255 ( 2^8,这里是为了控制溢出,值可以无限大小,只要是 2 的正整数倍即可)


新增访问量时,只需计算应该往哪个 /哪些域( offset )里增加就可以了,如 22:13 的视频 123456 的访问量新增 17,根据时间计算 offset 为 3,命令:

`BITFIELD v:123456:cnt:bit OVERFLOW SAT INCRBY u8 0 17

这条命令返回对 offset 为 0 的域进行了+17 的操作,u8 表示按照 10 个 bit 分域,上限 255,0 标识 offset,即第 0 分钟,`OVERFLOW SAT`表示如果 incr 后的结果超过上限(这里是 2^8 ),那么结果保持在最大值 255 ( 8 位全 1 )



获取视频 123456 近 10 分钟的访问量,命令:

`BITFIELD v:123456:cnt:bit GET u8 GET u8 1 GET u8 2 GET u83 GET u8 4 GET u8 5 GET u8 6 GET u8 7 GET u8 8 GET u8 9`

这条命令会返回每个域(即每分钟)的值,求和后即为近 10 分钟的累计访问量

BITFIELD 的每个子命令的复杂度是 O(1)的,如果访问 /操作 N 个视频的近 10 分钟的访问量,也就是操作 N 次 Redis 即可



方案的优点

1. 省空间,bitmap 占用空间很小
2. 支持批量,BITFIELD 的子命令可以多个同时操作

缺点:

1. 不是严格的滑动窗口,有一定的精度损失

这个可以通过拆细粒度来解决,如 10 秒(甚至 1 秒)一个域,相应地,这样会增加一定的存储

2. 设计时需要考虑单个单位时间内的上限,超过上限时,统计不准,因为我们在溢出控制时使用了饱和算法( SAT )

这个可以在设计初期尽量预留一个保险的值,当然了,越大的话,存储也会越大

3. 对于读写分离的场景(即读从写主),BITFIELD 被 Redis 标识为写命令,所以所有的 BITFIELD 都会在主节点上执行

这个问题我们遇到了,但没有造成很高的负载,所以没有处理;不过阿里云有篇文章可以参考: https://developer.aliyun.com/article/757841
waibunleung
2021-04-10 23:37:09 +08:00
@ch2 这种方案的话,还不如先写到 redis 一分钟同步一次数据到 mysql,基本没有写压力,方案上更趋近于这个

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

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

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

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

© 2021 V2EX