优惠券业务

月伴飞鱼 2024-11-18 00:01:12
业务相关
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

电商系统的促销手段:优惠券,拼团,砍价,老带新。

优惠券种类

满减券,直减券,折扣券。

优惠券系统的核心流程

发券,领券,用券。

优惠券分库分表

用户优惠券与用户ID关联,并且用户ID是贯穿整个系统的重要字段,因此使用用户ID作为分库分表的路由因子。

这样可以保证同一个用户路由至相同的库表,既有利于数据的聚合,也方便用户数据的查询。

保证领券校验的准确性

领券时,需要严格校验优惠券的各种属性是否满足:

  • 比如领取对象、各种限制条件等。
    • 其中,比较关键的是库存领取数量的校验。
  • 因为在高并发的情况下,需保证数量校验的准确性,不然很容易造成用户超领

存在这样的场景:

  • A用户连续发起两次领取券C的请求,券C限制每个用户领取一张。
  • 第一次请求通过了领券数量的校验,在用户优惠券未落库的情况下,如果不做限制,第二次请求也会通过领券数量的校验。
    • 这样A用户会成功领取两张券C,造成超领。

采用分布式锁方案:

在校验用户领券数量前先尝试获取分布式锁,优惠券发放成功后释放锁,保证用户领取同一张券时不会出现超领。

库存扣减

领券要进行库存扣减,常见库存扣减方案有两种:

方案一:

  • 数据库扣减。

缺点主要有两点:

  • 库存是数据库中的单个字段,在更新库存时,所有的请求需要等待行锁

    • 一旦并发量大了,就会有很多请求阻塞在这里,导致请求超时,进而系统雪崩。
  • 频繁请求数据库,比较耗时,且会大量占用数据库连接资源。

方案二:

  • 基于Redis实现库存扣减操作。

将库存放到缓存中,利用RedisIncrby特性来扣减库存。

缺点是:系统流程会比较复杂,而且需要考虑缓存丢失或宕机数据恢复的问题,容易造成库存数据不一致。

库存拆分:

将单库存字段分散成多库存字段,分散数据库的行锁,减少并发量大的情况数据库的行锁瓶颈。

img

库存数更新后,会将库存平均分配成M份,初始化更新到库存记录表中。

用户领券,随机选取库存记录表中已分配的某一库存字段(共M个)进行更新,更新成功即为库存扣减成功。

  • 同时,定时任务会定期同步已领取的库存数。

相比方案一,该方案突破了数据库单行锁的瓶颈限制,且实现简单,不用考虑数据丢失和不一致的问题。

一键领取多张券

在对接的业务方的领券场景中,存在用户一键领取多张券的情形。

因此统一领券接口需要支持用户一键领券,除了领取同一券模板的多张,也支持领取不同券模板的多张。

  • 一般来说,一键领取多张券指领取不同券模板的多张。

在实现过程中,需要注意以下几点:

如何保证性能

领取多张券,如果每张券分别进行校验、库存扣减、入库,那么接口性能的瓶颈卡在券的数量上,数量越多,性能直线下降。

两个措施:

批量操作

  • 从发券流程来看,瓶颈在于券的入库。
  • 领券是实时的(异步的话,不能实时将券发到用户账户下,影响到用户的体验还有券的转化率)。
    • 券越多,入库时与数据库的IO次数越多,性能越差。
  • 批量入库可以保证与数据库的IO的次数只有一次,不受券的数量影响。
  • 如上所述,用户优惠券数据做了分库分表,同一用户的优惠券资产保存在同一库表中,因此同一用户可实现批量入库。

限制单次领券数量

  • 设置阀值,超出数量后,直接返回,保证系统在安全范围内。

保证高并发情况下,用户不会超领:

假如用户发起请求一键领取A/B/C/D四张券,同时活动系统给用户发放券A,这两个领券请求是同时的。

  • 其中,券A限制了每个用户只能领取一张。

按照前述采用分布式锁保证校验的准确性,两次请求的分布式锁的Key分别为:

用户id+A_id+B_id+C_id+D_id

用户id+A_id

这种情况下,两次请求的分布式锁并没有发挥作用,因为锁Key是不同,数量校验依旧存在错误的可能性。

为避免批量领券过程中用户超领现象的发生,在批量领券过程中,对分布锁的获取进行了改造。

上例一键领取A/B/C/D四张券,需要批量获取4个分布式锁,锁Key为:

用户id+A_id

用户id+B_id

用户id+C_id

用户id+D_id

获取其中任何一个锁失败,即表明此时该用户正在领取其中某一张券,需要自旋等待(在超时时间内)。

获取所有的分布式锁成功,才可以进行下一步。

领券接口幂等性

在网络超时、异常情况下,领券结果没有及时返回,业务方会进行领券重试。

  • 如果接口不保证幂等性,会造成超发

幂等性的实现有多种方案,可以利用数据库的唯一索引来保证幂等。

如何给大量用户发券?

异步发送。

券过期

券过期是一个状态推进的过程,使用 RocketMQ 来实现。

由于 RocketMQ 支持的延时消息有最大限制,而卡券的有效期不固定,有可能会超过限制。

  • 所以将卡券过期消息循环处理,直到卡券过期。
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!