用户优惠券与用户id关联 , 并且用户id是贯穿整个系统的重要字段 , 因此使用用户id作为分库分表的路由因子 。这样可以保证同一个用户路由至相同的库表 , 既有利于数据的聚合 , 也方便用户数据的查询 。
假设共分N个库M个表 , 分库分表的路由策略为:
库后缀databaseSuffix = hash(userId) / M %N
表后缀tableSuffix = hash(userId) % M

文章插图
3.2 优惠券发放方式设计
为满足各种不同场景的发券需求 , 优惠券系统提供三种发券方式:统一领券接口、后台定向发券、券码兑换发放 。
3.2.1 统一领券接口
保证领券校验的准确性
领券时 , 需要严格校验优惠券的各种属性是否满足:比如领取对象、各种限制条件等 。其中 , 比较关键的是库存和领取数量的校验 。因为在高并发的情况下 , 需保证数量校验的准确性 , 不然很容易造成用户超领 。
存在这样的场景:A用户连续发起两次领取券C的请求 , 券C限制每个用户领取一张 。第一次请求通过了领券数量的校验 , 在用户优惠券未落库的情况下 , 如果不做限制 , 第二次请求也会通过领券数量的校验 。这样A用户会成功领取两张券C , 造成超领 。
为了解决这个问题 , 优惠券采用的是分布式锁方案 , 分布式锁的实现依赖于redis 。在校验用户领券数量前先尝试获取分布式锁 , 优惠券发放成功后释放锁 , 保证用户领取同一张券时不会出现超领 。上面这种场景 , 用户第一次请求成功获取分布式锁后 , 直至第一次请求成功释放已获取的分布式锁或超时释放 , 不然用户第二次请求会获取分布式锁失败 , 这样保证A用户只会成功领取一张 。
库存扣减
领券要进行库存扣减 , 常见库存扣减方案有两种:
方案一:数据库扣减 。
扣减库存时 , 直接更新数据库中库存字段 。
【vivo 全球商城:优惠券系统架构设计与实践】该方案的优点是简单便捷 , 查验库存时直接查库即可获取到实时库存 。且有数据库事务保证 , 不用考虑数据丢失和不一致的问题 。
缺点也很明显 , 主要有两点:
1)库存是数据库中的单个字段 , 在更新库存时 , 所有的请求需要等待行锁 。一旦并发量大了 , 就会有很多请求阻塞在这里 , 导致请求超时 , 进而系统雪崩 。
2)频繁请求数据库 , 比较耗时 , 且会大量占用数据库连接资源 。
方案二:基于redis实现库存扣减操作 。从优惠券系统当前及可预见未来的流量峰值、系统维护性、实用性上综合考虑 , 优惠券系统采用了方案一的改进方案 。改进方案是将单库存字段分散成多库存字段 , 分散数据库的行锁 , 减少并发量大的情况数据库的行锁瓶颈 。
将库存放到缓存中 , 利用redis的incrby特性来扣减库存 。
该方案的优点是突破数据库的瓶颈 , 速度快 , 性能高 。
缺点是系统流程会比较复杂 , 而且需要考虑缓存丢失或宕机数据恢复的问题 , 容易造成库存数据不一致 。

文章插图
库存数更新后 , 会将库存平均分配成M份 , 初始化更新到库存记录表中 。用户领券 , 随机选取库存记录表中已分配的某一库存字段(共M个)进行更新 , 更新成功即为库存扣减成功 。同时 , 定时任务会定期同步已领取的库存数 。相比方案一 , 该方案突破了数据库单行锁的瓶颈限制 , 且实现简单 , 不用考虑数据丢失和不一致的问题 。
一键领取多张券
在对接的业务方的领券场景中 , 存在用户一键领取多张券的情形 。因此统一领券接口需要支持用户一键领券 , 除了领取同一券模板的多张 , 也支持领取不同券模板的多张 。一般来说 , 一键领取多张券指领取不同券模板的多张 。在实现过程中 , 需要注意以下几点:
推荐阅读
- 华为|华为HarmonyOS设备已超2.4亿台 余承东:华为专利申请连续5年蝉联全球第一
- 如何在 Facebook 上赚钱
- 一文读懂全球化系统中的日期时间处理问题
- vivo|5499元起!vivo X80 Pro明天首销:骁龙8+自研芯片V1+
- 王者荣耀|《王者荣耀》更新:vivo X80 Pro天玑9000版全球首发极高帧率+极致画质
- 销量|超越《王者》!《原神》全球用户支出突破176亿元:打破季度付费记录
- 显卡|110GB/s速度 全球最快SSD加速卡诞生:塞入一块光追显卡
- vivo|vivo X80系列超大杯确认:大底主摄+高像素潜望长焦
- Intel|高塔半导体批准 Intel 354亿元买下全球第七大芯片代工厂
- 什么是全球环境问题
