1.背景
对于券码系统来说,在一些营销领域是非常常见的,同时也是比较有难度的。
主要的点是:
- 导入的券码量大,对系统会有一定的冲击,后续数据多了也容易造成大表影响性能
- 发放产生的券码记录很大,也需要考虑
- 需要对性能有较高的要求
- 发放失败后,需要做好补偿机制
2.架构设计
1.券码导入
image.png
要点:
- 对于大文件的券码,例如50w的券码,如果用xlsx格式,即使你使用easyExcel的流式处理,它仍然会一次性将文件加载进内存(内容会有每个单元的字体、颜色等等),这个是由与excel格式决定的。我们这边50w的券码大概会占用500M,所以采用csv,直接一行行读,不存在OOM风险。
2.券码发放
image.png
要点:
- 1.数据存放:发放券码的时候会触发异步任务从db查询出来,放到redis,一批1000这样的加载
- 2.加载阈值:发放券码时,会触发db加载到redis,这里有一个阈值,我们这是1w5,当券码数量在redis中小于阈值后,就会自动触发异步从db加载,这样能比较好的防止突发流量把redis的数据全部取走,导致去db加载,击穿db
- 3.一致性: 由于在调用发券的时候,有可能存在对方系统抖动,导致我们发放失败,但是这个时候券码又被从redis取出了然后因为异常,这个券码被丢弃了。所以,这里做了兜底机制,取出券码同时会有个redis标记,等发放完成后再删除。如果出现异常,那么会有定时任务去检测redis标记,进行补发,补发完成后再删除这个标记。
3.兜底机制
image.png
要点:
1、标记会先存在redis中,重试多次不成功后会落到db,db继续重试,重试再不成功后告警,这样如果下游服务长时间不稳定,不会直接把redis打爆。
3.其他细节
1、对券码、券码领取记录根据有效期备份,做个归档,减少表的数据压力
2、当数据库的券码加载到redis用完了后,会设置一个过期时间10min的数据库券码为空标记到redis,这样下次触发加载后,不会再查db,防止击穿。同时如果有券码导入了以后,会移除这个标记
3、第一次初始化的时候走同步加载,加载的线程加分布式锁,只能有一个线程去加载,其他线程CAS的方式去获取券码(sleep50ms获取一次),同时加载券的线程进行小数量(可配置)的加载,加快初次加载的时间。
4.后期优化
1、可以根据参数自动创建新表用于券码导入、发放,这样针对一些大型活动,导入、发放的时候直接走新表,减少对其他业务的影响
2、券码表分库分表,能支持短期的大量券码导入、发放