在 Redis 中进行分页排序查询

2017-01-07 00:09:27 +08:00
 liuxin5959

Redis 是一个高效的内存数据库,它支持包括 String 、 List 、 Set 、 SortedSet 和 Hash 等数据类型的存储,在 Redis 中通常根据数据的 key 查询其 value 值, Redis 没有条件查询,在面对一些需要分页或排序的场景时(如评论,时间线), Redis 就不太好不处理了。

前段时间在项目中需要将每个主题下的用户的评论组装好写入 Redis 中,每个主题会有一个 topicId ,每一条评论会和 topicId 关联起来,得到大致的数据模型如下:

{
    topicId: 'xxxxxxxx',
    comments: [
        {
            username: 'niuniu',
            createDate: 1447747334791,
            content: '在 Redis 中分页',
            commentId: 'xxxxxxx',
            reply: [
                {
                    content: 'yyyyyy'
                    username: 'niuniu'
                },
                ...
            ]
        },
        ...
    ]
}

将评论数据从 MySQL 查询出来组装好存到 Redis 后,以后每次就可以从 Redis 获取组装好的评论数据,从上面的数据模型可以看出数据都是 key-value 型数据,无疑要采用 hash 进行存储,但是每次拿取评论数据时需要分页而且还要按 createDate 字段进行排序, hash 肯定是不能做到分页和排序的。

那么,就挨个看一下 Redis 所支持的数据类型:

**String: **主要用于存储字符串,显然不支持分页和排序。 **Hash: **主要用于存储 key-value 型数据,评论模型中全是 key-value 型数据,所以在这里 Hash 无疑会用到。 **List: **主要用于存储一个列表,列表中的每一个元素按元素的插入时的顺序进行保存,如果我们将评论模型按 createDate 排好序后再插入 List 中,似乎就能做到排序了,而且再利用 List 中的 LRANGE key start stop 指令还能做到分页。嗯,到这里 List 似乎满足了我们分页和排序的要求,但是评论还会被删除,就需要更新 Redis 中的数据,如果每次删除评论后都将 Redis 中的数据全部重新写入一次,显然不够优雅,效率也会大打折扣,如果能删除指定的数据无疑会更好,而 List 中涉及到删除数据的就只有 LPOP 和 RPOP 这两条指令,但 LPOP 和 RPOP 只能删除列表头和列表尾的数据,不能删除指定位置的数据,所以 List 也不太适合。 **Set: **主要存储无序集合,无序!排除。 **SortedSet: 主要存储有序集合, SortedSet 的添加元素指令ZADD key score member [[score,member]...]会给每个添加的元素 member 绑定一个用于排序的值 score , SortedSet 就会根据 score 值的大小对元素进行排序,在这里就可以将 createDate 当作 score 用于排序, SortedSet 中的指令ZREVRANGE key start stop又可以返回指定区间内的成员,可以用来做分页, SortedSet 的指令 ZREM key member 可以根据 key 移除指定的成员,能满足删评论的要求,所以, SortedSet 在这里是最适合的。

所以,我需要用到的数据类型有 SortSet 和 Hash , SortSet 用于做分页排序, Hash 用于存储具体的键值对数据,我画出了如下的结构图:

在上图的 SortSet 结构中将每个主题的 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 中达到了分页和排序的目的。

6586 次点击
所在节点    Redis
13 条回复
gouchaoer
2017-01-07 00:23:13 +08:00
这个。。。 comment 另外存 hash 是不对的,这意味着你取一页就要有一个 zset 操作+几十个 hash get 操作,应该直接把 comment 放 zset 里
ihuotui
2017-01-07 00:56:56 +08:00
@gouchaoer 因为要考虑更新,删除操作。而且内容和排序分开是一个比较实践。
Jaylee
2017-01-07 01:06:07 +08:00
假如有一个用户改了昵称怎么办?
Ouyangan
2017-01-07 10:12:00 +08:00
@Jaylee 应该要做好缓存更新策略
soli
2017-01-07 10:38:08 +08:00
@gouchaoer 可以用 hmget 一次性取出。这样只需两个操作就行了。

不过 hmget 消耗也挺大的。
gouchaoer
2017-01-07 10:46:20 +08:00
@soli 你居然不知道 hmget/hgetall 的坑,小心哭哦
uuhp2009
2017-01-07 10:56:33 +08:00
什么坑
fuxkcsdn
2017-01-07 11:40:52 +08:00
@gouchaoer
zset 占用的空间大,把整个 commit 放到 member 里得有大内存
soli
2017-01-07 13:23:18 +08:00
@gouchaoer 呀,什么坑?请指教哈。
chenqh
2017-01-07 13:39:16 +08:00
@gouchaoer 我发现我也遇到了 hget 的那个坑……关键是我的 redis 是 3.0.7 呀
owt5008137
2017-01-07 14:37:48 +08:00
为啥非得把 NoSQL 用成 SQL
wwwicbd
2017-01-08 00:31:01 +08:00
@gouchaoer 这样删除就不好做了
wwwicbd
2017-01-08 00:34:38 +08:00
@owt5008137 这就是挺典型的 redis 应用啊。新版 redis 支持插件,还真有个项目在 redis 里实现了部分 SQL 查询

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/332801

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX