1为什么这么难?
读写冲突,锁非常严重,这个业务最难的地方
2优化方向
2.1将请求尽量拦截在系统上游。
2.2热点数据隔离出来
2.3另外产品上做一些调整,但是会损失一些体验,加验证码,做题目等
2.4充分利用缓存,读多写少的应用场景
首先要做的是隔离
业务隔离。把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名,从技术上来说,卖家报名后对我们来说就是已知热点,当真正开始时我们可以提前做好预热。
系统隔离。系统隔离更多是运行时的隔离,可以通过分组部署的方式和另外99%分开。秒杀还申请了单独的域名,目的也是让请求落到不同的集群中。
数据隔离。秒杀所调用的数据大部分都是热数据,比如会启用单独cache集群或MySQL数据库来放热点数据,目前也是不想0.01%的数据影响另外99.99%。
按照业务执行流程,大致分为4个阶段:
第一个浏览器客户端层
第二个站点,理解为接受请求层
第三个服务层,处理核心业务
第四个数据层,存储数据层。
针对每一层,做一些优化性的内容。
3操作
3.1浏览器客户端层操作
3.1.1秒杀开始前的页面处理:
在秒杀开始前,通过不停刷新浏览器页面以保证不会错过秒杀,静态化
秒杀新增的网络带宽,必须和运营商重新购买或者租借。为了减轻网站服务器的压力,需要将秒杀商品页面缓存在CDN,同样需要和CDN服务商临时租借新增的出口带宽。
使用JavaScript脚本控制,在秒杀商品静态页面中加入一个JavaScript文件引用,该JavaScript文件中包含 秒杀开始标志为否;当秒杀开始的时候生成一个新的JavaScript文件(文件名保持不变,只是内容不一样),更新秒杀开始标志为是,加入下单页面的URL及随机数参数(这个随机数只会产生一个,即所有人看到的URL都是同一个,服务器端可以用redis这种分布式缓存服务器来保存随机数),并被用户浏览器加载,控制秒杀商品页面的展示。这个JavaScript文件的加载可以加上随机版本号(例如xx.js?v=32353823),这样就不会被浏览器、CDN和反向代理服务器缓存。
3.1.2实际请求地址秒杀开始后才到页面上:
每次的秒杀链接地址不同,只有在秒杀开始时候出来,在nginix层拒绝非法请求
3.1.3点击后按钮置灰,拒绝大部分小白用户的多次请求
3.1.4x秒之内只能提交一次请求
本机内存实现:(guava的漏桶原理,多机器如何限制)
1)加大限制(这是建议的方案,最简单)
2)在nginx层做7层均衡,让一个uid的请求尽量落到同一个机器上
3.2站点处理
页面处理:页面缓存,访问缓存页面,既能保证用户有良好的用户体验(没有返回404)又能保证系统的健壮性。
另外,页面本身跟其他网站的上的页面区分,不需要再渲染,模板,直接输出
3.3服务层
3.3.1防止超发:
1.悲观锁
2.FIFO.
并发队列的选择:
Java的并发包提供了三个常用的并发队列实现,分别是:ConcurrentLinkedQueue 、 LinkedBlockingQueue 和 ArrayBlockingQueue。
ArrayBlockingQueue是初始容量固定的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。
ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。
LinkedBlockingQueue也是阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。
由于我们的系统入队需求要远大于出队需求,一般不会出现队空的情况,所以我们可以选择ConcurrentLinkedQueue来作为我们的请求队列实现
3.乐观锁
乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。
3.3.2业务上分时段
部分数据展示的时候不需要细节值,直接有无即可
如:对于12306,余票查询这个业务,票剩了58张,还是26张,你真的关注么,其实我们只关心有票和无票
3.3.3业务系统拆分
下单业务与 支付业务的分离
3.3.4过载保护
如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。
秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回。
3.4数据
最终的库存是存在这里的,mysql是一个典型
设计:
1.秒杀未支付的,到点回仓
2.可以数据拆分;
3.秒杀活动很多,垂直拆分;