The World

scribble

Ralph YY's Blog

12 May 2021
Pagination With Cache

How to do the cache for Pagination senario.

数据量较多的情况,通常是要对数据进行缓存,如果返回很多,还会需要对返回的数据进行分页,但是缓存和分页如何结合呢,这里提供了一些方案。

一些通用方案

1.普通分页缓存
就是把所有Query结果进行cache,让cache服务器自己选择数据。一般分页做缓存都是直接查找出来,按页放到缓存里,但是这种缓存方式有很多缺点。如缓存不能及时更新,一旦数据有变化,所有的之前的分页缓存都失效了。比如像微博这样的场景,微博下面现在有一个顶次数的排序。这个用传统的分页方式很难应对。

2.解耦数据库,控制搜索粒度
提到不缓存整页的数据,而是分条存取。每次只从数据库获取分页数据对应的id序列。每次我们只从数据库获取分页数据对应的Id序列,然后根据再根据这些Id从service中获取(缓存策略在service中实现)。

具体做法:
1)通过数组函数array_column获取数组某一列的值,返回一个一维数组。
2)其实当时查代码的时候只是要id,可以加个filed(‘id’),然后存就存一个二维数组,子数组只有一个字段id,拿的时候通过array_slice获取一段要的数据。
就是可以查询的时候从数据库查出这一页的数据id,然后根据id到缓存里分别取这些id对应的数据,没有命中缓存的最后一次性从数据库查出来。

3.增删改时,更新缓存的做法
存在的问题: 1)新增和修改没有考虑ids缓存不存在的情况!! 2)且存在隐患:当用户新增数据并发量大,插入数据库先的人存入缓存慢!! 导致列表排序有问题!!如:当前数据1,2,3;a先插入4,但是还没来得及保存到缓存的那一个节点! b插入了5,且插入了缓存,导致原本应该1,2,3,4,5的缓存列表变成1,2,3,5,4 一劳永逸的解决方案:增删改涉及到的缓存直接删除!!因为获取的时候找不到缓存时会到数据库查并存入缓存!!

4.如果是时间段,可以使用bucket的方式
比如按照月份存放,要查1/20-2/20的数据,就获取全部1月份和2月份的bucket,返回的时候aggregate结果。

扩展问题:数据量太大,通过时间分多个redis存储,每个月存一个reids列表,怎么拿完这个月的值再拿上一个月的,并且要数据衔接好?解决思路:获取值时先获取当前月份的数据,若数据不满足当前页码要显示的个数,则获取下一个缓存补充,一个缓存获取完,获取下一个缓存。前端传多一个缓存块id。

5.Redis里面也有分页数据zset
数据以ID为key缓存到Redis里,把数据ID和排序打分存到Redis的skip list,即zset里。

当查找数据时,先从Redis里的skip list取出对应的分页数据,得到ID列表。 用multi get从redis上一次性把ID列表里的所有数据都取出来。如果有缺少某些ID的数据,再从数据库里查找,再一块返回给用户,并把查出来的数据按ID缓存到Redis里。

6.前端优化技巧
比如在缺少一些ID数据的情况下,先直接返回给用户,然后前端再用ajax请求缺少的ID的数据,再动态刷新。

具体的场景案例

有多种分页的情况:

1.See More的场景
比如FB的timeline效果,在滚动的时候显示更新。
通常api需要传递maximum id和page size,这种设计在系统设计也有涉及(比如twitter timeline的设计)。分为fanout和client主动pull的情况,一种做法是维护每个user的timeline,一发推就更新每个用户的follower或者followee的timeline table。这种并不显示页码,不是严格的分页场景。

2.单表一段的数据的场景
比如获取activity表在10:00-11:00的数据。如果是单表的数据,可以直接使用cassandra这种数据库很适合范围搜索,如果是relation的数据库也可以在time的字段加上索引来快速检索。单表的话速度通常也不慢,未必需要缓存。
如果数据不常变化,可以对前面几页进行缓存(通过定期后台script),如果经常变化,缓存意义不大。缓存的初衷是对请求频繁又不易变的数据,实际使用中很少会反复的请求同一页的数据(查询条件也相同)。

3.复杂联合Query场景
如果是关系型数据库,直接的方案就是提高数据库性能,比如buffer大小之类来提高性能。或者是引入solr和elasticsearch来处理。速度快,可以filter(where),sort,aggregation(group by,count,sum,max等),也能分页。然后根据搜索出来的id直接取缓存数据。

如果是NoSQL或者是已经解耦的表结构设计,就需要多级查询了,比如通过followers找到所有userId,再通过所有userId找到所有对应的messageId,中间调用filter和cache来实现。

Reference

1.Redis 多条件组合的查询列表页,大家怎么做缓存
2.利用redis缓存热门数据,分页的一种思路
3.初学redis分页缓存方法实现
4.缓存分页如何设计?
5.基于redis做缓存分页
6.对于分页数据该如何缓存?


Til next time,
at 00:00

scribble

comments powered by Disqus