关于积分兑换系统设计的思考

1 需求概述

近期在业务中接手开发类似积分兑换的功能。

需求简单描述如下: 用户可以在app中完成指定任务后,以抽奖的形式领取随机数额的积分。

比如说10个积分,每次领取到的积分是有过期时间的,只能在一定时间内使用该积分。用户可以拿着领取到的积分按照一定比例兑换优惠券(or下载券),比如说用了5个积分去做兑换。

2 需求关键点明确

整个需求的简化场景如上描述,核心的问题点是2个

  1. 积分如何拆分
  2. 如何优先使用快过期的积分
  3. 积分兑换明细

3 方案对比

3.1 方案一 - 目前线上方案

  • 方案描述:
    • 将用户的积分按最小值1拆分,用户有N个积分,表中就有N行记录。
  • 涉及存储表
    • 积分表
      • user_id
      • num 这里的num始终为1
      • status
      • start_time
      • end_time
    • 用户领取积分记录表
      • user_id
      • nums 当次获取的积分数
      • 创建时间
    • 用户兑换记录表
      • user_id
      • consume_num 消耗的值
      • exchange_num 兑换后的值
      • 兑换时间
  • 核心接口逻辑梳理
    • 获取积分逻辑
      • insert into 用户领取积分记录表
      • 批量insert into 积分表
    • 兑换积分逻辑
      • select count(num) from 积分表 where status = 0 and end_time > now 获取用户剩余的积分
      • 根据用户输入积分计算可兑换的券量
      • 异步发送下游业务进行剩余积分和消耗积分的再次对比,接着对积分表order后进行update,确保优先利用最快过期的豆
      • 写入用户兑换记录表
  • 方案优劣势
    • 缺点:数据量太大,整个数据的波动依赖于发放积分的多少,整体感觉这个场景下的这种设计思路不太合理,随着积分的翻倍,促销活动的多发,这个数据量简直是爆炸性增长
    • 优点:个人感觉没有特别明显的优点,最大的优点是省去了解决分拆的复杂度

3.2 方案二 - 单笔领取存储维度

  • 方案描述
    • 通过存储每个积分使用的量,对用户的多笔进行分拆计算
  • 涉及存储表
    • 用户积分详情表
      • user_id
      • nums 积分数
      • used 使用积分数
      • start_time
      • end_time
    • 用户兑换记录表
      • user_id
      • consume_num 消耗的值
      • exchange_num 兑换后的值
      • 兑换时间
    • 用户流水明细表
      • user_id
      • type 增加积分 or 消耗积分 可扩展对应场景
      • nums
      • detail 记录详情 包括消耗的是哪个id下的积分?增加的是哪个id
      • 创建时间
    • redis
      • 存储用户总的积分值 可离线运行 日级别更新
  • 核心接口逻辑梳理
    • 获取积分逻辑
      • INSERT into 用户积分详情表 user_id, nums, used=0, end_time=now() + 7days
    • 兑换积分逻辑
      • 先获取所有可用积分,并按过期日期排序:SELECT xx from 用户积分详情表 where user_id and end_time > now(), nums > used ORDER_BY end_time
      • 判断sum( nums - used ) 看可用积分够不够
      • 如果可用积分是够的,就按照order好的依次更新各条记录中的 used 的值,直到本次使用积分扣除完毕
  • 方案优劣势
    • 缺点:在涉及分拆时代码处理会稍复杂一些
    • 优点:库表的容量可控,不会存在极速增长的情况

4 说明

上述文档没有说到以下几点,但是需要关注

  1. 缓存
  2. 事务

缓存的话,看业务的量,如果并发不大,个人认为数据库完全可以抗住 事务的话,可以采用 柔性事务或者是补偿性的来,也可以直接使用日常的大事务,只要保证并发量和操作稳定性。