发券
面向海量用户发放支付券。
异步发券-提升接口响应速度
适用:用户领到券后不会立即使用的情况。

异步发券带来的问题:
用户领券后感知到领券成功,实际券最终没有发给用户,大部分为库存不足、风控拦截等原因。
库存扣减优化-减热点
库存操作的逻辑是:
收到一个用户的发券请求->读取 Redis,查看发放的券批次库存是否充足->写入 Redis,扣减券批次库存。
为了使 Redis 集群流量均匀,不同券批次的库存数据被打散到了不同的 Redis 分片上。
但是当集中发放某一券批次时,流量仍然会大量偏移到一个分片内,造成 Redis 数据热点问题。
需要想办法能将某个券批次多次零散扣减库存的操作合并到一起。
库存拆分:
将单库存字段分散成多库存字段,分散数据库的行锁,减少并发量大的情况数据库的行锁瓶颈。

库存数更新后,会将库存平均分配成M份,初始化更新到库存记录表中。
用户领券,随机选取库存记录表中已分配的某一库存字段(共M个)进行更新,更新成功即为库存扣减成功。
- 同时,定时任务会定期同步已领取的库存数。
相比方案一,该方案突破了数据库单行锁的瓶颈限制,且实现简单,不用考虑数据丢失和不一致的问题。
常见库存扣减方案有两种:
方案一:
- 数据库扣减。
缺点主要有两点:
库存是数据库中的单个字段,在更新库存时,所有的请求需要等待行锁。
- 一旦并发量大了,就会有很多请求阻塞在这里,导致请求超时,进而系统雪崩。
频繁请求数据库,比较耗时,且会大量占用数据库连接资源。
方案二:
- 基于
Redis
实现库存扣减操作。将库存放到缓存中,利用
Redis
的Incrby
特性来扣减库存。缺点是:系统流程会比较复杂,而且需要考虑缓存丢失或宕机数据恢复的问题,容易造成库存数据不一致。
合并发券:
尝试对请求中相同券批次的发券请求进行合并,扣减库存时进行集中扣减。
比如之前为 N 个不同用户发放同一券批次 A,每次库存减 1,需要对 Redis 进行 N 次写操作。
合并发券后,只需对 Redis 进行 1 次写操作,库存扣减 N 即可。
扣减库存前的校验逻辑:
在应用本地内存维护了一份券批次的库存信息,定时将 Redis 库存信息同步到本地。
发券时只需要在本地内存简单校验库存信息即可,不需要再访问远程的 Redis。
幂等校验
每一次发券动作都会生成一个全局唯一的序列号,发券时会将序列号作为唯一索引落入数据库中。
当发生用户连续点击领券或网络异常重试等情况时,相同序列号由于唯一索引冲突落库无法成功。
库存防超卖
每次对 Redis 进行库存扣减时,可能会存在网络超时、失败等异常情况,造成扣减库存的结果处于未知状态。
如果发券失败,可以尝试回滚 Redis 库存。
券过期
券过期是一个状态推进的过程,使用
RocketMQ
来实现。由于
RocketMQ
支持的延时消息有最大限制,而卡券的有效期不固定,有可能会超过限制。
- 所以将卡券过期消息循环处理,直到卡券过期。