一、背景
秒杀场景就不多做介绍了,本文只是讲解如果使用RabbitMQ进行异步下单,后续会有专门文章讲如何使用利用Jmter压力秒杀接口,接下来首先看下如果不使用异步下单的方式而是采用传统方式处理会有什么问题。
二、传统模式
传统处理:如果不涉及到redis的话最初用户请求进来的流程大概是先去数据库判断下当前用户是否已经秒杀过当前商品,如果秒杀过的话则返回秒杀失败不能重复秒杀,否则的话则执行减库存,下订单等步骤。然而在高并发场景下尤其是秒杀场景下,用户在同一时间点可能都在秒杀,此时的用户量非常庞大直接访问数据库的话数据库资源马上就会被耗尽,因此此方法我们废弃。
三、利用Redis进行预减库存
为了减少对数据库的访问我们很自然就会想到使用redis进行缓存处理,步骤为:用户请求过来之后不访问数据库而是访问redis,我们在系统初始化的时候就将库存信息写入到redis中,这样无论有多少用户在秒杀的时候都先经过redis来判断库存,如果库存大于0的话则进行下一步即判断是否已经秒杀过,否则直接返回秒杀失败(库存都小于0了还秒杀啥呢~)。画个图大概是这个样子:
四、使用内存标记优化
请注意:虽然此时利用redis进行了预减库存,但毕竟还是要操作redis,因此我们还可在此基础上进一步优化,即内存标记法,大致流程为:用户请求 -> 内存标记 -> redis -> 数据库。画个图大概是这个样子
那么实现内存标记到底该怎么做呢,请看下面示例代码:
首先定义了一个Map该Map即作为内存标记的存储,在初始化时除了将库存数量写入redis(下次访问不再查库)之外我们在map中也存放一份,默认第一次加载的时候是flase表示当前商品还未结束秒杀。接下来在访问redis的时候会首先根据goodsId去取当前商品是否结束秒杀如返回true则表示已结束则抛错,否则进行预减库存。
五、使用RabbitMQ进行异步下单
对于秒杀这种高并发的场景,仅仅通过前面的预减库存是远远不够的,大量请求同时涌入之后,我们需要设计一个暂存请求的空间从而来缓解系统压力,这时RabbitMQ就登场了,预减库存之后如果该商品还在秒杀中,则将该次请求入队,并马上返给排队中给用户展示类似12306买票场景,之后将该请求出队列并生成订单、减库存成功之后将订单写入到redis中,客户端做轮询处理,如发现生成订单则返回秒杀成功。
请注意:在此判断是否已经秒杀到的时候必须是从数据库中查询结果,如果是从redis中查的话可能存在库中存在,但是还没有写到redis的情况,此时明明用户已经秒杀成功,但是查询结果却是秒杀失败。如果秒杀到了则返回已经秒杀过不可重复秒杀,否则的话直接入队并返回排队中。
出队的时候判断库存是否小于0或者是否已经秒杀到(订单已存在)这两种情况下直接返回,否则下订单、减库存,下订单写入村的方法就不截图了。
最后来看一下客户端请求代码: