使用 Java +mysql+redis 实现一个简易的类似随机的数据获取算法,有哪些比较好的方案?目前我都有点怀疑需求是不是有问题了。

2021-01-10 18:05:13 +08:00
 CandyMuj

说明

  公司的小项目,没有打算使用搜索引擎或大数据相关的技术。

首先说一下产品的需求:

  目前有个视频列表查询,需实现每次启动 app 获取的第一页数据不同即可(如果不做处理,使用 mysql 每次获取数据的顺序都是一定的,则每个用户每次启动 app 获取的第一页数据都是一样的);还需要保证在用户没有获取完所有的视频前不再获取之前已经获取过的视频。即以每个用户为单位,看过的视频不能再被查出来,除非数据库的数据都被这个用户获取过一遍。产品不允许有重复的视频在用户没有看完所有视频前再被查询出来,无论数据量是大还是小。

举例说明

假设

​ 数据库总数据:10 条
​ id 为自增:1-10
​ 每页查询:4 条
​ 默认排序为 id 顺序自增

当前方案

如果保证每次返回的数据 id 是顺序自增的那么是没问题的,如果是倒序自增,也没问题,每次重启 app 取数据的时候 id>? 变为 id<? 即可。

遇到的问题

有一个特殊情况,管理员可以设置精选视频,可以把一些视频进行置顶,那么获取的数据就不是顺序或倒序自增的了,顺序就会乱。

而且还存在增删操作。

问题也就出现了,第二次启动 app 查询的第一页数据 id7 和 9 这两条数据就和上一次启动 app 查询的数据重复了;并且 3 4 6 8 这些数据永远不会被查询出来。问题就是这样,未实现产品需求。 (数据没有全部查询过一遍,距上一次查询就出现了重复数据)

这种情况和数据量的大小没有较大关系(如果把自增的最大的一个 id 推荐到了第一页的最后一条,那么就永远只能查到前四条数据)。

还需要考虑管理员设置精选或者进行增删后的数据的实时性问题。设置精选和增删操作如果频繁有哪些影响。

产品提供的一个方案

将每个用户看过的视频进行记录,然后在查询的时候进行剔除,如果数据不够一页就从头开始获取一次;这种方式就必不会出现未看完就重复的问题,但是有严重的性能问题。

逻辑是没问题的,但是数据量少还好,如果数据量很大那么性能影响很严重。

小声 bb,产品告诉我,同时满足需求和性能不就是开发应该干的事嘛,我很想说想要性能和需求同时满足也得看需求是否合理吧。

如果有 10w 用户并且有 10w 数据,在最极端的情况下,假设这 10w 用户有 99999 条数据都看过了,那么 redis 就会存储 10w*99999 的数据量,并且在查询 mysql 的时候语句就变成 not in(99999 个 id),想想就恐怖,如果数据量更大呢?

但产品认为不要考虑这么多我们系统最多只有几千条数据,用户量可能会很多,但即使这样数据量也比较大 10w*几千 ,并且也没有这样设计系统的,不合理。


  不局限于目前已有的这些方案或技术栈(使用除 java+mysql+redis 以外的技术也可),只要能实现这个需求的目的就可以:以用户为单位,数据在没有全部查询过一次的情况下,不能出现重复数据。期间需考虑管理员可以在任意时刻,或很频繁的进行精选和增删操作。

  各位有没有什么好的想法,以及使用 java+mysql+redis 的技术栈能否实现,若不能实现,是否有其他实现方式?

  再提一句,这个产品实际上是个 Android 开发。

  帮忙出出主意吧,先谢谢各位了,最近已经被折磨的焦头烂额了!

3794 次点击
所在节点    Java
51 条回复
CandyMuj
2021-01-10 22:10:45 +08:00
@yzbythesea 这种方式用 sql 查询的时候会用到 in(id1,id2,ids...),这样的话不走索引的,或者有什么方法可以替代 in 么?而且这种操作一般都不会在 app 端做处理的。
CandyMuj
2021-01-10 22:36:16 +08:00
@CandyMuj 还有一点,记录历史记录的时候是每次查询都记录视频 id,但是查询由于是分页的,在查询的时候需要加一个限制 historyId <= 第一页查询时的最大历史记录 id,不然就会出现下面的问题
有数据 id:1 2 3 4 5 6 7 8 9 10
首次查询不剔除数据
第一页:limit 0,2 => 1 2 3 4
记录看过的:1 2 3 4
第二页查询:
此时就剔除 1234:5 6 7 8 9 10
查询:limit 2,2 => 7 8

就会导致有些数据是被直接跳过了
CandyMuj
2021-01-10 22:38:16 +08:00
@CandyMuj 还有一点,记录历史记录的时候是每次查询都记录视频 id,但是查询由于是分页的,在查询的时候需要加一个限制 historyId <= 第一页查询时的最大历史记录 id,不然就会出现下面的问题
有数据 id:1 2 3 4 5 6 7 8 9 10
首次查询不剔除数据
第一页:limit 0,2 => 1 2
记录看过的:1 2
第二页查询:
此时就剔除 1234:3 4 5 6 7 8 9 10
查询:limit 2,2 => 5 6

