高并发
秒杀环节会一瞬间涌入大量需求,主要思路就是削峰、限流、异步、补偿。
异步这里可以用消息队列来实现。将抢(即排队点进去跳转到wx或者zfb付款界面)还有购(就是真正支付的时候)分开,这样可以为mysql减压。
抢可以用redis来做,毕竟它轻量级,一秒能写入10w,并且可以集群提高扩展能力。可以先将库存数量加载到redis,然后在redis里扣减,扣减成功的消息传给消息队列。最后mysql从消息队列取消息来做真正的订单生产。
再说如何提升redis处理请求能力,假设我们预估一秒来的请求超过6w(毕竟要给redis留点余量,不能顶满),那可以考虑用多个redis分流。比如预计1s来了100w请求,那就把库存余额平均分20个redis,就可以处理了。然后来一个请求随机分给这20个里的任意一个,你分到的redis已经没余额就只能怪自己人品差了。
防止超卖
这个就是考察如何用redis保证原子性。可以判断余额和扣减库存两者写到一个Lua脚本里。这样的话有三种情况:
- 正常运行,发现库存用完了,那直接告诉用户没了就行
- 访问redis错误,也直接告诉用户让他刷新重试一下就行
- 访问redis超时(也就是网络错误之类的),其实这个时候可能已经扣完库存量了,不用重试了,因为是无效扣减,也就是说总数是不变的,只不过这一次扣减没让任何一个用户抢到东西而已,大不了少卖了。
尽可能避免少卖
除了超卖里第三条外,redis向消息队列发送信息出错误的话也会引起少卖现象。解决方案有三种:
- 发送给消息队列失败的话,会返回报错,只要增加渐进式重试机制就行(隔1s、5s、30s、1min…再重试一下),这种机制而不是固定时间轮询是因为失败一次大概率还会失败,所以节约cpu
- 更安全的方法,在第一种的基础上把消息记录在磁盘上
- 写磁盘也可能失败,但是再走WAL那套日志就和mysql的binlog、redolog之类差不多了,太复杂了没必要
所以实际上用第二种就行,大不了人工介入补偿一下,比如二次余量销售
Redis角色
redis能扮演扣减库存的角色主要是因为比sql处理能力强太多。实际上消息队列也可以只用redis代替,只不过不怎么可靠。后面补一下具体消息队列知识(kafka、rocketMQ之类的)