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

一个 abtest 实验中 redis 设计的思考

  •  
  •   ben548 · 297 天前 · 1810 次点击
    这是一个创建于 297 天前的主题,其中的信息可能已经有所发展或是发生改变。
    一个关于 abtest 需求的 redis 场景问题,
    需求:需要将用户分成 abcd 四个桶,不同桶内用户看到的信息不同,第一次分桶时需要将分桶结果发送给数据部门。
    实现:将用户 id 经过 murmurhash 计算出来的哈希结果按 4 取模,得到分桶信息,将该结果保持到 redis 中,每个用户一个 redis key ,字符串类型存储,如 abresult_11111(user_id):1,程序开始执行时先读取 redis ,如果存在分桶记录,那么直接返回,不存在则进行分桶操作,并将分桶结果发给数据部门

    疑问:
    目前这个设计是一个好的设计吗?百万级别的用户量的话,是不是会生成百万的 redis key ,印象中 redis_key 过多不是一个好的设计,比如不好管理等。
    我能想到的几个问题:
    1.印象中用 hash 结构来存储,会被这种存储方式节约内存,但是用 hash 来存储必然导致 big key 问题,当然在这种场景下面,不涉及像 getall 那样的 O(n)操作,是不是 big key 问题可以基本忽略不计?印象中 big key 可能导致的问题不止是性能问题,还有像数据倾斜导致的访问倾斜问题等,所以用 hash 来存储的话是不是也要那用户 id 来做分片才是比较合适的方案?
    2.redis 的删除是惰性删除+定时删除,定时删除基于取样,取样的话如果失效的数据过多,印象中会一直不断的循环删除,指定取样的结果不满足为止,想知道这个定时删除是在主进程上完成的吗?如果过多的 key 失效,会阻塞 redis 进程吗?

    大佬们,一起讨论下啊?一个是上面提出的一些问题,还有就是如果是你们来设计的话会怎么设计?
    29 条回复    2023-06-07 10:11:55 +08:00
    xiaofan2
        1
    xiaofan2  
       297 天前
    你们的 id 是 long 类型的吗?如果是 long 类型的话为什么要 hash ?
    ben548
        2
    ben548  
    OP
       297 天前
    @xiaofan2 是 long 类型,目前设计不是用 hash ,考虑用 hash 是想着 100w 的 string key-value 数据内存占用应该是大于一个 100 万 key-value 的 hash 数据的(没做测试,只是靠印象和经验)
    nicreve
        3
    nicreve  
       297 天前   ❤️ 1
    Murmur 也没有这么吃性能吧,为什么不每次都实时计算而是要存在 Redis 里呢?如果单纯为了 ID 分布均匀,还有很多比 Murmur 更快的非标 Hash 算法,反正这个场景也不在乎 Hash 冲突。
    matrix1010
        4
    matrix1010  
       297 天前
    value 是什么, 每个用户都不一样吗?
    octobersnow
        5
    octobersnow  
       297 天前
    每次都 hash ,不用存 redis
    ben548
        6
    ben548  
    OP
       297 天前
    @nicreve 因为需要记录是不是第一次生成,只有第一次分配桶,才触发同步数据部门,我需要记录这样一个是否已经分配过的状态,来判断是否需要同步分桶结果给到数据部门
    ben548
        7
    ben548  
    OP
       297 天前
    @matrix1010 就是 0 ,1 ,2 ,3 这种数字,分到对应桶的用户都是一个 value
    ben548
        8
    ben548  
    OP
       297 天前
    @octobersnow 因为需要记录是不是第一次生成,只有第一次分配桶,才触发同步数据部门,我需要记录这样一个是否已经分配过的状态,来判断是否需要同步分桶结果给到数据部门
    nicreve
        9
    nicreve  
       297 天前
    @ben548 问题是不是搞复杂了,你和数据部门的 Hash 及分桶规则保持一致不就可以了么,不需要进行同步啊。别告诉我你们的数据部门只会写 SQL ,这种基本的工程能力都没有?
    ben548
        10
    ben548  
    OP
       297 天前
    @nicreve 目前是需要同步给他们的,我理解是这样会更好一些吧,因为这样数据部门不与业务绑定,相互隔离会是更好的设计吧,不然我们这边的规则改了,他们也要跟着一起改吗?很多时候没有及时通知容易出问题
    seth19960929
        11
    seth19960929  
       297 天前
    上策: 让数据部分自己判断是否同步过
    中策: 别存分组, 实时计算, 用个 bitmap 来记录是否同步过
    下策: 代码能跑. 百万级你怕什么. 千万级的 hash 都见过
    cloudzhou
        12
    cloudzhou  
       297 天前
    在维护 key 数量和足够分散做一个均衡就好了:
    1. murmurhash(uid) % 4 = 分桶信息
    2. murmurhash(uid) % 1024 = 分桶信息存储的 key
    awalkingman
        13
    awalkingman  
       297 天前
    key:user_id ,value:1|2|3|4. 不用 hash 或者别的容器,有记录就是分完的,没记录就是没分过的。就当数据库用好了,不是大 key ,就算几千万个 key 也能跑就是耗点空间而已。
    删除是异步删的,但是删大 key 会影响性能(也就是阻塞)。
    或者直接放 db ,根据主键查一个字段,也是 10ms 以内的时间。
    ben548
        14
    ben548  
    OP
       297 天前
    @cloudzhou 这个差不多就是我的想法了,第二次做我应该会直接存 hash 里面,然后再做分片,晚点去写个测试案例,来试试是不是用 hash 存储会比用几十万个 string 存更省内存
    ben548
        15
    ben548  
    OP
       297 天前
    @awalkingman big key 还是不太建议的,带来的不仅仅是 getall 的 O(n)操作导致的阻塞问题还有很多其他的问题,要用 hash 还是会考虑分片
    ben548
        16
    ben548  
    OP
       297 天前
    @seth19960929
    中策 bitmap 的方案有一个比较大的弊端,如果 userid 不是那种连续递增的类型,会导致大量空间浪费,目前我们的用户 id 是雪花算法生成的 18 位长 id ,我感觉不太适合用 bitmap 了
    下策的方案 big key 还是不太建议的,带来的不仅仅是 getall 的 O(n)操作导致的阻塞问题还有很多其他的问题,要用 hash 还是会考虑分片
    blessingsi
        17
    blessingsi  
       297 天前
    不管是 hash 还是 bitmap 都分片存就行了吧
    worldOnlyYou
        18
    worldOnlyYou  
       297 天前
    百万 key 应该还好,每个 key 也不大。换成 hash 的话,除了能节省点内存,如果后期有性能问题,不是很好解决(扩分片没有效果)
    worldOnlyYou
        19
    worldOnlyYou  
       297 天前
    定时删除是在主进程上完成,如果过多的 key 失效,redis 会有定时机制。印象中是最多 10ms?
    xuanbg
        20
    xuanbg  
       296 天前
    取个模而已,为什么还要存起来?
    awalkingman
        21
    awalkingman  
       296 天前
    @ben548 是 many key 不是 big key
    ben548
        22
    ben548  
    OP
       296 天前
    @awalkingman 百万级别的 hash 不是 big key 吗?我理解是的,big key 和 many key 是怎么划分的呢
    awalkingman
        23
    awalkingman  
       296 天前
    @ben548 我的意思是,每个 key 内容是用户 id ,value 就是这个用户的分桶数值。不用套进 hash 或者其他集合容器,这样就会有数百万个 key ,就是最基本的 key:value
    tsutomu
        24
    tsutomu  
       296 天前
    我们部门代码都是 key_uid 这样拼成的 key ,最多能有几千万。hash 是不让用的,太大了,很容易触发读写频控,好像还会导致打在一个实例上导致负载不均衡啥的,这个就不清楚了。
    ben548
        25
    ben548  
    OP
       296 天前
    @tsutomu 对的,这些都是 big key 的坏处,然后也不是不可以解决,hash 分片就行,分的足够碎就不会有这样的问题了
    fivesmallq
        26
    fivesmallq  
       296 天前
    一般来说都是不记录的,client sdk 直接算,提供好统一的 sdk ,或者你们提供 api ,server 计算就可以。 每次同步给他和他直接拿结果没有太大区别。可以看看几个开源的 ab test 项目的实现,有的用的是 MurmurHash 有的是 FNV

    https://github.com/Unleash/unleash/issues/247

    https://docs.growthbook.io/lib/build-your-own#hashseed-string-value-string-version-integer-floatnull
    Masoud2023
        27
    Masoud2023  
       296 天前
    我最近总觉得取模这个东西不太好...如果某天突然要分成 5 个就要涉及到一个重新分区的问题...到时候就.爽了
    BQsummer
        28
    BQsummer  
       296 天前
    abtest 大部分是入组不出组的, 所以要记录入组信息, 方便下次进的还是同一个组. 我们公司就是 map 存的, key 是 uid, 可能是用户量不大, 日活才 60w, redis 扛得住.
    seth19960929
        29
    seth19960929  
       295 天前
    @ben548 按 userid 尾号切分
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2946 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 15:08 · PVG 23:08 · LAX 08:08 · JFK 11:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.