最近我们的电商系统中在大促时出现了一个分页 Bug,问题产生的原因就是 Redis 的 ZREVRANGEBYSCORE 指令使用不当导致的。表现在 java 代码中就是 zrangeByScoreWithScores 方法使用不当。本文来说说这个 Sorted Set 有序集合实现分页产生的 Bug 问题。
首先,我们线上的电商系统正在进行一个大促活动。买家非常的多,导致一时间产生了非常多的评论。开发人员在存储(保存)评论时,使用 Redis 中的 SortSet 这个数据结构。每新增一条评论,他都通过 zadd 命令将起保存至 Redis 和 MySQL 中,查询时直接在 Redis 中取出来。代码抽象一下,如下所示:
boolean operated; try (Jedis jedis = pool.getResource()) { operated = jedis.zadd(COMMUNITY_PRAISE + commentId, -System.currentTimeMillis() / 1000, accountId) == 1; } if (operated) { commentDao.incrPraiseCount(comment); }
顺便说一下,System.currentTimeMillis() 是毫秒,System.currentTimeMillis() / 1000 就相当于精确到秒。
这个活动是大促,参与活动的需要评论。活动比预期的热,导致同一秒的评论就比较多,导致 zrangeByScoreWithScores 在取出评论列表时,遇到同一秒的超过 10 条的评论,就会出现重复数据。
取出评论列表的逻辑大致抽象如下:
Set<Tuple> praisedIds = jedis.zrangeByScoreWithScores(COMMUNITY_PRAISE + id, lastScore, 0, 0, 10); List<Comment> result = new ArrayList<>(); for (Tuple praisedId : praisedIds) { // 其他逻辑 result.add(comment); } return result;
看到没?他每次拿 lastScore 记录,也就是上一页评论的最后一条数据的时间。来获取新的 10 条评论数据。由于同一秒评论的记录超过 10 条后,导致每次 APP 每次下拉刷新获取到重复数据的概率比较高。于是就有客户投诉,看评论中的数据都是一样的。无论怎么刷新都是 10 条一样的数据,和上一页的数据一样。
这个 zrangeByScoreWithScores 其实就是 Redis 中的 ZREVRANGEBYSCORE 指令。完整指令如下:
ZREVRANGEBYSCORE key max min WITHSCORES LIMIT offset count
其中这个 limit 是可以分页的。这个分页对下拉刷新也有一个问题,那就是在你下拉刷新时,刚好新增了几个评论,所以刷新出来的数据也可能有重复的数据。
后来这个程序员针对这个问题有改了一下,每次拿到 lastScore 后加 1。我看到这个代码后,又找他了,这个做法肯定不行的。也就是说你每次拿到上一页最后一条的 lastScore 后,再加 1。就相当与时间上加了 1 秒,中间肯定会漏掉一些数据的。
那么该怎么办呢?如果你想使用 lastScore 进行分页,就必须保证它唯一。不然就有概率发生刷新列表时,出现重复数据。
我们这个和时事新闻还有些不一样,新闻的写入时间重复概率没有我们这个高。尤其是我们在大促的活动当中。
最后说一下这个问题的解决办法。一种就是保证 lastScore 唯一,或者说时间在进一步精确。还有一种就是 lastScore + limit 的组合来实现分页。或者就是单纯的使用 limit 来实现,但是也要注意不能刷新出重复数据。
我们的做法,最终是参考了上图。
将每个主题的 topicId 作为 set 的 key,将与该主题关联的评论的 createDate 和 commentId 分别作为 set 的 score 和 member,commentId 的顺序就根据 createDate 的大小进行排列。
当需要查询某个主题某一页的评论时,就可主题的 topicId 通过指令 zrevrange topicId (page-1)×10 (page-1)×10+perPage 这样就能找出某个主题下某一页的按时间排好顺序的所有评论的 commintId。page 为查询第几页的页码,perPage 为每页显示的条数。
当找到所有评论的 commentId 后,就可以把这些 commentId 作为 key 去 Hash 结构中去查询该条评论对应的内容。
这样就利用 SortSet 和 Hash 两种结构在 Redis 中达到了分页和排序的目的。
: » Redis 中ZREVRANGEBYSCORE(zrangeByScoreWithScores) 使用不当导致的分页 Bug!
原创文章,作者:254126420,如若转载,请注明出处:https://blog.ytso.com/251975.html