问题

我在做论坛的是时候遇到了如下的问题。论坛里可以有很多的主题topic,每个topic对应到很多回复reply。现在要查询某个topic下按照replyTime升序排列的第pageNo页的reply,每页pageSize个reply。

reply是存放在mysql中的。以前的实现是利用mysql的limit查询

1
2
3
4
select * from reply
where topicId = ?
order by replyTime asc
limit (pageNo - 1) * pageSize, pageSize

由于现在有很多的主题的回复很多,当有人查询第几百甚至几千页的时候,mysql性能表现很不好。“select limit offset, size” 只要offset太大,传统的关系型数据库的性能表现都不好。

如果能够利用带索引的查询条件先过滤掉一部分数据,就可以大大提高性能,比如:

1
2
3
4
5
select *
from reply
where topicId = ? and replyId > lastReplyIdOfCurrentPage
order by replyTime asc
limit (pageNo - currentPageNo) * pageSize,  pageSize

lastReplyIdOfCurrentPage 是当前页的最后一个reply的id。currentPageNo是当前页的页号。这里用replyId过滤条件,把前面页的内容过滤掉,这样减少了 offset的大小。但是当用户需要跳转到很远的一个页面的时候,offset还是会很大。比如,当前是第10页,要跳转到第1000页,offset = 990 * pageSize,还是会很大,性能依旧不行。尽管目前很多产品,都不提供这样的跳转能力了,但是我们的产品团队还是认为这个功能在我们的产品里面不可或 缺。

迁移到cassandra

后来我们把reply数据全部迁移到了cassandra上。cassandra的数据结构和mysql不一样。我们创建了一个topic_reply 列簇,每一行的行号是topicId,每一列是这个topic的replyId,这样得到类似如下结构

1:1,2,5,33,245,663,780...
2:36,78,89,94,235,345...

在cassandra中列是自然排序的,形成了一个从topic到reply的索引。查询的时候只能查询topicId行的列大于(或小于)replyId的size个replyId,相当于sql:

1
select * from topic_reply where replyId > ? limit size

, 不能够 “limit offset, size”。这意味着如果要查询第一千页,而我不知道第一千页开始的replyId是多少,我就得取出这一千页的数据,这显然是行不通的。所以得想办法从靠近我要取的数据的某个replyId处开始取数据。

reids的SortedSet


论是mysql还是cassandra,都不能很好地解决从一个很长的序列中取出任意一段数据的问题,而造成这一问题的根源在于这些数据是存放在磁盘上
的,磁盘不适合做此类的随机读的操作。所以想,如果能有一个程序,管理一些很大很大的放在内存中排序数组就好了,因为对内存中的数组做下标访问,是非常快
速的。做了一下调查正好发现,redis提供了此类的功能。

redis将数据存放到内存中,所以既便是随机读写,速度都是非常快。
redis支持的SortedSet结构正好适合于做分页查询。SortedSet按照给定的score给member排序,允许通过下标或者score
去查询。把同一个topic的replyId作为member,以replyId本身为score存放到SortedSet后,就可以通过下标取值了,例
如:

//存入数据
zadd tr:1 1 1
zadd tr:1 2 2
zadd tr:1 5 5
zadd tr:1 33 33
zadd tr:1 245 245
zadd tr:1 663 663

//pageSize = 3 取 第二页,即下标 3 到5的元素
zrange tr:1 3 5

其中 tr:1 是这个SortedSet的key,”tr:”只是用来区分其它key用的前缀,1是topicId。更详细的内容看redis官网http://redis.io

如此一来,就可以实现任意分页查询了,而且性能非常好。

缓存索引

redis
的数据全部存放到内存中,如果把所有topic到reply的关系都放到内存中,要耗费很多内存,而且这么多的内存实际上很多是浪费的,毕竟大部分的
topic是不活跃的。再者topic到reply的映射关系是非常重要的,所以我们需要把这种关系持久化。最后我们决定,这个映射关系,或者称为索引还
是存放在cassandra里面,只是在需要的时候,才从cassandra里面把索引载入到redis内,然后再利用redis分页查询。如此一
来,redis成了一个支持分页查询的强大的缓存。

分片缓存

对于超长的主题,全新载入到redis一次也是相当的耗时的,我们采取分片来解决这个问题。我们把索引每4800个值分成一片,用另外一个数据结构记录索引长度和索引从第二片开始的每片的开始值。

更新的索引的时候更新这个分片信息,记录各分片的头部是为了便于从cassandra载入分片。


询的时候把分页查询转化成某个片上某段索引的值。当分片大小大于pageSize并且能被pageSize整除时,这个转化是很简单的,因为分页正好会全
部落在某一个分片中。我们之所以把分片大小设置成4800正是因为这个值能被10 15 20 25 30 40 50 60 80 100 200
等很多常用分页大小整除。分片太大浪费内存,分片太小分片就太多。

只要算出这一页所在的分片,然后把需要的索引段载入到redis,再利用redis的分页查询查出结果。这样,只有活跃的索引分段才会被载入到redis内存中。

如果用mysql来持久化索引效果也是类似的,而且查询更加便利能力更强。

总结

只要产品能接受,就不要使用任意分页,任意跳转。确实需要高速分页查询的时候可以使用redis的SortedSet,但是得注意内存大小和持久化问题。

