V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
liuxin5959
V2EX  ›  Redis

在 Redis 中进行分页排序查询

  •  
  •   liuxin5959 · 2017-01-07 00:09:27 +08:00 · 6728 次点击
    这是一个创建于 2873 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 中达到了分页和排序的目的。

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

    不过 hmget 消耗也挺大的。
    gouchaoer
        6
    gouchaoer  
       2017-01-07 10:46:20 +08:00 via Android   ❤️ 1
    @soli 你居然不知道 hmget/hgetall 的坑,小心哭哦
    uuhp2009
        7
    uuhp2009  
       2017-01-07 10:56:33 +08:00
    什么坑
    fuxkcsdn
        8
    fuxkcsdn  
       2017-01-07 11:40:52 +08:00 via iPhone
    @gouchaoer
    zset 占用的空间大,把整个 commit 放到 member 里得有大内存
    soli
        9
    soli  
       2017-01-07 13:23:18 +08:00
    @gouchaoer 呀,什么坑?请指教哈。
    chenqh
        10
    chenqh  
       2017-01-07 13:39:16 +08:00
    @gouchaoer 我发现我也遇到了 hget 的那个坑……关键是我的 redis 是 3.0.7 呀
    owt5008137
        11
    owt5008137  
       2017-01-07 14:37:48 +08:00 via Android
    为啥非得把 NoSQL 用成 SQL
    wwwicbd
        12
    wwwicbd  
       2017-01-08 00:31:01 +08:00 via iPhone
    @gouchaoer 这样删除就不好做了
    wwwicbd
        13
    wwwicbd  
       2017-01-08 00:34:38 +08:00 via iPhone
    @owt5008137 这就是挺典型的 redis 应用啊。新版 redis 支持插件,还真有个项目在 redis 里实现了部分 SQL 查询
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4850 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 09:49 · PVG 17:49 · LAX 01:49 · JFK 04:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.