在 Redis 中进行分页排序查询 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
liuxin5959
V2EX    Redis

在 Redis 中进行分页排序查询

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

    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 /td>
        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 呀,什么坑?请指教哈。
    simple2025
        10
    simple2025  
       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     Solana     955 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 19:22 PVG 03:22 LAX 11:22 JFK 14:22
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86