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

对 redis 的 zset 做分片的时候怎么选择分片个数

  •  
  •   ben548 · 294 天前 · 1632 次点击
    这是一个创建于 294 天前的主题,其中的信息可能已经有所发展或是发生改变。
    背景:活动场景,用户可以订阅活动,订阅完会在活动开始前收到开始的通知,目前了解到如果是热门活动订阅人数可能超过百万,但是有些不怎么热门的活动可能只有数百人或者人数是 0 。
    现状:关注人数存在 redis 的 zset 中,每个活动有一个独立的 zset 。发送 push 消息通知的时候,是分页遍历 zset ,逐个发送通知。可能在关注数超百万的时候需要较长的处理时间(比如超过 5 分钟)。
    要求:希望在推送消息时尽可能的快,低延时。
    为了达到这个要求,选择对 zset 做分片,分片的规则是:按订阅用户的 userid 做 hash 取值,取值结果按分片数取模,在分配到相应的 zset 分片上。
    问题:zset 数据除了承担 push 消息通知的数据源之外,还用于查询用户是否订阅该活动的状态查询(这个没啥问题,找到对应的分片,然后查询 zscore 即可)和订阅总数计算的角色。如果我为了某些活动可能超过百万的关注订阅数,而设置 1024 个 zset 分片,是不是意味着绝大多数的活动,为了计算总数都要去做 1024 次 zcount 再将 result 相加才能得到结果?感觉也不是很好的设计。。。。(当然也可以总数单独维护,不通过 zset 分片结果相加获取,就是这样又增加了存储成本)
    大家觉得该怎么做比较合适呢?
    11 条回复    2023-07-08 18:42:55 +08:00
    icql
        1
    icql  
       294 天前
    看场景为啥不考虑直接用 bitmap 呢,zset 这场景看着不大合适
    zpfhbyx
        2
    zpfhbyx  
       294 天前
    在加个 bitmap 就好了. 反正都是个粗略的数字或者单独维护一个 key 来进行统计, 用 lua 脚本 zadd 或者 zdel 的时候 对这个 key 进行加或者减 .
    skyemin
        3
    skyemin  
       294 天前
    一个 zset 里存储百万用户有可能变成 bigkey,分片存储又增加了聚合查询的成本,建议一个 key 一个 bitmap,更省空间,同时也满足用户是否订阅该活动的状态查询
    ben548
        4
    ben548  
    OP
       294 天前
    @icql bitmap 只能查状态和总数吧,不能遍历关注用户。。。不能拿到关注的用户数据的话,push 怎么发呢?
    ben548
        5
    ben548  
    OP
       294 天前
    @icql 而且 bitmap 如果存用户的 uid 的话,uid 是雪花算法生成的的 32 位的数字,用 bitmap 其实非常占内存,本质上用 bitmap 基本上就是为了省内存,不过感觉这种场景下也完全没有节省到内存,甚至比用 zset 占用更多
    fkdtz
        6
    fkdtz  
       294 天前
    这是一个典型的大 key 热 key 场景,不管是否存在推送慢的情况,可能分片都是应该做的。
    楼主的思路我感觉没啥问题,成本也是最低的。
    至于考虑到订阅计数器的单独维护问题,我觉得这点成本是应该的,毕竟你分片之后对于热门活动不可能在线上实时 zcount 1024 个分片吧,那也太慢了。
    尽量保持线上业务快速响应,很多活可以异步处理,比如这个计数器就可以异步执行,不需要实时增加,不影响线上业务响应。

    实际上我理解最好是特殊情况特殊处理,不需要一视同仁给所有活动都分片,可以动态地给大 key 做分片,不过一旦涉及到动态就会引入更多复杂的逻辑例如 rehash 或是引入哈希环等等,展开来就有更多东西可以讨论了,这个场景下有点舍本逐末就算了吧。

    另外我觉得这个场景用 bitmap 不太合适,用户量级在百万级,那么单个 bitmap 的大小至少要上 M ,也算是大 key 了,而且很多绝大部分的活动都是冷门的,意味着 bitmap 中绝大部分都是非常稀疏的,这么搞不太划算。
    我认知里 bitmap 更适合全体用户都参与的场景,例如是否活跃、是否在线等等吧。

    一些临时的想法可能有不太合适的地方,可以一起讨论
    ben123321
        7
    ben123321  
       294 天前
    随便解答下,看看是否能满足条件:
    1. 增加一个大活动的 list ,用于存储哪些活动是大活动,只有进入了该 list ,才会走分片逻辑。这样其实已经保证了只有「非常小」一部分的活动需要做分片。
    2. 针对大活动的 list ,维护一个 count key 。
    3. 把大活动 list 推到 local cache 中,让判断损耗下降为 0 。
    4. 如何从小活动转换成大活动?异步任务写分片数据,然后再加入到大 list 中。
    5. 大活动如何变成小活动?不支持,因为这个概率实在太小。

    以上。
    dusu
        8
    dusu  
       294 天前 via iPhone
    1. bitmap 也可以分片的
    每片 id 从 1 开始 分成若干个 bitmap
    拿出来的时候算下偏移就能拿到最终 id
    分片越多越省内存 但是 key 不好维护
    但是至少能解决内存太大问题
    2. 冷数据过期后从内存里剔除
    直接数据库里查归档结果就行了
    当数据穿透超过阈值就回 redis
    dusu
        9
    dusu  
       294 天前 via iPhone
    @dusu redis 的 bitmap 可以 dump 出来自己解析的
    那个执行性能应该可以满足你要快速 scan 的效果
    xuanbg
        10
    xuanbg  
       294 天前
    没明白活动为什么要用 zset ?
    icql
        11
    icql  
       294 天前
    @ben548 #5 整个拿到能遍历,uid 这么大自带的确实占内存不过可以试试 roaringBitmap
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2972 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 13:22 · PVG 21:22 · LAX 06:22 · JFK 09:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.