对于秒杀活动来说,要求系统不会出现压力过大而崩溃的场景,并且不会出现超卖、少卖的情形。
所以秒杀系统中我们需要思考:
- “系统如何扛住高并发请求系统如何保证不超卖等问题”
对此我的解决思路是:
- “服务端中,使用缓存减少对数据库访问将请求流量拦截在上游,可以使用限流技术使用分布式队列进行流量削峰,将并发串行化”
秒杀架构图
秒杀架构图如上。
客户端
用户发起请求的端口,目前电商项目秒杀活动主要客户端有微信小程序、H5(浏览器)、各平台app(比如Android、iOS、Windows)。我们可以在客户端实现限流机制,这样就可以避免用户发送大量请求到服务端。
客户端的限流可以控制按钮的点击频率,比如对按钮置灰。
反向代理
我们可以使用Nginx实现请求分流,通过负载均衡将请求均匀的分布到不同的Web节点中。
Nginx也可以作为限流使用。Nginx可以控制单位时间内的请求数,限制同一时间的连接数。
API网关
如果实际参与秒杀活动的用户非常大,并发请求非常大。我们就需要在API网关这一层中进行限流,这里可以实现对单个Web节点实现每秒最大请求数限制。
我们也可以控制每个用户的最大请求数,通过Redis记录每个用户的请求数。
缓存
在服务层业务中,为减少对数据库的访问,需要进行缓存设计,我们可以使用本地缓存,或者分布式缓存。
队列
队列主要是用来实现流量的削峰填谷,我们可以使用RocketMQ、Kafka等消息中间件作为分布式的队列。
关于限流
秒杀系统中为什么需要限流?在秒杀活动中商品库存是有限的,而请求的用户数量远远大于商品库存数量。大部分的用户请求实际上是无法抢到商品的无效流量。所以这部分流量可以拦截在上游进行限流。
为了避免用户采用脚本或者频繁点击发送大量请求,而导致其他用户无法正常参加活动,我们也需要控制用户每秒的请求量。
我们可从URI和用户两个方面实行限流。
URI限流示例代码:
用户维度的限流示例代码:
我们获取URI维度的限流器对象uriLimiter,和用户维度的限流器对象userLimiter。如果tryAcquire()成功获得许可,则请求通过,进入后续业务。否则直接报异常,前端界面作相应的友好提示。
在缓存中扣减库存
使用Redis来存储库存数量,当用户发起抢购请求时,先判断Redis中的库存是否可用。如果可用,将抢购请求放入分布式队列中,采用异步方式处理后续操作,并完成下单。同时在Redis中作库存扣减。
示例代码如下:
先做库存扣减并获取扣减后的库存数量,如果库存数量大于或等于0,将订单创建请求发送到mq。否则返回抢购失败的信息。
消费者创建订单:
关于如何初始化库存?
在抢购活动开始前,有运营人员在后台手动将商品库存从数据库同步到缓存中。库存的扣减在缓存中进行扣减。
利用Redis单线程特性可以实现多线程下安全的库存更新。如果查询缓存中的值时,发现没有,需要从数据库中查询,然后再同步到缓存中,这个过程需要加锁。
示例代码如下:
总结
秒杀设计是典型的高并发系统,关于秒杀系统的设计,经常会在面试中被问到。在设计秒杀系统时,我们需要考虑:
- “请求分流-通过负载均衡实现限流-采用漏斗算法(如guava的RateLimiter),信号量Semaphore,Redis计数等实现Redis扣减库存-减少对数据库的访问流量削峰-分布式队列实现”