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

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

  •  
  •   ben548 · 2023-06-05 17:21:29 +08:00 · 2121 次点击
    这是一个创建于 524 天前的主题,其中的信息可能已经有所发展或是发生改变。
    一个关于 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  
       2023-06-05 17:57:35 +08:00
    你们的 id 是 long 类型的吗?如果是 long 类型的话为什么要 hash ?
    ben548
        2
    ben548  
    OP
       2023-06-05 17:59:44 +08:00
    @xiaofan2 是 long 类型,目前设计不是用 hash ,考虑用 hash 是想着 100w 的 string key-value 数据内存占用应该是大于一个 100 万 key-value 的 hash 数据的(没做测试,只是靠印象和经验)
    nicreve
        3
    nicreve  
       2023-06-05 18:08:54 +08:00   ❤️ 1
    Murmur 也没有这么吃性能吧,为什么不每次都实时计算而是要存在 Redis 里呢?如果单纯为了 ID 分布均匀,还有很多比 Murmur 更快的非标 Hash 算法,反正这个场景也不在乎 Hash 冲突。
    matrix1010
        4
    matrix1010  
       2023-06-05 18:13:04 +08:00
    value 是什么, 每个用户都不一样吗?
    octobersnow
        5
    octobersnow  
       2023-06-05 18:16:06 +08:00
    每次都 hash ,不用存 redis
    ben548
        6
    ben548  
    OP
       2023-06-05 18:21:09 +08:00
    @nicreve 因为需要记录是不是第一次生成,只有第一次分配桶,才触发同步数据部门,我需要记录这样一个是否已经分配过的状态,来判断是否需要同步分桶结果给到数据部门
    ben548
        7
    ben548  
    OP
       2023-06-05 18:21:49 +08:00
    @matrix1010 就是 0 ,1 ,2 ,3 这种数字,分到对应桶的用户都是一个 value
    ben548
        8
    ben548  
    OP
       2023-06-05 18:21:56 +08:00
    @octobersnow 因为需要记录是不是第一次生成,只有第一次分配桶,才触发同步数据部门,我需要记录这样一个是否已经分配过的状态,来判断是否需要同步分桶结果给到数据部门
    nicreve
        9
    nicreve  
       2023-06-05 18:24:25 +08:00
    @ben548 问题是不是搞复杂了,你和数据部门的 Hash 及分桶规则保持一致不就可以了么,不需要进行同步啊。别告诉我你们的数据部门只会写 SQL ,这种基本的工程能力都没有?
    ben548
        10
    ben548  
    OP
       2023-06-05 18:26:28 +08:00
    @nicreve 目前是需要同步给他们的,我理解是这样会更好一些吧,因为这样数据部门不与业务绑定,相互隔离会是更好的设计吧,不然我们这边的规则改了,他们也要跟着一起改吗?很多时候没有及时通知容易出问题
    seth19960929
        11
    seth19960929  
       2023-06-05 18:27:27 +08:00
    上策: 让数据部分自己判断是否同步过
    中策: 别存分组, 实时计算, 用个 bitmap 来记录是否同步过
    下策: 代码能跑. 百万级你怕什么. 千万级的 hash 都见过
    cloudzhou
        12
    cloudzhou  
       2023-06-05 18:28:45 +08:00
    在维护 key 数量和足够分散做一个均衡就好了:
    1. murmurhash(uid) % 4 = 分桶信息
    2. murmurhash(uid) % 1024 = 分桶信息存储的 key
    awalkingman
        13
    awalkingman  
       2023-06-05 18:49:44 +08:00
    key:user_id ,value:1|2|3|4. 不用 hash 或者别的容器,有记录就是分完的,没记录就是没分过的。就当数据库用好了,不是大 key ,就算几千万个 key 也能跑就是耗点空间而已。
    删除是异步删的,但是删大 key 会影响性能(也就是阻塞)。
    或者直接放 db ,根据主键查一个字段,也是 10ms 以内的时间。
    ben548
        14
    ben548  
    OP
       2023-06-05 20:05:28 +08:00
    @cloudzhou 这个差不多就是我的想法了,第二次做我应该会直接存 hash 里面,然后再做分片,晚点去写个测试案例,来试试是不是用 hash 存储会比用几十万个 string 存更省内存
    ben548
        15
    ben548  
    OP
       2023-06-05 20:07:51 +08:00
    @awalkingman big key 还是不太建议的,带来的不仅仅是 getall 的 O(n)操作导致的阻塞问题还有很多其他的问题,要用 hash 还是会考虑分片
    ben548
        16
    ben548  
    OP
       2023-06-05 20:11:52 +08:00
    @seth19960929
    中策 bitmap 的方案有一个比较大的弊端,如果 userid 不是那种连续递增的类型,会导致大量空间浪费,目前我们的用户 id 是雪花算法生成的 18 位长 id ,我感觉不太适合用 bitmap 了
    下策的方案 big key 还是不太建议的,带来的不仅仅是 getall 的 O(n)操作导致的阻塞问题还有很多其他的问题,要用 hash 还是会考虑分片
    blessingsi
        17
    blessingsi  
       2023-06-05 21:02:29 +08:00
    不管是 hash 还是 bitmap 都分片存就行了吧
    worldOnlyYou
        18
    worldOnlyYou  
       2023-06-05 23:07:11 +08:00
    百万 key 应该还好,每个 key 也不大。换成 hash 的话,除了能节省点内存,如果后期有性能问题,不是很好解决(扩分片没有效果)
    worldOnlyYou
        19
    worldOnlyYou  
       2023-06-05 23:08:29 +08:00
    定时删除是在主进程上完成,如果过多的 key 失效,redis 会有定时机制。印象中是最多 10ms?
    xuanbg
        20
    xuanbg  
       2023-06-06 02:59:56 +08:00
    取个模而已,为什么还要存起来?
    awalkingman
        21
    awalkingman  
       2023-06-06 09:30:00 +08:00
    @ben548 是 many key 不是 big key
    ben548
        22
    ben548  
    OP
       2023-06-06 10:50:50 +08:00
    @awalkingman 百万级别的 hash 不是 big key 吗?我理解是的,big key 和 many key 是怎么划分的呢
    awalkingman
        23
    awalkingman  
       2023-06-06 10:55:14 +08:00
    @ben548 我的意思是,每个 key 内容是用户 id ,value 就是这个用户的分桶数值。不用套进 hash 或者其他集合容器,这样就会有数百万个 key ,就是最基本的 key:value
    tsutomu
        24
    tsutomu  
       2023-06-06 11:27:36 +08:00
    我们部门代码都是 key_uid 这样拼成的 key ,最多能有几千万。hash 是不让用的,太大了,很容易触发读写频控,好像还会导致打在一个实例上导致负载不均衡啥的,这个就不清楚了。
    ben548
        25
    ben548  
    OP
       2023-06-06 11:46:23 +08:00
    @tsutomu 对的,这些都是 big key 的坏处,然后也不是不可以解决,hash 分片就行,分的足够碎就不会有这样的问题了
    fivesmallq
        26
    fivesmallq  
       2023-06-06 12:52:55 +08:00
    一般来说都是不记录的,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  
       2023-06-06 17:39:48 +08:00
    我最近总觉得取模这个东西不太好...如果某天突然要分成 5 个就要涉及到一个重新分区的问题...到时候就.爽了
    BQsummer
        28
    BQsummer  
       2023-06-06 18:00:39 +08:00
    abtest 大部分是入组不出组的, 所以要记录入组信息, 方便下次进的还是同一个组. 我们公司就是 map 存的, key 是 uid, 可能是用户量不大, 日活才 60w, redis 扛得住.
    seth19960929
        29
    seth19960929  
       2023-06-07 10:11:55 +08:00
    @ben548 按 userid 尾号切分
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3000 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 13:48 · PVG 21:48 · LAX 05:48 · JFK 08:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.