秒杀项目的重要问题
核心问题
- 每秒上万次请求
- 数据一致性(超买超买复购)
- 响应速度(用户反馈)
每秒上万次请求的后果
短时间内,上万个数据库连接同时达到,数据库崩溃。数据库处理大量请求,如果一个IO耗时100ms,大量的请求冲进数据库,响应速度将十分缓慢。
限流策略
通常秒杀商品数量远远小于参与用户,而且用户在秒杀时,可能会大量点击,可能产生大量请求。因此要限制大部分流量。只允许小部分流量进入后端处理流程。
- 为了应对用户刷接口首先就要避免暴露秒杀接口。如果用户知道了接口url,就可以通过脚本大量一秒发几百次请求,非常恐怖。前端,限制用户点击,如果一个用户点了按钮,那么按钮变灰或者5s内不能再点。
- 反向代理层,Nginx限流,通过limit_req_zone、limit_req指令配合完成,限制单ip、单服务的流量,甚至可限制goodsId限流。
- 可通过Redis计数器或令牌桶策略限制单个用户访问接口的次数,请求没有抢到令牌则拒绝后续流程;在前端,通过按钮置灰、控制用户点击次数的方法减少用户大量发请求。
- 利用缓存。通过缓存避免到数据库读取数据。如将商品信息缓存,减少不必要的读请求。为了避免抢购前都到db中查询库存,还可以通过预减缓存。比如:在Redis中预设置秒杀商品库存,请求进入后端,首先用decr预减库存。若结果<0,则库存耗尽,服务器直接返回秒杀失败结果。此外,秒杀页面适合做成静态页面,提高加载速度。
- 消息队列将数据库读写异步化。Redis并发量远远大于Mysql,就算用了前面的各种策略,也还是会有大量请求冲进来压垮数据库。因此,可以将大量读写请求异步化,这里用到了消息队列。
消息队列可作为缓冲区,将写请求缓存起来,避免请求直接涌向数据库。异步处理:将生成订单、扣库存等流程分开,提高处理效率,及时给用户反馈(避免用户界面一直转圈);
超卖超买的问题
使用Mysql中的锁
- 行锁、表锁
事务开始时,执行select ... for update加行锁,注意,行锁必须依赖索引!如果不用索引,就会锁住全表。此外,不能锁同一个索引。select ... where goodID = 12345 for update
- 事务隔离级别:串行化(并发量很低)
分布式锁
Redis分布式锁或Zookeeper分布式做。Redis分布式锁性能更高。
请求排队处理
使用消息队列结合分布式锁保证数据一致性
对于分布式项目
- 应对高并发:负载均衡,节点水平拓展,数据库读写分离,增加示例数量
- 使用Sentinel限流熔断
- 分阶段顺序执行先在redis预减库存,Redis的吞吐量远远大于DB,decr具有原子性,能够安全、准确的减库存。预减库存后进入消息队列,这时候可通过分布式锁。比如根据商品id对应一个分布式锁,同时刻同一个商品只允许一个服务进行修改。