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

微信读书的书币逻辑是怎样做到独立过期的呢?

  •  1
     
  •   zero47 · 12 天前 · 4912 次点击
    有点好奇微信读书的书币系统是怎么实现的。微信读书每天阅读可以获得书币奖励,基本每天能领取 1 到 2 个,而这些领取的书币都有独立的有效期,貌似一个月。如果每笔领取记录都单独过期,意思是一个月可能会有 30 多笔记录。假设每笔都是 1 个书币,难道买本 30 块的电子书要更新 30 条记录?每次查看余额都要 sum 一下记录?
    46 条回复    2024-04-22 09:36:30 +08:00
    yefuchao
        1
    yefuchao  
       12 天前
    余额就是一个数字,过期时间到了给你扣掉不就好了
    NoOneNoBody
        2
    NoOneNoBody  
       12 天前   ❤️ 1
    硬件足够的话,数据越细越好
    不需要每次计算,缓存,或者保存预计算结果就可以了
    zero47
        3
    zero47  
    OP
       12 天前
    @yefuchao 不能吧,他付款肯定是先用旧的奖励记录,那就必须每笔记录是否被消费都记
    Donjote
        4
    Donjote  
       12 天前
    更新 30 条记录也没啥吧,余额可以存另外一个表里
    Subfire
        5
    Subfire  
       12 天前   ❤️ 1
    跟游戏开发中的道具类似, 每次获得的书币道具, 只是 configId 一样, 但是 instanceId 是新增的, 不同 instanceId 的道具都有独立的有效期. 扣减书币道具的时候, 优先扣减即将到期的
    xxxaadsdss
        6
    xxxaadsdss  
       12 天前
    每次领取都是一条领取记录。领取的商品有过期时间。到时间了。减一下总表的 书币数就好了
    zero47
        7
    zero47  
    OP
       12 天前
    想了想还有一个情况是,30 块的交易,用户有 29 个 1 块书币奖励和 1 个 2 块书币奖励,还得在最后一个 2 块奖励里记录消费了 1 块,还有 1 块没被消费,这太不优雅了吧
    zero47
        8
    zero47  
    OP
       12 天前
    @xxxaadsdss 这只能解决 sum 的问题,实际消费还得一条一条的更新奖励记录
    NessajCN
        9
    NessajCN  
       12 天前   ❤️ 4
    参考比特币的区块嘛
    做一个表,只记录交易信息,譬如 xxxx(时间戳) 入账 1 币,一个月后该条交易记录失效
    消费的时候就是手动将依然在有效期内的前 3 条 1 币交易记录失效,如果是入账 3 币只消费 2 币的记录就失效掉 3 币的记录重新生成一条基于原时间戳的 1 币记录
    余额计算就是简单的所有有效入账记录求和
    zero47
        10
    zero47  
    OP
       12 天前
    @NessajCN 是的,就是觉得这逻辑太重了,在微信读书这种大体量用户下,这个独立过期逻辑有点自己坑自己的意思
    NessajCN
        11
    NessajCN  
       12 天前
    @zero47 不重啊......你自己试一下就知道了,没啥计算量的,也就 IO 多一些。不过这种程度的 IO 相对微信本身那就是毛毛雨了
    Rickkkkkkk
        12
    Rickkkkkkk  
       12 天前
    每条领取记录都是库里一条记录
    定期跑离线任务去库里把数据都过期掉
    算余额会把库里的值全部加起来


    这里会出现几个问题:
    跑全量任务更新过期会不会太重了? (记录很多, 真正要过期的很少)
    每次算余额要把所有的记录加起来会不会慢查询?

    (留为作业吧)
    Fish1024
        13
    Fish1024  
       12 天前
    领取的时候就写入了这些币的过期时间,到时间自动过期了。
    tomatocici2333
        14
    tomatocici2333  
       12 天前
    @Fish1024 我感觉也是 写个定时任务扫描过期
    caotian
        15
    caotian  
       12 天前   ❤️ 5
    之前做一个简单的系统想积分带过期功能,硬着头皮做了类似的方案,基本也实现了需求,结果最后做到退款功能时,还是给整破防了,因为还要考虑退款时退积分,退的积分要根据退款金额计算,退的积分还要保持原来的过期时间,还要考虑退款时,退回的积分有可能已经过期了,过期的积分是直接过期,还是根据规则折算成新积分,感觉本来一个简单的功能越做越复杂,最后索性不做积分过期功能了
    deltadawn
        16
    deltadawn  
       12 天前   ❤️ 2
    按每月存 1 条记录,初始值为 32 个 0 ,签到的时候把当前天数那一位改成获得的金币数,过期只要把上个月当前天那一位改成 0 ,扣钱的时候,从上个月当天开始循环减。这样数据库操作就少了
    jookr
        17
    jookr  
       12 天前
    先进先出呗。
    先获取的,先到期/先使用
    ashuai
        18
    ashuai  
       12 天前
    balance 另有个 detail 表,独立过期时间就行了,不是啥麻烦事
    nqlair
        19
    nqlair  
       12 天前
    存成 sortedMap ,key 是过期时间,value 是积分,每次登录把过期的删除,积分就是未过期的所有 value 的和,使用时候优先扣过期时间近的
    tinytoadd
        20
    tinytoadd  
       12 天前
    使用书币是先用快过期的还是先用新领取的。
    zero47
        21
    zero47  
    OP
       12 天前
    @caotian 我也觉得短期过期太复杂了,话费信用卡的积分都一年清算一次
    qeqv
        22
    qeqv  
       12 天前
    感觉最简单的方法就是你正文提到的了,别的逻辑都很复杂。
    1. 购买时更新 30 条记录也没什么
    2. 总额可以做缓存,余额变动时更新
    3. 1 个 2 块书币奖励可以设置成两条 1 书币记录
    futuretech6
        23
    futuretech6  
       12 天前
    感觉是类似 NFT 的实现,每个 token 有自己的 id 和过期时间,然后整体也会维护一个 token 数量信息
    runzekk
        24
    runzekk  
       12 天前
    sum 也没什么问题,冗余存也没什么问题,量不大,没有大事物都不是问题
    444571840
        25
    444571840  
       12 天前
    如果要实现的话要挺容易的,数据库一个字段就能解决。
    记录的时候 [获得书币 A ,到期时间戳 1 。获得书币 B ,到期时间戳 2 。xxx],每次要写入新数据的时候,先判断是否有已经到期的书币,删除了,再在字段后面添加(新活动书币,新到期时间戳)即可。
    客户端每次要展示的时候,根据当前时间戳把数据库未到期的 sum 一遍就好了。
    其他查历史的话看 log 就好。
    mrgeneral
        26
    mrgeneral  
       12 天前
    总和可以是单独的数据,过期是定时任务就能解决,流水信息可以定期归档,实际数据量不大。
    janus77
        27
    janus77  
       12 天前
    领取时一条一条更新
    使用时只需要更新一下总数字就行了,只有一次啊
    hanbin
        28
    hanbin  
       12 天前   ❤️ 2
    两个表:

    CREATE TABLE PointsRecord (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '自增 ID',
    user_id INT NOT NULL COMMENT '用户 ID',
    points_amount INT NOT NULL COMMENT '积分数量',
    receive_time TIMESTAMP NOT NULL COMMENT '领取时间',
    expiration_time TIMESTAMP NOT NULL COMMENT '过期时间',
    status VARCHAR(20) NOT NULL COMMENT '状态',
    receive_source VARCHAR(50) NOT NULL COMMENT '领取来源',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
    ) COMMENT '积分领取记录表';

    CREATE TABLE PointsWallet (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '积分钱包 ID',
    user_id INT NOT NULL COMMENT '用户 ID',
    points_balance INT NOT NULL DEFAULT 0 COMMENT '积分余额',
    total_points INT NOT NULL DEFAULT 0 COMMENT '积分总额',
    used_points INT NOT NULL DEFAULT 0 COMMENT '已用积分总额',
    expired_points INT NOT NULL DEFAULT 0 COMMENT '已过期积分总额',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
    ) COMMENT '积分钱包表';

    领取积分:

    PointsRecord 入库一条记录,PointsWallet 更新 total_points & points_balance ,事务处理

    过期积分:

    PointsRecord 更新一条数据,PointsWallet 更新 total_points & points_balance & expired_points ,事务处理

    积分消费:

    PointsWallet 更新 used_points & points_balance

    过期积分用脚本处理

    积分消费时判断余额够就允许消费。
    hanbin
        29
    hanbin  
       12 天前
    @hanbin 将积分领取和积分消费解耦,消费时不关心积分的有效期属性,积分的有效属性也不关心消费的具体场景,只关心额度变化即可。
    MoYi123
        30
    MoYi123  
       12 天前
    @zero47 这点数量算什么? 随便一个网络游戏, 一次就加载几千个装备物品, 还不是大把人能做?
    pkoukk
        31
    pkoukk  
       12 天前
    两个表,一个当前的 sum ,一个 detail
    通过事件系统,detial 表的变动通知到 sum ,sum 改余额就完事了
    而且一天才一两条,不用事件系统,搞定时任务扫都绰绰有余
    NizumaEiji
        32
    NizumaEiji  
       12 天前
    流水过期
    handpr
        33
    handpr  
       12 天前
    类似物流的.出入库. 入库明细-》 spu,sku,批次,效期次,入库数量
    Motorola3
        34
    Motorola3  
       12 天前
    @zero47 有没有一种可能 实际上都是 1
    Motorola3
        35
    Motorola3  
       12 天前
    @deltadawn 有道理
    kaedea
        36
    kaedea  
       12 天前 via Android
    你在查找的是不是:数据库
    somebody1
        37
    somebody1  
       12 天前
    @deltadawn
    补充思路,可以是 32 个 0-9 ,a-z ,这样每天获取到的就可以非常多了,只需要计算一个 32 的字符串,就可以算出来了。

    但是缺点有很明显,微信读书可以做到分钟级别的失效,这个就做不到了。
    yanliu
        38
    yanliu  
       12 天前
    以我浅见,这种余额类的大都是事件溯源的,即你看到的余额不是简单的存储了一个数字,而是由交易记录实时计算出来的,然后定期合并一个快照,减少计算量,那么就可以在交易记录上打标记来实现过期。这是最常用,也是最安全的做法。当然,也有简单的方案,比如将这类会过期的书币存入 redis 的 zset 中,然后将过期时间戳作为 score ,就可以使用 ZRANGEBYSCORE 之类的命令来返回当前时间和过期时间之间的数据,求和就行了。
    yanliu
        39
    yanliu  
       12 天前
    @yanliu 一切都要看业务。如果你有 15L 说的那样情况,或者其它情况,那么事件溯源,就是这一类问题目前的唯一解,同时兼顾了安全性和灵活性,并且快照设置合适的话,并发性也不差。
    cellar
        40
    cellar  
       12 天前
    又不是严格的金融系统。。要么弄个 job ,过期的全删了,要么 where 条件永远带个时间条件就是了。。
    资源有的是的话弄个 redis...
    whileFalse
        41
    whileFalse  
       12 天前 via Android
    余额会单独存储,每次买书同时更新余额和 30 条消费记录
    DB 性能很强的,现在的硬件太牛逼了
    Znemo
        42
    Znemo  
       12 天前
    @somebody1 多几个字段,都是 32 个 0-9 ,分别表示个、十、百、千、万位,这样每天的数量取就没有上限了。
    sherryqueen
        43
    sherryqueen  
       11 天前
    每天记录多加一个余额和状态字段. 消费前拉出所有可用余额, 找出要扣除的记录更新对应状态和余额就好了吧?
    sherryqueen
        44
    sherryqueen  
       11 天前
    至于总余额 临时算或找个地方 cache 下. 每条记录的过期的话, 定时任务或找个用户查询/消费的时候进行一次统一计算就行.但用户也就是一次批量查询 + 一次批量更新. 计算量也不大
    flyingghost
        45
    flyingghost  
       10 天前
    大体量用户❎
    蝇量级计算✅

    数字产品没有库存问题,所以各用户间购买行为无关,自己消费自己的就行。
    购买行为不涉及用户间的借款、代付,所以自己算自己的账就行。
    每个用户独立计算自己的币和消费过程,总共百条书币记录,0.1TPS 的操作,单用户放文件存储都不怕效率低。
    过期状态更新只存在于查看时、消费时,依然百条书币记录,0.1TPS 的操作。

    先不考虑其他功能需求,单说你的问题来看,这简直和大体量没关系啊。。。
    唯一和大体量有关的是,如何在亿级用户中找到这个用户独立的账户存储空间。
    lazyfighter
        46
    lazyfighter  
       9 天前
    可以算一下, 我们默认存储金币记录 1 年, 假设所有用户每天全部签到 365 ,假设共 2 亿用户,那么 2 年的金币记录就是:2*2*365 亿,假设分 128 个表,每个表就是 6 亿左右,所以目前来看存储不是大问题, 那么问题来了就是过期怎么搞,单纯的定时任务去扫这几百亿的数据很扯,所以分开看线上用户实时查看: 实时算每个用户请求自己实时算生效的金币。运营数据统计: 离线任务
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2529 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:12 · PVG 08:12 · LAX 17:12 · JFK 20:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.