秒杀、高并发架构

什么是秒杀

秒杀场景一般会在电商网站举行一些活动或者节假日在12306网站上抢票时遇到。对于电商网站中一些稀缺或者特价商品,电商网站一般会在约定时间点对其进行限量销售,因为这些商品的特殊性,会吸引大量用户前来抢购,并且会在约定的时间点同时在秒杀页面进行抢购。

秒杀系统场景特点

  • 秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增。
  • 秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功。
  • 秒杀业务流程比较简单,一般就是下订单减库存。

秒杀架构设计理念

保障服务稳定的三大利器:熔断降级、服务限流和故障模拟。

限流 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。

削峰:对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。

异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。

内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。

可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了。像淘宝、京东等双十一活动时会增加大量机器应对交易高峰。

 

设计思路

    将请求拦截在系统上游,降低下游压力:秒杀系统特点是并发量极大,但实际秒杀成功的请求数量却很少,所以如果不在前端拦截很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时。 
    充分利用缓存:利用缓存可极大提高系统读写速度。 
    异步消息队列:消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。

前端方案

浏览器端(js):

页面静态化:缓存在网络、缓存在服务器本机、缓存在用户浏览器

1、CDN:将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。 

2、可以在服务器端使用freemarker,不需要占用服务器逻辑,但是会占用带宽

3、将服务器的页面用原生的Js、Jquery等写出来,并设置spring配置文件参数让浏览器端可以缓存页面,这样以后当下次请求到来的时候浏览器可以直接走自己的缓存,几乎不需要与服务器交互

       静态资源还可以进行优化:(tengine)

       1、JS/CSS压缩,减少流量(webpack专门用于打包前端资源)

       2、多个JS/CSS组合,减少连接数


禁止表单重复提交: 

1、js禁止提交按钮,用户提交之后按钮置灰,禁止重复提交

2、Post-Redirect-Get (PRG)模式

当用户提交了表单后,去执行一个客户端的重定向,转到提交成功信息页面。

这能避免用户按F5导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。

3、在session中存放一个特殊标志

服务端对用户生成一个标识符存入session,同时将它写入表单的隐藏字段中,用户录入信息后点击提交,在服务器端获取表单中隐藏字段的值,与session中的唯一标识符进行比较,相等说明是首次提交,处理完此次请求后删除session标识符,下次用户再提交则失败

4、使用header函数转向:用户点击表单后立即转向其他的页面,使页面脚本失效

5、在数据库中添加唯一性约束

6、使用cookie记录表单提交的状态(如果用户禁用cookie则不生效


    用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流

 

后端方案

服务端控制器层(网关层)

接口防刷方案:

1、限制uid(UserID)访问频率:我们上面拦截了浏览器访问的请求,但针对某些恶意攻击或其它插件,在服务端控制层需要针对同一个访问uid或者其url,限制访问频率。(可以在redis里面加一次几秒钟的key,其value就是用户可以访问的次数)

2、验证码、滑块、找不同

 

 

服务层

上面只拦截了一部分访问请求,当秒杀的用户量很大时,即使每个用户只有一个请求,到服务层的请求数量还是很大。比如我们有100W用户同时抢100台手机,服务层并发请求压力至少为100W。

  1. 采用消息队列缓存请求:既然服务层知道库存只有100台手机,那完全没有必要把100W个请求都传递到数据库啊,那么可以先把这些请求都写到消息队列缓存一下,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
  2. 利用缓存应对读请求:对类似于12306等购票业务,是典型的读多写少业务,大部分请求是查询请求,所以可以利用缓存分担数据库压力。
  3. 利用缓存应对写请求:缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。

数据库层

数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧

 

重启与过载保护

如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回。

 

 

 

https://www.cnblogs.com/andy-zhou/p/5364136.html

架构上:

1、独立部署秒杀系统:将秒杀系统独立部署,甚至使用独立域名,使其与网站完全隔离——防止高并发导致整个网站瘫痪

2、页面静态化:重新设计秒杀商品页面,不使用网站原来的商品详细页面,页面内容静态化,用户请求不需要经过应用服务,由反向代理指引至静态文件处——防止用户刷新页面导致的流量过多(服务器上freemarker、网络上CDN、用户浏览器上spring)

3、CDN缓存:将秒杀商品页面缓存在CDN,和CDN服务商临时租借新增的出口带宽——防止系统带宽不够

4、合理设置下单链接:

1、将下单页面URL动态化:是在下单页面URL加入由服务器端生成的随机数作为参数,在秒杀开始的时候才能得到,这个随机数只生成一次——避免用户直接访问下单页面URL

2、在redis中设置一个key,设置过期时间为倒计时时间,而下单的逻辑只有当检测到redis过期才有效,这样便可防止提前下单。

5、js文件设置购买按钮:购买按钮在秒杀开始时才能点击。如果页面是动态生成的,可以在服务端构造响应页面输出,控制按钮状态,但是因为要使用上面所说的CDN、静态页面与反向代理等,所以在静态页面中加入一个JS文件引用,其包含秒杀开始标志为false,当秒杀开始由后端系统生成一个相同名字的JS文件,其开始标志为true,并带有下单页面的URL及服务端生成的随机数(第4点),js文件带有随机版本号,防止被用户浏览器、CDN和反向代理服务器缓存,而且js文件特别小,所以不会对系统造成太大压力。同时,为了防止用户重复提交请求,设置用户在x秒内只能提交一次请求。

如果是带有倒计时的按钮:

为了防止js调用客户端时间出现的时间不一致,可以将web服务器的时间发给客户端,由于不涉及后端逻辑,因此速度很快,同时可以优化http请求使得数据量很小

为了防止集群上的不同web服务器时间不一致,可以每隔一分钟让所有参与活动的web服务器就与时间服务器做一次时间同步。

6、提高并发量并防止超卖:数据库乐观锁,秒杀系统集群,查询走redis,更新用异步kafka方式写数据库再设置redis

       1、防止超卖1:在数据库上设置一个版本字段,用于实现乐观锁(吞吐量高,性能低);或者在数据库+唯一索引+判断库存>0

       2、防止超卖2:每次更新数据库的时候都获取表锁(select for update)或行锁,实现悲观锁(吞吐量低,性能高)

       3、防止超卖3:在memcache或redis中设置库存计数器,每次成功下单-1,直到为0禁止购买,最后再统一修改库存

       4、防止超卖4:不在数据库加锁,而在redis加锁,当用户下单某个产品时,将产品id存储到redis里面,其他用户下单时发现redis有这个key,就禁止下单

       5、为了进一步提高秒杀时的吞吐量以及响应效率,对服务进行了集群,使用nginx做负载均衡

       6、为了防止连接数据库的请求过多导致数据库奔溃,使用分布式限流,将并发控制在一个可控的范围之内,为了防止过多的select查询库存,可以使用redis缓存,每次查询库存时走redis,扣库存时更新redis(需要提前将库存信息写入redis)

       7、利用同步转异步来提高性能,将写订单以及更新库存的操作进行异步化,利用kafka来进行解耦和队列的作用:每当一个请求通过了限流到达了Service层通过了库存校验之后就将订单信息发给Kafka,这样一个请求就可以直接返回了(减少占用系统并发资源),异步之后需要通过回调或其他方式提醒用户购买完成

7、防止黑客(1%的用户):用户使用工具或for循环调用后端的http请求

       1)同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面

