防同一用户并发操作方案咨询

新客领取优惠券逻辑

  • 判断是否新客

  • 若为新客 查询数据库是否已领取过

  • 未领取过 给用户发券

同一用户并发领券的情况下 会有超领的情况 因为并发进来时查询数据库均没有领取记录

而又不能针对用户和优惠券做唯一索引 因为有些券允许用户领取多张。

于是决定采用redis计数来防同一用户并发超领 新客领券逻辑改成如下

  • 判断是否新客

  • 若为新客 基于用户ID计数 incr {user_id}_receive_count

  • 如果计数大于1 表示同一用户并发领券 直接返回

  • 如果计数==1 表示第一次领取

  • 查询数据库是否已领取过

  • 未领取过 给用户发券

上述逻辑没有问题 但纠结该如何设置计数key的过期时间

  • 设置过期时间为1分钟 基本上大部分并发操作都是在同一秒 过期时间1分钟能满足要求了

  • 设置过期时间为1小时 万一数据库负载高呢 并不能在一分钟内完成一次领券事务操作 如双十一这种场景 还是设的长一点 更保险一点 不管怎样 1小时足够完成一次领券事务操作了吧 但时间设的长了 会不会因为其他原因(如网络超时)导致领券失败回滚 用户要等一小时后 才能重新领券

有没其他防并发领取方案呢? 可以不用纠结这种过期时间的设置呢。

阅读 4.1k
评论
    5 个回答
    • 972

    建议将防并发超领与判断是否新客 分开判断

    • 防并发超领可以使用以下(上锁), 每次操作完成记得删除key(释放锁)

    // 操作的原子性,如该key在有效时间30秒被设置过返回0,一般请求超时为30秒
    $redis->set($key, 1, array("NX", "EX"=>'30'));
    • 判断是否是新客(用户是否领取过,有效时间设置到活动结束时间),可以设置一个新的KEY作用户的领取状态,领取过直接返回给用户已经领取过优惠(追求并发性能时,不连接数据库查询)

    • 获取优惠卷(提前把优惠卷塞入队列,从队列里获取,不从数据库获取,此处的好处是,队列数据取完就没,不会出现超领取情况),写入redis队列

    • 后台进程消费队列,我用链表阻塞类型进行消费(将领取信息存放入库,入库前对用户是否领取过优惠卷进行查询判断)

      相似问题
      推荐文章