场景说明
客户端有一个社区列表的展示界面,该列表的排序方式是按照关注人数进行反向排序(从大到小),类似:
社区id | 社区名称 | 社区关注人数 |
---|---|---|
1 | AAA | 4 |
3 | CCC | 3 |
2 | BBB | 2 |
4 | DDD | 1 |
但是社区的 关注人数是动态变化的 ,当社区数量较多时也要考虑分页的情况,那么该列表的排序和分页有了很大的难度。
这里简单整理当前的实现思路,并指出明显的缺陷和暂时解决不了的问题,待以后有能力解决。
实现思路
这里首先要解决的第一个问题就是将动态转换成静态,然后才能排序。
如果做到根据实时的关注人数进行排序,对于后端是容易实现的,但是要考虑的客户端的处理方式:客户端会缓存已经获取的列表信息,同一个用户分页获取数据的时候,由于关注人数的动态变化,会导致数据出现重复或缺漏的问题。
举个例子说明
某时刻社区的关注人数如下(这里是举个例子说明,所以数据仅列出了4条):
社区id | 社区名称 | 社区关注人数 |
---|---|---|
1 | AAA | 3 |
3 | CCC | 4 |
2 | BBB | 3 |
4 | DDD | 1 |
当前时刻为t1,那么此时的社区排序结果如下:
社区id | 社区名称 | 社区关注人数 | 生成时刻 |
---|---|---|---|
3 | CCC | 4 | t1 |
1 | AAA | 3 | t1 |
2 | BBB | 3 | t1 |
4 | DDD | 1 | t1 |
一段时间之后,时刻为t2,那么此时社区的关注人数发生了变化:
社区id | 社区名称 | 社区关注人数 |
---|---|---|
1 | AAA | 5 |
3 | CCC | 7 |
2 | BBB | 9 |
4 | DDD | 4 |
那么此时的排序结果相对与t1时刻已经发生了明显的变化:
社区id | 社区名称 | 社区关注人数 | 生成时刻 |
---|---|---|---|
2 | BBB | 9 | t2 |
3 | CCC | 7 | t2 |
1 | AAA | 5 | t2 |
4 | DDD | 4 | t2 |
数据重复或漏缺的复现
这里出现的数据重复漏缺是有前提的:数据条数有点多,不能够一次全部返回给客户端,需要分页获取。
根据上面的那个例子,在t1时刻,用户当前的分页参数 count=2
,那么返回的是id为 3
和 1
的社区信息,当用户在t2时刻继续获取分页的时候,返回的却是id为 1
和4
那么这里id为1的社区就出现重复的问题了,缺漏的原理也类似。
缓存生成的关注排行榜
这里的实现思路是每隔一段时间将当前的关注排行榜进行缓存:
排行榜id | 社区id | 社区名称 | 社区关注人数 | 生成时刻 |
---|---|---|---|---|
1 | 3 | CCC | 4 | t1 |
2 | 1 | AAA | 3 | t1 |
3 | 2 | BBB | 3 | t1 |
4 | 4 | DDD | 1 | t1 |
5 | 2 | BBB | 9 | t2 |
6 | 3 | CCC | 7 | t2 |
7 | 1 | AAA | 5 | t2 |
8 | 4 | DDD | 4 | t2 |
这个时间间隔就是每次任务的时间间隔T,在T间隔内获取的数据都将通过该缓存排行榜中获取。用户在第一次访问的时候,仅携带分页的count
即可,返回的数据中,不仅包含社区的相关信息,还包括最新的时刻tn
,在以后的分页获取数据过程中,需要携带该时刻。
比如用户在t1
时刻传入参数count=2
,那么返回的是id为3
和1
的社区信息,同时返回参数中也包含t1
的值,那么在第二次访问的时候,需要携带count=2&time=t1
,这样列表接口将会根据用户的时刻继续获取他的排行队列,那么返回的就应该是id为2
和4
的社区信息,同样包括t1
的值。
其实也就是用户访问的时候,如果没有携带时刻参数,那么就默认返回距离当前时间最近的时刻。
缓存的过期和时刻参数t的失效设定
讨论一下极端情况:
- 当时刻为t9的时候,时刻参数是t1的用户获取的将是很久以前的数据,可能这个数据对当前用户来说已经没有意义了。
- 最理想的情景就是,用户发出请求的时候,刚好有一批新的社区排行缓存生成,这是拿到的数据也是最合理的数据。
- 还有一种比较不合理的情景,就是用户发出请求的时候,刚好有一批新的社区排行榜将要生成,那么用户拿到的数据也就是间隔时间T之前的缓存。
所以这里就需要进行缓存过期和时刻参数失效的设定。
- 设定时刻参数比当前时间小N×T的为无效时刻,将自动返回最新的缓存信息。
- 缓存排行榜列表也会在任务执行的时候清除 N×T 前的缓存数据。
后端的实现
数据库中缓存表的构建
- 该缓存表专门缓存各个时刻的排行信息,有定时任务进行插入和删除数据,任何用户均不能操作修改该表中数据。
- 该表包含的基本字段有:
自身的id
、社区的id
、关注的人数
、生成的时刻(时间戳)
。
定时任务的调度
- 社区排行榜生成任务,该任务是每T时间执行一次,完成对当前社区排行榜的计算、排序和入缓存库,任务执行时生成当前任务的唯一时刻值并一同存入缓存数据库中。
- 缓存数据库清理任务,该任务的执行间隔可以是N×T,完成对N×T时刻前的排行榜数据进行清除。
列表接口的实现
- 判断用户传入时刻值的有效性。如果用户没有传入时刻值或时刻值比当前时间小N×T则分配最新的时刻值。
- 向缓存表中获取指定分页的社区id,并返回响应的社区信息列表和对应的时刻值。
客户端相关的任务
- 用户首次进入列表界面时,请求不需要携带时刻值。
- 用户下拉刷新的时候,请求不需要携带时刻值。
- 用户上拉浏览列表的时候,请求需要携带时刻值。
- 用户上拉浏览列表的时候,如果时刻值失效,需要清楚本地的部分缓存。
存在的问题
- 用户浏览的数据不是最新的数据,在上面的极端情况中已经讨论
- 客户端处理较为复杂
- 用户在界面滞留时间越久,客户端的到的数据越不理想
- 用户在当前列表的上的操作,比如关注某个社区,客户端需要将原本的关注人数+1,但是刷新界面之后,关注的人数又会变成原来的数字。
- 由于是缓存的数据,当用户点击某个社区的详情时,看到的关注人数可能与列表中的人数不一致。