使用 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 开发。

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

4014 次点击
所在节点    Java
51 条回复
qingluo
2021-01-11 10:39:05 +08:00
redis 有一个 hyberloglog 的数据结构 可以做做参考
Nillouise
2021-01-11 13:31:50 +08:00
没详细看题目,但我考虑过类似的问题,即信息流推荐,因为信息的内容是实时更新的,所以什么生成固定序列,其实不是个好办法,无法处理这种情况,也无法处理不同信息有权值的情况。

网上我看到有文章说,记录最多用户看过的 10000 个信息,然后直接过滤这 10000 个信息即可。
pushback
2021-01-11 13:32:51 +08:00
order by RAND()
MySQL 无所不能
simonlu9
2021-01-11 14:32:57 +08:00
自己用 redis 有序集合存吧,优先推荐那些就把权重提高一点,我是用 es 做的,可以满足很多需求
thinkmore
2021-01-11 18:45:15 +08:00
我有一个想法。把所有数据分配到一个环中。

然后给每个用户随机分配一个在环中的起始点。

当有精品视频时,就将这些排序好的重新构成环即可

@CandyMuj
OldCarMan
2021-01-11 23:42:10 +08:00
不知道,你说的不重复是什么样的时间频率,如果是每一天保证不重复(或者隔多长时间保证不重复)并且只靠 mysql 实现的话,加一个用户浏览记录表,然后每次查询加上一个 not in 之类的,最后加个定时任务,定时删除该频率内产生的这些记录数据。
auh
2021-01-12 05:13:21 +08:00
依赖 mysql 是没办法实现这种乱序操作的,必然带来 msyql 的压力。
视频记录必须 load 到 redis 。

分页,这种分页,本质上不是分页。而是单纯的下一批。
每页 10 个。就读取 redis 集合存储的 Vid10 个。

如果视频没有时效性,直接顺序读取 redis 列表就行。只为每个用户记录一个游标,因为对用户来说,用户感知不到随机性。兼容竞选列表也很简单。单独处理精选列表逻辑。合并两个结果集,保证一页总共 10 个就行了。

如果视频具备特殊属性。不仅是时效性。可能就需要,为每个用户记录已读列表了。
这个时候,优化的话,如果可以,对视频列表进行分片,用户读完一片之后,不需要再关心之前的片了。已读列表就很小了。
如果分片不了,相当于用户面向的是整张视频表的随机。这种必须完整记录。但是,现在可以考虑针对用户分组。不同组群的用户共享一个已读列表。每个用户只持有游标。内存就又少了。

ok 。不废话了。具体情况,具体分析。因为你遇到的可能比你描述的复杂的多。
fkname
2021-01-12 14:35:34 +08:00
1.建议客户端做随机操作,这样可以大大减少服务端的计算
2.如果用新表存储已读记录,可以做逻辑删除,这样不会有重建索引的问题。当然插入时需要修改为 insertOrUpdate 方式
CandyMuj
2021-01-12 15:09:09 +08:00
@fkname #48 感谢,是个很好的办法!
CandyMuj
2021-01-12 15:12:02 +08:00
@auh 谢谢
CandyMuj
2021-01-12 15:43:41 +08:00
@auh #47 万分感激,我还一直在分页这里面琢磨。。。 实际上这里就是分批次拿数据,都不应该叫做分页了!

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

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

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

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

© 2021 V2EX