我看了二十篇左右的秒杀系统设计及解决方案的文章,从架构、产品、前端、后端四个层面分别总结了一些解决方案。html
1.架构:扩容,业务分离,数据分离前端
2.产品:下单按钮控制,秒杀答题削峰,简化页面设计redis
3.前端:限流(反做弊) 静态化sql
4.后端:内存 队列数据库
比较火热的秒杀在线人数都是10w起的,如此之高的在线人数对于网站架构从前到后都是一种考验。后端
任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每一个抢购活动都要面临的难题。浏览器
实际上超卖问题是高并发带来的一个子问题,可是由于这个问题太过致命,因此咱们把他的解决方案单独拿出来讲。缓存
解决办法:安全
一、将库存字段number字段设为unsigned,当库存为0时,由于字段不能为负数,将会返回false。服务器
二、采用数据库的悲观锁思路,select ...for update。见《MySQL锁之三:MySQL的共享锁与排它锁编码演示》
三、采用数据库的乐观锁思路,常见的就是大可能是基于数据版本( Version )记录机制实现。(即为数据增长一个版本标识,在基于数据库表的版本解决方案中,通常是经过为数据库表增长一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。)
四、采用Redis中的watch。见《Redis事务和watch》
五、采用FIFO队列思路。(直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,咱们就不会致使某些请求永远获取不到锁。看到这里,是否是有点强行将多线程变成单线程。由于请求不少,极可能一瞬间将队列内存“撑爆”)
六、采用文件锁的思路。单台物理机上是能够,分布式集群下不可行。见《NIO文件锁FileLock》
尽可能将请求拦截在系统上游
读多写少的经常使用多使用缓存
说白了加机器
为了不短期内的大访问量对现有网站业务形成的冲击,能够将秒杀系统独立部署。系统隔离更可能是运行时的隔离,能够经过分组部署的方式和另外99%分开。秒杀还申请了单独的域名,目的也是让请求落到不一样的集群中。即便秒杀系统崩溃了,也不会对网站形成影响。
将即将被秒杀的热数据维护到redis。秒杀所调用的数据大部分都是热数据,好比会启用单独cache集群或MySQL数据库来放热点数据,目前也是不想0.01%的数据影响另外99.99%。
购买按钮只有在秒杀开始的时候才能点亮,在此以前是灰色的,显示活动未开始。
秒杀答题一个很重要的目的是为了防止秒杀器。还有一个重要的功能,就是把峰值的下单请求给拉长了,从之前的1s以内延长到2~10s左右,请求峰值基于时间分片了,这个时间的分片对服务端处理并发很是重要,会减轻很大压力,另外因为请求的前后,靠后的请求天然也没有库存了,也根本到不了最后的下单步骤,因此真正的并发写就很是有限了。其实这种设计思路目前也很是广泛,如支付宝的“咻一咻”已及微信的摇一摇。
秒杀场景业务需求与通常购物不一样,用户更在乎的是可以抢到商品而不是用户体验。因此秒杀商品页面应尽量简单而且拍下后地址等我的信息应该使用默认信息,减轻秒杀进行时系统负载,如有更改能够在秒杀结束后进行更改。
将页面可以静态的部分都静态化,并将静态页面缓存于CDN,以及反向代理服务器,可能还要临时租借服务器。 利用 页面静态化、数据静态化,反向代理 等方法能够避免 带宽和sql压力 ,可是随之而来一个问题,页面抢单按钮也不会刷新了,能够把 js 文件单独放在js服务器上,由另一台服务器写 定时任务 来控制js 推送。 另外还有一个问题,js文件会被大部分浏览器缓存,咱们可使用xxx.js?v=随机数 的方式来避免js被缓存。
1.针对同一个用户id来实现,前端js控制一个客户端几秒以内只能发送同一个请求,后端校验同一个uid在几秒以内返回同一个页面
2.针对同一个ip来实现,进行ip检测,同一个ip几秒以内不发送请求或者只返回同一个页面
3.针对多用户多ip来实现,依靠数据分析
4.为了不用户直接访问下单页面URL,须要将改URL动态化,即便秒杀系统的开发者也没法在秒杀开始前访问下单页面的URL。办法是在下单页面URL加入由服务器端生成的随机数做为参数,在秒杀开始的时候才能获得。
若是系统发生“雪崩”,贸然重启服务,是没法解决问题的。最多见的现象是,启动起来后,马上挂掉。这个时候,最好在入口层将流量拒绝,而后再将重启。若是是redis/memcache这种服务也挂了,重启的时候须要注意“预热”,而且极可能须要比较长的时间。
秒杀和抢购的场景,流量每每是超乎咱们系统的准备和想象的。这个时候,过载保护是必要的。若是检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,可是,这种作法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回。
由于秒杀是典型的读多写少的场景,适合操做内存而非操做硬盘;缓存工具redis自己的操做是保证原子性的,因此能够保证请求了redis的写的操做的线程安全性。
将用户请求放置于一个或多个队列中,队列中元素总和等于该商品库存总和,未进入队列的请求均失败。利用多线程轮询分别从一个或多个队列中取出用户请求。操做redis进行减库存操做,成功减库存以后返回成功,并将用户信息与商品信息存入另外一个队列当中,进行生成订单的操做。利用两个队列异步处理业务减轻秒杀高峰时期服务器负载。
队列与缓存为了保证请求redis的次数不超过总的库存量,利用一个程序计数器来这一点。程序计数器用JUC包下原子类能够实现。
分布式状况下能够利用分布式锁来解决任务每次只能由一次服务来执行且不能重复执行。 分布式锁的实现:zk、redis 分布式锁的优化:先考虑是否能够去锁,而后考虑尽量多用乐观锁,少用悲观锁。这里有一个问题,乐观锁若是每一次都会有并发冲突的话性能反而不如悲观锁,那么难道真的多用乐观锁性能会比悲观锁高吗?选举考虑ha,好比心跳检测。
利用集群并发加入队列,选举队列处理服务单点执行,这样能够保证并发实现和加锁同样的并发量但不会影响性能。