浏览历史数据库表设计与缓存设计

目录

1. 功能需求

2. 实现方案

3. 数据库设计

3.1 预测数据量
3.2 按用户区间分表
3.3 定时任务数据归档

4. 缓存设计

4.1 怎么存
4.2 哪里存
4.3 缓存时间设置

1 功能需求

最近,公司 app 需要增加一个新的功能是历史浏览记录的页面。

说道 “浏览历史”,大家应该都不陌生,跟浏览器的浏览历史功能基本一致。浏览历史的功能,仅限于用户浏览文章页面, 记录用户浏览过的文章,一方面方便用户下次再次查找,另一方面,也对我们将来对用户画像设计有所帮助。

历史记录图片


2 实现方案

为了记录用户浏览过的文章,我们只需要在用户访问文章详情的页面进行记录即可。

可以采用“异步方式”,将用户浏览过的文章记录。

但是目前用户上百万,每个用户一天浏览量不很清楚,但是随时间递增,这个记录一定很大;所以我们采用新建一个 RDS 实例, 专门用来存放历史记录。

面对庞大的数据量我们应该如何进行分表呢?


3 数据库分表

分表前,我们先思考影响这个表的有哪些变量?

一般都离不开 “时间” 和 “用户数”。 所以我们到底是要采用按“时间分表”还是“用户分表”呢?


3.1 预测数据量

我们可以到服务器上看文章详情请求数。

1
cat 20200214.log | grep -E "articles/[0-9]+" | wc -l

将一天文件中的文章详情数统计出来,也可以通过脚本获取一个月的文件进行统计。

然后我们可以根据我们的分表方式来对数据进行预测:

1) 通过时间-按月分表

这时候我们需要统计一个月的请求数,大致推测一个月数据 600w 左右的浏览量。

2) 通过用户-按用户id分表

这个时候统计的时候要考虑 user_id 的值,通过脚本给了个平均预测,一个用户一个月 50*30 = 1500。 (较大较理想情况,因为我们就是要防止一些大用户的操作)


根据数据和预测情况,我们可以简单看出其中的优缺点

1) 按月分表

优点:各个表数据量分布均匀

缺点

  1. 由于需求要展示最近三个月的数据,那么读取数据的时候比较麻烦,需要将多个月表的数据 union 后,进行排序和分页,union 后的数据不走索引,况且分页效率有点低。

  2. 如果用户突然增长,月表数据可能突破限制。这时候只能再通过月表再次分表。

2)按用户分表

优点:因为查询只针对个别用户可以充分利用索引,查询将十分简单高效,用户之间独立。

缺点

  1. 由于每个用户属性不一样,阅读数不等,造成数据量不平均。

  2. 用户数量级庞大,分表数量庞大

我们可以通过这两个优缺点进行取长补短,查询问题必须选用 “按用户分表”,那么如何解决用户表数量过多问题?

一般会有 按用户id取模分表,但是这种分表有缺点:

  1. 数量再次增大,想要再进行扩展分表比较困难;

  2. 取模运算如果是通过 mysql,取模将无法走索引;


3.2 按用户区间分表

所以我们可以选择 按用户区间分表,它呢补了上面取模的缺点,对后期单表中数据增大可以很方便继续再分; 例如我们 1w 个用户一张表,如果将来数据上亿,可以考虑变成 5k 个用户一张表,也就是在原来的基础上分成两张表, 通过脚本即可创建并迁移数据;

那么我们可以通过前面获得的访问量来确定大概要多少用户为一个区间;那么剩下一个问题了,当时间不短推移, 数据量还是很大,难道我们还要对再对用户分表吗?

1
2
3
$suffix = strval(floor($user_id / History::SPLIT_NUM));
$tablename = 'history'.$suffix;
$this->setTable($tablename);

3.3 定时任务数据归档

不,因为只展示用户最近三个月数据,所以我们可以通过定时任务对旧数据进行归档处理。 这样我们的表就一直控制在那个数据级;唯一可能上升的情况就是平均用户浏览数增多,不过影响不大,前面已经说了, 再次对用户进行划分也很简单。

注意,由于数据一直在这张表,所以主键就不要再用自增 id 了,因为日积月累的数据可能撑爆最大整型。可以用 UUID 或其他字符串类型等;



就此存储方案就好了,剩下显示方案;

关于显示,我们可以在表增加查询需要的所以,直接从中获取到我们的数据,速度很快;但是还有一个问题,就是 文章查询问题。

由于每篇文章可能根据运营人员更改而变动,所以一开始我们保存在浏览历史表中的数据将是关联id,例如此处是文章id;

这时候一种方法就是通过 join 文章表进行显示,但是这样三个月浏览记录去 JOIN 文章,可想而知这是一个噩梦;

那么我们还有另一个办法,通过缓存减少对文章的查询,也舍弃 join 文章的方法。


4 缓存设计

4.1 怎么存

如何设计缓存呢?和分表一样,我们也需要考虑几个变量。

这里我们要显示的是用户某一天查询的文章的分页,所以变量将是:

  • 用户id
  • 日期
  • 分页页码
  • 每页数量

如果用结果缓存,这个组合将非常巨大,并且 redis 存储不适合一次性存储超过 150kb 的数据;

所以我们可以通过 片段缓存 的方式来存储;

1)优点:多处地方都可以使用该片段缓存,缓存利用率高;

2)缺点:还是需要消耗一点点性能;

每个文章进行缓存,这样从性能上说,每次只是在浏览历史数据库将数据分页排序后取出,

对一页中的数据进行循环,每次循环直接内存中读取缓存即可。

最坏情况,循环每页数量去数据库搜索每篇文章(这个问题可以通过缓存存储触发位置来优化)。


4.2 哪里存

通过从产品上分析,用户是大多都是从文章列表点击文章详情进入,所以,我们可以在文章详情处就设置这个 片段缓存;当然,浏览历史的地方也要,只是浏览历史处命中率就变得非常高了,几乎已经先在文章详情处 进行了存储,所以循环取文章就变得非常少了;


4.3 缓存时间设置

TODO

Comments