一、背景
秒杀一般都是定时上架的 很多人抢 不停的刷新
瞬间流量暴涨 商品瞬间卖完 要担心超卖问题
查询商品->创建订单->扣减库存->更新订单->付款->卖家发货 大部分过程短时间完成
二、隔离
2.1 业务隔离
秒杀活动只是网站营销的一个附加活动,这个活动具有时间短,并发访问量大的特点,如果和网站原有应用部署在一起,必然会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪。
独立部署 (集群隔离)
域名隔离
2.2 数据隔离
秒杀大部分使用的都是热数据,可以启用单独的cache集群 和 mysql 集群 数据还需要预热 提前用程序写到缓存里面
2.3 动静隔离
静态资源都放在CDN上
把整个页面Cache在用户浏览器 强制刷新整个页面,再请求到CDN (这样把90%的静态数据缓存在用户端或者CDN上,当真正秒杀时用户只需要点击特殊的按钮“刷新抢宝”即可,而不需要刷新整个页面)
只有动态请求才能打到服务器上
三、多级缓存
3.1 浏览器缓存
静态页面直接缓存到浏览器中
3.2 CDN缓存
静态资源放到CDN上 并申请扩大CDN带宽
3.3 nginx缓存
静态资源也可以缓存到nginx上
3.4 本地缓存
不用动态更新的商品信息 , 配置数据可以提前缓存到web服务内存中
3.5 Redis分布式缓存
商品库存, 商品信息 ,用户信息 等可以放到redis里面。
四、流量削峰
4.1 基于时间分片削峰
秒杀的流量走势在秒杀开始的时候会瞬间达到峰值 一柱擎天,对系统的压力很大 , 可以在点击秒杀的时候 加入验证码 答题等方式 强行平滑一波曲线。
4.2 基于队列削峰
可以吧请求放入到kafka等队列中,使用消费者控制消费速度。
五、限流熔断
5.1 Nginx 限流
可以针对IP进行漏桶限流 允许一定程度的并发
可以针对IP进行连接数的限流
5.2 Hystrix 限流
需要对下游的服务进行限流 否则可能会造成服务雪崩
对限流的流量可以直接做未秒杀成功的处理 反正他们也不知道
六、防止超卖
秒杀流量异常巨大,用数据库扣减库存(无论乐观锁、悲观锁) , 悲观锁会造成大量的事务阻塞在mysql,可能会导致整个系统不响应。乐观锁想需要获取一下版本号,然后大并发的更新同一行。虽然mysql 企业版提供了线程池排队插件(要钱的),但是依然无法满足好几万QPS的要求。
比较好的方法 库存保存在redis中,使用redis lua 脚本 + kafka落地mysql 。
为什么要用redis + lua ?
redis 速度快,lua 实现原子性。 下面代码不具备原子性。
int 库存 = redis.get(key);
if(库存 > 0){
redis.decr(key)
}
redis 需要开启AOF持久化 ,但是毕竟是异步复制,所以还是需要通过kafka 落地到mysql 里面的。
如果lua 脚本返回0 , 标识秒杀结束 , 修改内存中的标志位。 后续请求全部作废。
七、 优化
7.1 web容器优化
tomcat jetty 其实不太适合瞬时高并发应用, 可以使用undertow 基于NIO实现 吞吐量更高。
7.2 nginx 优化
链接数 , 线程数 可以配置的更高
7.3 模板引擎优化
一般秒杀页面的首页(其实里面内容很少,就css,js链接)需要模板引擎渲染,可以使用beetl ,
并且将渲染内容缓存。
八、安全性考虑
8.1 如何防止直接拿到下单URL?
为了避免用户直接访问下单页面URL,需要将改URL动态化,即使秒杀系统的开发者也无法在秒杀开始前访问下单页面的URL。办法是在下单页面URL加入由服务器端生成的随机数作为参数,在秒杀开始的时候才能得到。
8.2 如何控制秒杀按钮点亮?
使用js控制 ,js内容true、fasle,该js链接带随机版本号不被缓存,这个js很小 不会对造成影响。