数据库表
秒杀场次表:start_time和end_time分别表示场次的开始时间和结束时间。
场次与商品关联表:promotion_seesion_id表示秒杀场次id,sku_id表示商品id,seckill_price表示秒杀价格,seckil_count表示商品数量,seckill_limit表示每个用户的秒杀限量,seckill_sort表示商品排序。
秒杀业务的特点
秒杀业务的特点是瞬时高并发。因此必须要做到限流、异步、缓存。
商品定时任务
quartz中有一个cron表达式,可以用来描述任务的启动时间。(cron表达式规则自行百度)
springboot整合定时任务和异步任务
首先在类上加@EnableScheduling注解,再在需要定时启动的任务中添加@Scheduled(cron="cron语句")注解即可。
秒杀商品上架
首先需要获取最近三天的秒杀活动信息。随后再获取这些秒杀活动需要上架的商品信息(这些商品信息作为列表存储在秒杀活动信息类中)。具体操作如下:
随后在redis中缓存活动信息与商品信息:首先需要缓存秒杀活动的id、开始时间和结束时间,设置其过期时间为1个小时:
其次需要缓存秒杀活动中商品信息(商品id、价格、秒杀库存、限购数量、排序及对于商品的相关详细信息:名称、描述等一些商品表中的内容(这个需要在秒杀服务里新创建一个类)),这里的key需要为场次id+商品id以区分不同秒杀场次的商品,设置其过期时间为1个小时。另外,还需要在redis缓存相关商品的信号量(通过redisson实现)以防止超卖,信号量的初始值等于商品库存。
然后将通过uploadSeckillSkuLatest3Days()集成上面的两个方法。然后通过定时启动(每隔整点)uploadSeckillSkuLatest3Days()方法实现将需要上架的秒杀活动及其商品更新在redis缓存中:
幂等性问题
考虑上图到分布式环境中,不同的服务器上的业务A都启动了上架定时任务是不允许的(只需要其中一个业务进行上架就可以了)。为了保证幂等性,我们需要加上分布式锁。如下图所示:
方法如下所示:
1. 使用redisson为上架方法加上互斥锁。
2. 其他业务再希望上架活动和商品可以先通过redis查询对应的key是否存在,不存在才可以进行上架。
秒杀流程
获取商品信息
首先从redis获取秒杀商品的详细信息:
合法性校验
其次需要校验合法性,首先要查询当前商品是否在秒杀时间段内;其次要校验购买的数量是否在允许范围内(之前已经设置了每一个人的购买上限);随后要判断用户是否买过:其实现方法是在redis存储用户购买的数量,key为场次id+商品id+用户id,value为购买数量,如果当前购买数量+已存储的数量<限购数量则为合法,设置这个过期时长为结束时间-当前时间。
redis减库存
当上述合法性都满足的时候,就可以开始秒杀业务。首先在redis通过相应的key获取相应商品的信号量(也就是其数量),然后让信号量减去用户购买的数量,如果库存还够,进行下一步。
向RabbitMQ发送订单消息
然后进行快速下单,向RabbitMQ发送消息:
随后秒杀业务需要监听RabbitMQ队列是否有秒杀的订单消息。首先创建相应的消息队列与交换机。随后需要绑定交换机与队列(@Bean注解在服务启动时会自动创建):
随后直接通过监听类实现队列的监听,如果队列有消息,后台就像数据库保存秒杀订单信息: