优惠券系统

月伴飞鱼 2025-05-11 22:19:19
系统设计
支付宝打赏 微信打赏

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

发券

面向海量用户发放支付券。

异步发券-提升接口响应速度

适用:用户领到券后不会立即使用的情况。

图片

异步发券带来的问题:

用户领券后感知到领券成功,实际券最终没有发给用户,大部分为库存不足、风控拦截等原因。

库存扣减优化-减热点

库存操作的逻辑是:

收到一个用户的发券请求->读取 Redis,查看发放的券批次库存是否充足->写入 Redis,扣减券批次库存。

为了使 Redis 集群流量均匀,不同券批次的库存数据被打散到了不同的 Redis 分片上。

但是当集中发放某一券批次时,流量仍然会大量偏移到一个分片内,造成 Redis 数据热点问题。

需要想办法能将某个券批次多次零散扣减库存的操作合并到一起。

库存拆分:

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

img

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

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

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

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

常见库存扣减方案有两种:

方案一:

  • 数据库扣减。

缺点主要有两点:

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

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

方案二:

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

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

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

合并发券:

尝试对请求中相同券批次的发券请求进行合并,扣减库存时进行集中扣减。

比如之前为 N 个不同用户发放同一券批次 A,每次库存减 1,需要对 Redis 进行 N 次写操作。

合并发券后,只需对 Redis 进行 1 次写操作,库存扣减 N 即可。

扣减库存前的校验逻辑:

在应用本地内存维护了一份券批次的库存信息,定时将 Redis 库存信息同步到本地。

发券时只需要在本地内存简单校验库存信息即可,不需要再访问远程的 Redis。

幂等校验

每一次发券动作都会生成一个全局唯一的序列号,发券时会将序列号作为唯一索引落入数据库中。

当发生用户连续点击领券或网络异常重试等情况时,相同序列号由于唯一索引冲突落库无法成功。

库存防超卖

每次对 Redis 进行库存扣减时,可能会存在网络超时、失败等异常情况,造成扣减库存的结果处于未知状态。

如果发券失败,可以尝试回滚 Redis 库存。

券过期

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

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

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

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