秒杀系统-情报背景本篇内容完全是笔者自己对技术分析和总结沉淀,由于笔者技术和能力有限,如果有不对的地方,还望大家多多见谅和包涵,并且多多指正留言,谢谢。
相信大家都接触过新浪微博、淘宝、京东等等这些访问量较为巨大的平台以及网站,针对于“高流量”、“高并发”来讲,更是我们【技术开发者】都要面临的的一个很难的“包袱”难题。哎,看来如果要在这行混下去,即使你可能没有接触高并发场景,也要自己创造“高并发”进行迎难而上,因为只有这样子我们才可以更进一步啊!
秒杀系统-情报介绍对于今天我们要介绍的内容就属于高并发的一个最极端的场景之一:“秒杀”,这个名词一般会在“大促”的时候出现,当然也会在某些平台活动上出现,那么肯定会有小伙伴会说,秒杀系统要注意哪些问题啊!为啥会比较难呢,难在哪里啊!
秒杀系统- 特点分析-
瞬时剧增:在某一个时刻开始进入流量(很少会有热身以及缓慢增长机制),秒杀时大量用户会在同一时间,抢购同一商品,网站瞬时流量激增。
-
僧多粥少:商品的库存是有限的,秒杀请求下的订单数量会远远大于库存数量,只有少部分用户能够秒杀成功。
-
资源锁定:秒杀业务流程比较简单,一般就是下订单减库存。库存就是用户争夺的“资源”,实际被消费的“资源”不能超过计划要售出的“资源”,也就是不能被“超卖”。
它的难度就在于要完成一个“60-100 分”的秒杀系统,那么它必须要要至少兼顾以下这三个方面,才算合格,这三个“恶魔”分别叫“服务可用性”、“数据一致性”和“快速响应性”,有点“苛刻”!
在我们现在的场景下,很难再去考虑一个非分布式系统的架构了。(分布式架构)相信大家都知道 CAP 理论吧!没事不知道也没关系,可见内容:
CAP 理论又称 CAP 定理,它说的是在一个分布式系统中,服务(数据)层面的一致性(Consistency)、服务自身的可用性(Availability)、网络不同节点分区容错性(Partition tolerance)。
A 和 C 相信大家从字面上都可以理解了,这里要声明一下比较陌生的 P:它代表如果要保证不同的节点即使在网络出现问题的时候仍能够访问到数据,那么最直接的办法就是冗余赋值节点,否则一切都是空谈,所以作为一个分布式系统而言,无法忽略 P,我们可以理解它就是 A 和 C 的基础。
CAP 体系总结-
只保证 AC 就是一个单体应用,根本不是分布式。意义当然有,在分布式出现之前都是这么搭系统。倘若这个系统的节点之一挂了,不会发生脑裂而是整个系统直接宕掉。
-
进一步说如果网络中存在的节点越多,分区容忍性越高,但要复制更新的数据就越多,一致性就越难保证。
-
为了保证一致性,更新所有节点数据所需要的时间就越长,可用性就会降低。
服务可用性,是在于高并发流量的冲击下,仍然可以保持服务的可用性并且还要保证一直可以输出对外界的服务能力,不会造成宕机以及资源损坏,即使在内存和网络甚至硬件资源有限的情况下,也不会被击垮“死亡”。
数据的一致性都知道,我们开发的程序以及现在多数的服务器,比如数据库,他们在处理数据的时候,很有可能会存在多个线程同时在修改同一行数据或者同一块内存,在 Java 角度而言本身也会存在不一致的问题,而在程序和中间件的角度而言,也是一样,会出现同一时刻在数据修改顺序的乱序化,以及数据的紊乱,造成数据的重复 *** 作,造成与我们预期的设想不同。
-
除非你可以实现串行化,一条一条处理,不让它们同一时刻就行修改或者 *** 作数据,这个是最本质且最安全的办法,但是也是最影响性能的办法。(悲观锁、同步队列)。
-
此外还有一种办法就是,时时刻刻在原子层级,也就是最接近底层的计算机修改数据的时候,或者在所有节点之间建立一个应用层级的中间汇总干路点(redis 或者 database 的主干点),上面加入写屏障和读屏障,在修改之前,在进行一次校验判断,如果数据与预期不同,就不进行修改。这就是著名的乐观锁!
一般来讲这个属于用户体验,一个较为合格的秒杀系统,是不应该让用户漫长的等待最好尽可能快速反馈结果。要做成快速响应,就不需要是异步返回,直接快速响应。此外还需要尽快帮助用户计算数据,直接返回。
秒杀系统-架构设计我们将秒杀架构进行一下划分,大体分为三个层级进行分析:由外到内进行分析,分别是:应用层、服务层、数据访问层。
应用层架构设计 动静分离+CDN 技术动静分离分析
-
场景分析:在秒杀活动开启之前,用户一般都会尝试不断的刷新浏览器页面(俗称 F5)以保证不会错过秒杀活动的商品。
-
按照常用的网站应用架构:
-
我们假设,如果这些无用的请求,频繁的冲击我们的后台服务器,比如说经过:Web 服务器(LVS、Nginx 等)->应用服务器(tomcat 或者 Jetty 等)、连接数据库(MySQL),者无疑会对后端服务以及服务器造成非常大的压力。
-
解决方案:重新设计秒杀商品页面,不使用网站原来的商品详细页面,页面内容静态化,减少/隔绝无用的请求经过后端服务。
CDN 技术分析
增加网络带宽
网站的静态页面数据大小 100K,那么需要的网络和服务器带宽是 2G(100K×10000),这些网络带宽是因为秒杀活动新增的,超过网站平时使用的带宽。
即使将动态业务转换为静态化页面,但是秒杀活动会非常剧烈的增加的网络带宽的消耗,同时并不会减轻前端网站服务器的压力,所以如果可以的话,需要再进一步将秒杀商品页面缓存在 CDN,而不在是单纯的我们的前端 Nginx 服务器层面,所以需要和 CDN 服务商临时租借新增的出口带宽。
阻断缓存页面
在页面中加入一个 Javascript 文件引用(进行传递随机号+状态位),该 Javascript 文件
中包含秒杀开始标志为否;
-
当秒杀开始的时候生成一个新的 Javascript 文件(文件名保持不变,只是内容不一样),更新秒杀开始标志为是,加入下单页面的 URL 及随机数参数(这个随机数只会产生一个,即所有人看到的 URL 都是同一个,服务器端可以用 redis 这种分布式缓存服务器来保存随机数),并被用户浏览器加载,控制秒杀商品页面的展示。
-
这个 Javascript 文件的加载可以加上随机版本号(例如 xx.js?v=32353823),这样就不会被浏览器、CDN 和反向代理服务器缓存。
-
这个 Javascript 文件非常小,即使每次浏览器刷新都访问 Javascript 文件服务器也不会对服务器集群和网络带宽造成太大压力。
根据 ID 限制频率
为了控制公平性原则,由于黄牛或者一些黑客达人,会采用”高科技“,比如说,采用爬虫脚本 *** 作,疯狂的去刷新页面。为了防止一些人的破坏以及公平分散,所以采用同一个标准去控制 UID(用户 ID)去访问频率信息,当超过每个人所需要达到的频率阈值,就要进行限制互动窗口内能够访问刷新的数据量!
例如:可以用 Redis 给每个用户做访问统计,根据用户的 ID 和商品的标识双方面进行对用户对某一个商品的访问频率控制,超过访问频率后,就会将他的请求暂时性熔断。
负载均衡
秒杀系统必然是一个集群系统,在硬件不提升的情况下利用 nginx 做负载均衡也是不错的选择。
负载均衡(Load Balance)是集群技术(Cluster)的一种应用,可以将工作任务分摊到多个处理单元,从而提高并发处理能力,有利于提升中大型网站的性能。需要使用服务集群和水平扩展,让“高峰”请求分流到不同的服务器进行处理。
http 协议负载均衡
根据用户的 http 请求的 DNAT 计算出一个真实的 web 服务器地址,并将该 web 服务器地址写入 http 重定向响应中返回给浏览器,由浏览器重新进行访问。该方式比较简单,但性能较差。
DNS 解析负载均衡
DNS 服务器上配置多个域名对应 IP 的记录。该方式直接将负载均衡的工作交给了 DNS,为网站管理维护省掉了很多麻烦,访问速度快,有效改善性能。
反向代理负载均衡
反向代理服务器在提供负载均衡功能的同时,管理着一组 web 服务器,根据负载均衡算法将请求的浏览器访问转发到不同的 web 服务器处理,处理结果经过反向服务器返回给浏览器。
网络层 IP 负载均衡
网络层通过修改目标地址进行负载均衡,该方式在响应请求时速度较反向服务器负载均衡要快,但是,当请求数据较大(大型视频或文件)时,速度反应就会变慢。
MAC 层负载均衡
数据链路层修改 Mac 地址进行负载均衡,负载均衡服务器的 IP 和它所管理的 web 服务群的虚拟 IP 一致。它不需要负载均衡服务器进行地址的转换,但是对负载均衡服务器的网卡带宽要求较高。
硬件负载均衡
F5 的全称是 F5-BIG-IP-GTM,硬件负载均衡设备,其并发能力达到。该方式能够实现多链路的负载均衡和冗余,可以接入多条 ISP 链路,在链路之间实现负载均衡和高可用。
服务层架构设计 降速机制即使我们扩展再多的应用服务,使用再多应用服务器,部署再多的负载均衡器,都会遇到支撑不住海量请求的时候。
排队处理
排队处理就像我们日常买东西排队一样,将请求放入队列中的,采用 FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,有一些将多线程处理方式变成单线程处理机制,会大大影响数据的效率和性能!
阻塞队列
-
ArrayBlockingQueue 是初始容量固定的阻塞队列。
-
ConcurrentlinkedQueue 使用的是 CAS 原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。
-
linkedBlockingQueue 也是阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。
分批放行
在同步排队的基础上,可以再加入一个分批放行执行机制,我们可以考虑达到预定阈值以后,在进行相关的执行后端服务,这样子可以提高一定的性能以及减少后端请求的次数和压力,如下图所示:
利用缓存和队列技术减轻应用处理的压力,通过异步请求的方式做到最终一致性。
限流机制漏桶算法
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
-
设定漏桶流出速度及漏桶的总容量,在请求到达时判断当前漏桶容量是否已满,不满则可将请求存入桶中,否则抛弃请求。
-
采用一个线程以设定的速率取出请求进行处理。
算法弊端
-
只能以特定速率处理请求,如果速率设置太小则会浪费性能资源,设置太大则会造成资源不足。
-
无论输入速率如何波动,均不会体现在服务端,即使资源有空余,对于突发请求也无法及时处理,故对有突发请求处理需求时,不宜选择该方法。
令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
实现原理
设定令牌桶中添加令牌的速率,并且设置桶中最大可存储的令牌,当请求到达时,向桶中请求令牌(根据应用需求,可能为 1 个或多个),若令牌数量满足要求,则删除对应数量的令牌并通过当前请求,若桶中令牌数不足则触发限流规则。
为解决固定窗口计数带来的周期切换处流量突发问题,可以使用滑动窗口计数。滑动窗口计算本质上也是固定窗口计数,区别在于将计数周期进行细化。
滑动窗口
滑动窗口计数法与固定窗口计数法相比较,除了计数周期 T 及周期内最大访问(调用)数 N 两个参数,增加一个参数 M,用于设置周期 T 内的滑动窗口数。
数据访问层由于要承受高并发,mysql 在高并发情况下的性能下降尤其严重。
数据更新点需要针对于数据更新点进行控制!
悲观锁
可以从“悲观锁”的方向
-
悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。
-
虽然它解决了线程安全的问题,但是“高并发”场景下,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。
-
这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。
乐观锁
讨论一下“乐观锁”的思路了,常有缓存乐观锁、数据库乐观锁。(判断更新行数是否>0)制
-
乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新/通过状态化进行更新 *** 作机制。
-
所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。
-
不需要考虑队列的问题,不过,它会增大 CPU 的计算开销。但是在冲突较小的时候,这是一个比较好的解决方案。
接下来,会有姊妹篇【「绝密档案」“爆料”完整秒杀架构的设计到技术关键点的“八卦资料”】需要的扫码加微信领取:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)