就会导致有些数据是被直接跳过了
zieglar
2021-01-11 01:48:47 +08:00
@CandyMuj 其实都使用 redis 操作是可行的,首先维护好 redis 里对应的影片列表,在排序、新增、修改以后都同步到这个列表里,然后每个用户两个列表,一个是已看,一个是未看,初始数据时影片列表同步到未看,然后用 spop 来获取未看列表数据输出为 A 列表,然后把看 A 列表数据同步到已看列表里,后面的操作是当影片列表发生变化时,影片列表和已看进行 sdiff,把差异值覆盖到未看去,这样每次从未看拿到的数据符合影片列表的最新排序,且根据 A 列表数据也可以对从数据库的记录进行排序,并不觉得这样根据 ID 来搜索 mysql 的数据会早层什么性能问题
YouLMAO
2021-01-11 01:57:44 +08:00
标题写随机,内文没有,骗子
xyjincan
2021-01-11 02:06:14 +08:00
头一次见这么详细的说明帖子,管理员操作信息直接维护在列表数据库,用户已读数据单独建表,
hma
2021-01-11 02:22:35 +08:00
@CandyMuj #17 可以把精选列表独立出来。再记录精选的已阅列表。
yzbythesea
2021-01-11 07:15:17 +08:00
@CandyMuj 直接用 redis 存用户看不过没有? App 端还真有类似做法。
lldld
2021-01-11 08:40:20 +08:00
还是需要对每个用户存储其看过的视频, 但是每个 id 都记不合适.

大部分情况下, 给用户展示的视频是按 id 排序的, 少部分是随机的(精选).

记录的 id 也分两种形式, 比如 [1, 30], [40, 48], {51, 72, 99} 表示看过 1~30, 40~48, 以及 51, 72, 99.

那么当下要推荐给用户的视频, 应该是优先推荐精选, 然后就是以缩短上面的记录为原则.

比如今天的精选是 {200, 210, 22}, 然后首页需要 5 个视频, 那么再补上 49, 50 比较好.
sql 就是 select id in [200, 210, 22, 49, 50]

这样新的记录变成 [1, 30], [40, 51], {72, 99, 200, 210}
CandyMuj
2021-01-11 09:19:22 +08:00
@YouLMAO 我说了这是一个类似随机的方案,因为直接采取随机的话,会出现重复的数据,不满足需求。如果想实现随机+不重复 你可以在取数据的时候进行随机获取并踢除已看视频,如果数据不足一页就一次查询多页,查询多页需要注意一个问题,看第 23 楼
CandyMuj
2021-01-11 09:24:10 +08:00
@yzbythesea #28 我没有这样处理过 😂
CandyMuj
2021-01-11 09:29:32 +08:00
@zieglar #24 这样确实也可以,谢谢你的思路。
如果把数据全部维护到 Redis,个人不太喜欢这样的处理方式。
处理方式挺不错的,如果后续有其他需求可以用到这个思路!
CandyMuj
2021-01-11 09:31:28 +08:00
@yzbythesea #28 关于用户一般就是存个 token,缓存一些用户数据,直接用 Redis 当用户表就没玩过了 😂
CandyMuj
2021-01-11 09:32:03 +08:00
@hma 后续会考虑这方面的优化,谢谢!
CandyMuj
2021-01-11 09:36:55 +08:00
@xyjincan #26 最开始就是打算的这个方案,可能我当时技术欠缺或者没想到如何优化性能,然后就不打算采取这个方案。现在有了性能优化方案以及整个查询逻辑的细节处理方法,就可以用啦。

问题不描述清楚,我光解释就要解释半天。😂
CandyMuj
2021-01-11 09:37:53 +08:00
@lldld 感谢,好的想法,学习到了! ^^
CandyMuj
2021-01-11 09:39:14 +08:00
@lldld #29 暂时先记录所有吧,先出一个可以用的,然后再优化吧,这种不记录全部 id 的想法很棒!
CandyMuj
2021-01-11 09:44:19 +08:00
@YouLMAO #25 并且,我这是一篇求助帖,而不是教程帖。
zxyroy
2021-01-11 09:52:05 +08:00
我突然有个想法,既然没有要求每个用户顺序不能一样,不如直接生成一个乱序列表,每个用户都按这个乱序列表返回,新视频就攒 10 个再乱序追加到最后。每当有用户 load 完其中一个列表就再创建一个。每个用户一定要 load 完一个列表才能进入下一个。
kkkkkrua
2021-01-11 10:29:58 +08:00
给个 sort 列,专门用来排序,好像就可以解决你 id 乱序的问题吧

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

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

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

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

© 2021 V2EX