如果查询结果中包含的数据量非常大,则经常需要分页查询。 本文总结了寻呼查询的技术以及如何使用mysql cassandra和redis等实现寻呼查询。
问题
我在做论坛的时候,遇到了以下问题。 论坛有许多主题主题主题,每个主题都支持许多回复复制。 要确定某个主题下按replyTime升序排列的页面的reply,请在每页上pageSize一个reply。 reply存储在mysql中。 以前的实现使用的是mysql的limit查询
选择*来自reply where topicid=? orderbyreplytimeasclimit (pageno-1 ) * pageSize,pageSize
由于目前许多主题的回答很多,当有人查询数百至数千页时,mysql的性能非常差。 如果offset太大,选择极限偏移,大小会降低传统关系数据库的性能。 如果可以使用索引查询条件过滤部分数据,则可以大大提高以下性能:
选择
*
来自
重新播放
where
topicId=?
andreplyidlastreplyidofcurrentpage
订单依据
复制时间Asc
极限运动
(pageno-currentpageno(*pageSize,pagesize
lastReplyIdOfCurrentPage是当前页上最后一次复制的id。 目前页面是目前页面的页码。 这里通过使用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.2336036,如下所示这是否相当于SQL:select * from topic _ replywherereplyid? 不能为limit size、' limit offset、size '。 也就是说,在查看第一千页时,如果不知道从第一千页开始的复制id是多少,就必须取出这一千页的数据。 这显然行不通。 所以,必须考虑如何从与我想取的数据接近的某个复制id中提取数据。
reids的SortedSet
无论是mysql还是cassandra,都无法很好地解决从长序列中检索任意数据的问题。 导致此问题的原因是这些数据存储在磁盘上,磁盘不适合这种随机读取操作。 所以,我希望我能管理放在内存中很大程度上对数组进行排序的程序。 因为用下标访问内存中的数组非常快。 经过调查,发现redis提供了这样的功能。 redis将数据存储在内存中,因此即使是随机读写,速度也非常快。 redis支持的SortedSet结构适用于分页查询。 SortedSet根据给定的score对成员进行排序,以便可以使用下标或score执行查询。 如果将同一主题的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取第2页,即下标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,但必须注意内存大小和持久性问题。