秒杀/抢购系统设计优化
12306抢票,票是有限的,库存一份,瞬时流量非常多,都读相同的库存,读写冲突,锁非常严重;
小米手机每周二的秒杀,可能手机只有1万部,但瞬时进入的流量可能是几百几千万;
这是秒杀业务难的地方。那我们怎么优化秒杀系统呢?
一、难点
(1)高并发
用户在秒杀开始前,通过不停刷新浏览器页面以保证不会错过秒杀,这些请求如果按照一般的网站应用架构,访问应用服务器、连接数据库,会对应用服务器和数据库服务器造成负载压力。
(2)超卖
由于库存并发更新的问题,导致在实际库存已经不足的情况下,库存依然在减,导致卖家的商品卖得件数超过秒杀的预期。
二、架构
常见的站点架构基本是这样的:
(1)浏览器端,最上层,会执行到一些JS代码
(2)站点层,这一层会访问后端数据,拼HTML页面返回给浏览器
(3)服务层,向上游屏蔽底层数据细节,提供数据访问
(4)数据层,最终的库存是存在这里的
三、思路
(1)将请求尽量拦截在上游:传统秒杀系统之所以挂,请求都压倒了后端数据层,数据库读写锁冲突严重,导致响应慢,下单基本不能成功
(2)利用缓存:这是一个典型的读多写少的应用场景,非常适合使用缓存
四、解决方案
(1)浏览器层请求拦截
1.产品层面,用户点击“查询”或“购票”后,按钮置灰,禁止用户重复提交请求
2.js层面,限制用户在n秒之内只能提交一次请求
(2)站点层请求拦截与页面缓存
1.静态化,将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素
2.限频率,同一个UID,限制访问频率,做页面缓存,n秒内到达站点层的请求,均返回同一页面
(3)服务层请求拦截与数据缓存
1.对于写请求,将所有写请求在缓存(Redis或Memcached)中,做请求单队列排队,每次只透过有限的写请求异步写入到数据层,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”
2.对于读请求,用Redis或Memcached
缓存写性能和读性能都远高于MySQL,只有非常少的写和读缓存的请求会透到数据层去
(4)数据层
1.尝试扣减库存,扣减库存成功才会进行下单逻辑(由于MySQL事务的特性,不可能完全避免超卖)
UPDATE table_name SET n=n-1 WHERE n>1;
2.扣减库存后进行检查,保证减完不能等于负数
查看更多:
开发一个微信小程序实例教程
HTTP协议整理
PHP安全之Web攻击
MySQL优化
Linux下常见的IO模型
参考资料:
https://my.oschina.net/xianggao/blog/524943
http://www.infoq.com/cn/articles/flash-deal-architecture-optimization