分页查询和redis的更多相关文章

  1. 【Redis】redis分页查询理解

    偶然在代码中发现一个接口,接口定义说是分页查询,但逻辑实现是Redis.不太理解,Redis怎么分页?后来看到一篇文章,然后了解了. 1.Zrevrange实现 通过SortedSet的zrevran ...

  2. MongoDB 分页查询的方法及性能

    最近有点忙,本来有好多东西可以总结,Redis系列其实还应该有四.五.六...不过<Redis in Action>还没读完,等读完再来总结,不然太水,对不起读者. 自从上次Redis之后 ...

  3. C#MongoDB 分页查询的方法及性能

    传统的SQL分页 传统的sql分页,所有的方案几乎是绕不开row_number的,对于需要各种排序,复杂查询的场景,row_number就是杀手锏.另外,针对现在的web很流行的poll/push加载 ...

  4. Mysql分页查询性能分析

    [PS:原文手打,转载说明出处,博客园] 前言 看过一堆的百度,最终还是自己做了一次实验,本文基于Mysql5.7.17版本,Mysql引擎为InnoDB,编码为utf8,排序规则为utf8_gene ...

  5. mysql分库 分页查询

    Mysql海量数据分表分库如何列表分页? 1.现在使用ElasticSearch了.基于Lucene的解决方案 2.必须将mysql里的数据写入到类似hbase这样的分布式数据库,查询快.但分页.查询 ...

  6. 数据库分库分表和带来的唯一ID、分页查询问题的解决

    需求缘起(用一个公司的发展作为背景) 1.还是个小公司的时候,注册用户就20w,每天活跃用户1w,每天最大单表数据量就1000,然后高峰期每秒并发请求最多就10,此时一个16核32G的服务器,每秒请求 ...

  7. 在MySQL中如何使用覆盖索引优化limit分页查询

    背景 今年3月份时候,线上发生一次大事故.公司主要后端服务器发生宕机,所有接口超时.宕机半小时后,又自动恢复正常.但是过了2小时,又再次发生宕机. 通过接口日志,发现MySQL数据库无法响应服务器.在 ...

  8. 服务器文档下载zip格式 SQL Server SQL分页查询 C#过滤html标签 EF 延时加载与死锁 在JS方法中返回多个值的三种方法(转载) IEnumerable,ICollection,IList接口问题 不吹不擂,你想要的Python面试都在这里了【315+道题】 基于mvc三层架构和ajax技术实现最简单的文件上传 事件管理

    服务器文档下载zip格式   刚好这次项目中遇到了这个东西,就来弄一下,挺简单的,但是前台调用的时候弄错了,浪费了大半天的时间,本人也是菜鸟一枚.开始吧.(MVC的) @using Rattan.Co ...

  9. 阿里云对象存储服务,OSS使用经验总结,图片存储,分页查询

    阿里云OSS-使用经验总结,存储,账号-权限,分页,缩略图,账号切换 最近项目中,需要使用云存储,最后选择了阿里云-对象存储服务OSS.总的来说,比较简单,但是仍然遇到了几个问题,需要总结下. 1.O ...

随机推荐

  1. jinja2 中的 Template 批量替换json字符串中的内容

    项目中用到elasticsearch,使用Json格式查询方式,一个查询语句中有好几个地方需要替换,且替换的值都相同.最开始把json转为字符串发方式,利用format函数处理,发现再转回json时无 ...

  2. [DeeplearningAI笔记]序列模型3.6Bleu得分/机器翻译得分指标

    5.3序列模型与注意力机制 觉得有用的话,欢迎一起讨论相互学习~Follow Me 3.6Bleu得分 在机器翻译中往往对应有多种翻译,而且同样好,此时怎样评估一个机器翻译系统是一个难题. 常见的解决 ...

  3. [DeeplearningAI笔记]序列模型2.9情感分类

    5.2自然语言处理 觉得有用的话,欢迎一起讨论相互学习~Follow Me 2.9 Sentiment classification 情感分类 情感分类任务简单来说是看一段文本,然后分辨这个人是否喜欢 ...

  4. 介绍 JSON (转)

    本文转自:http://www.json.org/json-zh.html JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. 易于人阅读和编写.同时也易于 ...

  5. HDU 5145 分块 莫队

    给定n个数,q个询问[l,r]区间,每次询问该区间的全排列多少种. 数值都是30000规模 首先考虑计算全排列,由于有同种元素存在,相当于每次在len=r-l+1长度的空格随意放入某种元素即$\bin ...

  6. 分享一个数据库sql_mode 引起的坑

    sql_mode坑 MySQL 5.7.x 默认值: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR ...

  7. 【51nod】1222 最小公倍数计数 莫比乌斯反演+组合计数

    [题意]给定a和b,求满足a<=lcm(x,y)<=b && x<y的数对(x,y)个数.a,b<=10^11. [算法]莫比乌斯反演+组合计数 [题解]★具体 ...

  8. 【洛谷 P3168】 [CQOI2015]任务查询系统(主席树)

    题目链接 被自己的sb错误调到自闭.. 主席树的进阶应用. 把\(P_i\)离散化一下,得到每个\(P_i\)的排名,然后建一棵维护\(m\)个位置的主席树,每个结点记录区间总和和正在进行的任务数. ...

  9. VS2015_动态链接库学习

    非MFC动态链接库 创建一个名为ex1的Win32项目 创建一个DLL项目,保留预编译的头文件   默认文件 创建完成项目之后,包含几个默认的文件   stdafx.h文件用于包含标准系统包含的头文件 ...

  10. Redis安装和客户端cli常见操作

    安装Redis $ wget http://download.redis.io/releases/redis-4.0.6.tar.gz $ tar xzf redis-4.0.6.tar.gz $ c ...