2)同一个item的查询,例如某款手机页面,做页面缓存,x秒内到达站点层的请求,均返回同一页面

3)限制指定IP的请求频率(容易出现误判,比如多人宿舍用一个wifi)

4)弹出验证码(防止自动化脚本)

5)多个账户、不同IP发送不同请求

       对于第五点,说实话,这种场景下的请求,和真实用户的行为,已经基本相同了,想做分辨很困难。再做进一步的限制很容易“误伤“真实用户,这个时候,通常只能通过设置业务门槛高来限制这种请求了,或者通过账号行为的”数据挖掘“来提前清理掉它们。

 

服务层

ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。可以用在用户请求模块,存储请求

ArrayBlockingQueue是初始容量固定的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。

 

数据库方面(库切分、主从复制、双主互备、双主当主从(master-slave))

1、为了解决数据量太大的问题:加入分片机制(水平、垂直拆分)

 

2、为了解决可用性问题:加入分组机制(多个一样的数据库,主从复制)

目前很多公司采用的是写主数据库,而读从库,数据库做主从复制,读一般有多个,而写只有一个,有单点故障问题(故障后可以通过选举的机制选一个从库当主库)

如果是有一个用户发送大量的请求过来,而我们的逻辑是一个用户只能请求一次,这时判断逻辑是:读库上判断用户请求过,进而放行,写入数据库,而写数据库将数据同步到从库需要点时间,而这时间用户的其他请求仍然在从库得到用户可以放行的结果(可以采用redis缓存标志位,也可以采用将用户的请求放入队列,让队列一个一个处理而不是并行处理)

3、为了保证写数据库的高可用:

1.冗余写库,采用双主互备的方式

存在自增id冲突的问题,有两种解决方案:

1)两个写库使用不同的初始值,相同的步长来增加id:1写库的id为0,2,4,6…;2写库的id为1,3,5,7…;

2)不使用数据的id,业务层自己生成唯一的id,保证数据不冲突;

       2. 双主当主从用

有一个主提供服务(读+写),另一个主是“shadow-master”,只用来保证高可用,平时不提供服务。 master挂了,shadow-master顶上(vip漂移,对业务层透明,不需要人工介入)

好处:读写高可用且没有延迟

不足:无法扩展读性能,资源利用率为50%

 

 

 

综上:

提高并发量/秒杀手段:

       1、设置静态页面:CDN缓存(网络)、freemarker缓存(服务器)、spring设置缓存(浏览器)

       2、设置缓存减少数据库访问:根据商品ID信息设置缓存

       3、设置缓存防止用户刷接口:根据IP/userID+productId作为redis的key来限制用户的访问次数,或者添加验证码减低接口访问速度

       4、针对秒杀:可以将秒杀服务单独部署到服务器,同时租赁云服务器,防止影响其他业务

       5、其他服务可以使用服务降级降低带宽占用

       6、同步转异步:使用MQ来处理高并发的秒杀请求、MQ满了给用户显示等待中、库存扣完了提示用户卖完了

       7、防止超卖:数据库悲观/乐观锁/redis商品id锁/redis扣库存再减数据库

       8、防止表单重复提交:js设置点击一次后变灰、服务器设置session状态、浏览器用cookie记录访问次数。数据库设置唯一字段……

       9、防止用户提前获得下单链接:服务器redis标记秒杀开始是否开始、秒杀开始时由服务器发送一个随机数添加到url使得秒杀链接有效

       10、用户界面显示倒计时:由服务端将时间发送客户端,服务端之间设置一个时间服务器,其他服务器定时与其同步时间

       11、限流:计数器、漏桶、令牌桶

 

 

参考(部分转载)链接:https://blog.csdn.net/suifeng3051/article/details/52607544 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值