目录
1.什么是高并发
2.术语
3.如何应对处理高并发
3.1.提升系统的并发能力
3.3.1.垂直扩展
3.3.2.水平扩展
3.2.流量控制
4.削峰
4.1.怎样来实现流量削峰方案
4.2.限流
5.总结
1.什么是高并发
高并发是指系统在同一时间内处理大量请求的能力。在软件开发中,为了应对高并发的情况,通常需要进行系统架构设计、代码优化、缓存策略等方面的优化。
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
如何应对处理高并发:
初期的并发解决方案
系统或服务器级别的解决方案
1)增大服务器的CPU。
2)增加内存条。
3)增加硬盘个数,对硬盘做Raid5。
4)换掉免费的Tomcat,使用商用weblogic(美国Oracle公司出品的)
5)增加到二块网卡。
6)聘请系统架构师优化Linux内核
7)甚至花高价直接购买高性能服务器
随着业务的不断增加,服务器性能很快又到达瓶颈
2.应用级别的解决方案
1)网页HTML 静态化(需要CMS项目支持)
2)图片服务器分离(常用解决方案)
3)缓存(常用解决方案) 上上策为分布式缓存
4)镜像(下载较多)
随着业务的不断增加,服务器性能很快又到达瓶颈
由上面的问题引出终极解决方案—— 负载均衡
由于目前现有网络的各个核心部分随着业务量的提高,访问量和数据流量的快速增长,其处理能力和计算强度也相应地增大,使得单一的服务器设备根本无法承担。在此情况下,如果扔掉现有设备去做大量的硬件升级,这样将造成现有资源的浪费,而且如果再面临下一次业务量的提升时,这又将导致再一次硬件升级的高额成本投入,甚至性能再卓越的设备也不能满足当前业务量增长的需求。
针对此情况而衍生出来的一种廉价有效透明的方法以扩展现有网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性的技术就是负载均衡(Load Balance)。
负载均衡的功能:
-
转发请求
-
故障移除
-
恢复添加
负载均衡种类
1)一种是通过硬件来进行解决,常见的硬件有NetScaler、F5、Radware和Array等商用的负载均衡器,但是它们是比较昂贵的
2)一种是通过软件来进行解决的,常见的软件有LVS、Nginx、apache等,它们是基于Linux系统并且开源的负载均衡策略
数据库出现瓶颈怎么办呢?
以Mysql为例:
-
对Mysql进行优化(重点讲解)
-
缓存,主流缓存Memcached,redis…
-
mysql读写分离 + 主从复制
-
Oracle
-
Oracle读写分离 + 主从复制
-
Oracle Partition 分区
-
Oracle RAC集群(终级解决方案)此方案:非常贵,即使是淘宝,京东这样的大公司,也是很难受的。
解决方案有:MySql 主从复制与读写分离
1)提升系统的高并发的能力;
2)流量控制:通过分流,削峰、限流等业务、技术手段分散流量;
2.术语
响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。
吞吐量:单位时间内处理的请求数量。TPS、QPS都是吞吐量的常用量化指标
QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
并发用户数:同时承载正常使用系统功能的用户数量。这个数值可以分析机器1s内的访问日志数量来得到
3.如何应对处理高并发
3.1.提升系统的并发能力
联网分布式架构设计,提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。
3.3.1.垂直扩展
垂直扩展:提升单机处理能力。垂直扩展的方式又有两种:
(1)增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G;
(2)提升单机架构性能,例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间
-
CPU 从 32 位提升为 64 位
-
内存从 64GB 提升为 256GB(比如缓存服务器)
-
磁盘从 HDD(Hard Disk Drive)提升为 SSD(固态硬盘(Solid State Drives)),有大量读写的 应用
-
磁盘扩容,1TB 扩展到 2TB,比如文件系统
-
千兆网卡提升为万兆网卡
CDN 缓存
-
CDN
的全称是Content Delivery Network
,即内容分发网络。CDN
是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN
的关键技术主要有内容存储和分发技术。 -
CDN
它本身也是一个缓存,它把后端应用的数据缓存起来,用户要访问的时候,直接从CDN
上获取,不需要走后端的Nginx
,以及具体应用服务器Tomcat
,它的作用主要是加速数据的传输,也提高稳定性,如果从CDN
上没有获取到数据,再走后端的Nginx
缓存,Nginx
上也没有,则走后端的应用服务器,CDN
主要缓存静态资
2.3 应用缓存
2.3.1 内存缓存
在内存中缓存数据,效率高,速度快,应用重启缓存丢失
2.3.2 磁盘缓存
在磁盘缓存数据,读取效率跟内存比较,磁盘缓存稍低,但应用重启缓存不会丢失
代码组件:
-
Guava
-
Ehcache
服务器组件:
-
Redis
-
MemCache
2.4 多级缓存
在整个应用系统的不同层级进行数据的缓存,多层次缓存,来提升访问效率;比如:浏览器 -> CDN -> Nginx -> Redis -> DB (磁盘、文件系统)
2.5 缓存的使用场景
-
经常需要读取的数据
-
频繁访问的数据
-
热点数据缓存
-
IO 瓶颈数据
-
计算昂贵的数据
-
无需实时更新的数据
-
缓存的目的是减少对后端服务的访问,降低后端服务的压力
3 集群
-
有一个单体应用,当访问流量很大无法支撑,那么可以集群部署,也叫单体应用「水平扩容」,原来通过部署一台服务器提供服务,现在就多部署几台,那么服务的能力就会提升。
-
部署了多台服务器,但是用户访问入口只能是一个,比如
www.evanshare.com
,所以就需要 「负载均衡」,负载均衡是应用集群扩容后的必须步骤,集群部署后,用户的会话session
状态 要保持的话,就需要实现session
共享。
4 拆分
4.1 应用拆分
应用的拆分:分布式 (微服务)
单体应用,随着业务的发展,应用功能的增加,单体应用就逐步变得非常庞大,很多人 维护这么一个系统,开发、测试、上线都会造成很大问题,比如代码冲突,代码重复,逻辑错综混乱,代码逻辑复杂度增加,响应新需求的速度降低,隐藏的风险增大,所以需要按照 「业务维度进行应用拆分,采用分布式开发」;
应用拆分之后,就将原来在同一进程里的调用变成了远程方法调用,此时就需要使用到 一些「远程调用技」 httpClient
、hessian
、dubbo
、webservice
等;
随着业务复杂度增加,我们需要采用一些开源方案进行开发,提升开发和维护效率,比 如 Dubbo
、SpringCloud
;
通过应用拆分之后,扩容就变得容易,如果此时系统处理能力跟不上,只需要「增加服务 器即可」(把拆分后的每一个服务再多做几个集群)
4.2 数据库拆分
数据库拆分分为:「垂直拆分和水平拆分 (分库分表)」
「按照业务维度把相同类型的表放在一个数据库,另一些表放在另一个数据库」,这种方式 的拆分叫「垂直拆分」,也就是在 不同库建不同表,把表分散到各个数据库
比如产品、订单、用户三类数据以前在一个数据库中,现在可以用三个数据库,分别为 「产品数据库、订单数据库、用户数据库」,这样可以将不同的数据库部署在不同的服务器上,提升单机容量和性能问题,也解决多 个表之间的 IO 竞争问题
根据数据行的特点和规则,将表中的「某些行切分到一个数据库,而另外的某些行又切分 到另一个数据库」,这种方式的拆分叫「水平拆分」
单库单表在数据量和流量增大的过程中,大表往往会成为性能瓶颈,所以数据库要进行「水平拆分」
数据库拆分,采用一些开源方案,降低开发难度,比如:MyCat
、Sharding-Sphere
5 静态化
对于一些访问量大,更新频率较低的数据,可直接定时生成静态 html 页面,供前端访问,而不是访问 jsp
常用静态化的技术:freemaker
、velocity
定时任务,每隔 2 分钟生成一次首页的静态化页面
页面静态化首先可以大大提升访问速度,不需要去访问数据库或者缓存来获取数据,浏览器直接加载 html
页即可
页面静态化可以提升网站稳定性,如果程序或数据库出了问题,静态页面依然可以正常 访问
如何实现漏斗型系统
漏斗型系统需要从产品策略/客户端/接入层/逻辑层/DB 层全方位立体的设计。
产品策略
- 轻重逻辑分离,以秒杀为例,将抢到和到账分开;
- 抢到,是比较轻的操作,库存扣成功后,就可以成功了
- 到账,是比较重的操作,需要涉及到到事务操作
- 用户分流,以整点秒杀活动为例,在 1 分钟内,陆续对用户放开入口,将所有用户请求打散在 60s 内,请求就可以降一个数量级
- 页面简化,在秒杀开始的时候,需要简化页面展示,该时刻只保留和秒杀相关的功能。例如,秒杀开始的时候,页面可以不展示推荐的商品。
客户端
- 重试策略非常关键,如果用户秒杀失败了,频繁重试,会加剧后端的雪崩。如何重试呢?根据后端返回码的约定,有两种方法:
- 不允许重试错误,此时 ui 和文案都需要有一个提示。同时不允许重试
- 可重试错误,需要策略重试,例如二进制退避法。同时文案和 ui 需要提示。
- ui 和文案,秒杀开始前后,用户的所有异常都需要有精心设计的 ui 和文案提示。例如:【当前活动太火爆,请稍后再重试】【你的货物堵在路上,请稍后查看】等
- 前端随机丢弃请求可以作为降级方案,当用户流量远远大于系统容量时,人工下发随机丢弃标记,用户本地客户端开始随机丢弃请求。
接入层
- 所有请求需要鉴权,校验合法身份
- 如果是长链接的服务,鉴权粒度可以在 session 级别;如果是短链接业务,需要应对这种高并发流量,例如 cache 等
- 根据后端系统容量,需要一个全局的限流功能,通常有两种做法:
- 设置好 N 后,动态获取机器部署情况 M,然后下发单机限流值 N/M。要求请求均匀访问,部署机器统一。
- 维护全局 key,以时间戳建 key。有热 key 问题,可以通过增加更细粒度的 key 或者定时更新 key 的方法。
- 对于单用户/单 ip 需要频控,主要是防黑产和恶意用户。如果秒杀是有条件的,例如需要完成 xxx 任务,解锁资格,对于获得资格的步骤,可以进行安全扫描,识别出黑产和恶意用户。
逻辑层
- 逻辑层首先应该进入校验逻辑,例如参数的合法性,是否有资格,如果失败的用户,快速返回,避免请求洞穿到 db。
- 异步补单,对于已经扣除秒杀资格的用户,如果发货失败后,通常的两种做法是:
- 事务回滚,回滚本次行为,提示用户重试。这个代价特别大,而且用户重试和前面的重试策略结合的话,用户体验也不大流畅。
- 异步重做,记录本次用户的 log,提示用户【稍后查看,正在发货中】,后台在峰值过后,启动异步补单。需要服务支持幂等
- 对于发货的库存,需要处理热 key。通常的做法是,维护多个 key,每个用户固定去某个查询库存。对于大量人抢红包的场景,可以提前分配。
存储层
对于业务模型而言,对于 db 的要求需要保证几个原则:
- 可靠性
- 主备:主备能互相切换,一般要求在同城跨机房
- 异地容灾:当一地异常,数据能恢复,异地能选主
- 数据需要持久化到磁盘,或者更冷的设备
- 一致性
- 对于秒杀而言,需要严格的一致性,一般要求主备严格的一致。
实践——微视集卡瓜分系统
微视集卡瓜分项目属于微视春节项目之一。用户的体验流程如下:
架构图
- 客户端主要是微视主 app 和 h5 页面,主 app 是入口,h5 页面是集卡活动页面和瓜分页面。
- 逻辑部分为分:发卡来源、集卡模块、奖品模块,发卡来源主要是任务模块;集卡模块主要由活动模块和集卡模块组成。瓜分部分主要在活动控制层。
- 奖品模块主要是发钱和其他奖品。
瓜分降级预案
为了做好瓜分时刻的高并发,对整个系统需要保证两个重要的事情:
- 全链路梳理,包括调用链的合理性和时延设置
- 降级服务预案分析,提升系统的鲁棒性
如下图所示,是针对瓜分全链路调用分析如下图,需要特别说明的几点:
- 时延很重要,需要全链路分析。不但可以提高吞吐量,而且可以快速暴露系统的瓶颈。
- 峰值时刻,补单逻辑需要关闭,避免加剧雪崩。
我们的降级预案大概如下:
- 一级预案,瓜分时刻前后 5 分钟自动进入:
- 入口处 1 分钟内陆续放开入口倒计时,未登录用户不弹入口
- 主会场排队,以进入主会场 100wqps 为例,超过了进入排队,由接入层频控控制
- 拉取资格接口排队,拉取资格接口 100wqps,超过了进入排队,由接入层频控控制
- 抢红包排队,抢红包 100wqps,超过了进入排队,由接入层频控控制
- 红包到账排队,如果资格扣除成功,现金发放失败,进入排队,24 小时内到账。异步补单
- 入口处调用后端非关键 rpc:ParticipateStatus,手动关闭
- 异步补单逻辑关闭。
- 二级预案,后端随机丢请求,接入层频控失效或者下游服务过载,手动开启进入
- 三级预案,前端随机丢请求,后端服务过载或者宕机进入。手动开启
综上,整个瓜分时刻体验如下所示:
回顾下漏斗模型,总结下整个实践:
3.3.2.水平扩展
水平扩展:只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,如何在架构各层进行可水平扩展的设计,以及互联网公司架构各层常见的水平扩展实践
3.2.流量控制
应对高并发,不仅可以通过技术手段提升系统应该高并发能力,也可以通过分流、限流等策略分散流量,这里有上中下三策:
下策:限流
针对瞬时流量涌入,冲击系统,而系统没有应对能力,通过限流,限制用户请求,比如英雄联盟的一区排队,缺点显而易见,用户体验差,可能损失客户
中策:用钞能力打败魔法
增加硬件资源,增加系统的吞吐能力:更大的带宽,更好的服务器,更plus的其他硬件资源,我只要处理的够快,你就无法打败我,缺点嘛,成本高,烧钱
上策:分流、削峰
将高峰访问流量平滑分布到整个时间段,减少高峰流量,消减高峰用户请求频率:12306的提前购票,电商节的预售等等,让有需求的一部分人先满足需要
4.削峰
流量削峰是为了解决互联网应用在高峰期访问量过大,导致服务器压力过大、服务质量下降的问题而提出的一种技术手段。它的主要思想是通过限制用户请求的速率,将高峰期的访问流量平滑地分布到整个时间段内,从而达到减轻服务器压力、提高服务质量的目的。
削峰的出现解决了互联网场景下的高并发问题:12306节假日购票,618、双十一的电商活动等。削峰是一种思想,一种解决方案,他的应用不局限于互联网场景,也可以运用于其他领域:现在旅游业倡导的错峰出行。
上边提到的场景存在瞬间巨大流量的涌入,会拖垮整个系统。就双十一而言,一个商品的数量是有限制的,真正能买到的用户的也是有限的,同时,服务器资源有限,这就出现了一个比较有意思的现象,日常服务器资源剩余,高峰资源不足,为了解决高峰期资源不足问题,削峰这种解决问题的思路自然而然被提上日程。
4.1.怎样来实现流量削峰方案
削峰从本质上来说就是更多地延缓用户请求,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数要尽量少”的原则
-
限流算法:通过限制单位时间内请求的数量或速率,来控制访问流量。常见的限流算法包括令牌桶算法和漏桶算法。
-
缓存:将一部分数据缓存在内存中,减少对数据库的访问次数,从而降低服务器压力。
-
负载均衡:将访问流量分散到多台服务器上,避免单一服务器过载,提高系统的可用性和稳定性。
-
异步处理:将一些耗时的操作异步处理,如发送邮件、生成报表等,避免阻塞主线程,提高系统的并发能力。
-
CDN加速:通过将静态资源缓存在CDN节点上,加速用户对静态资源的访问,降低服务器压力
4.2.限流
这样就像漏斗一样,尽量把数据量和请求量一层一层地过滤和减少,需要查询的用户,在缓存中能能查询到需要的信息,无需通过每次查询数据库,那么最后留下的,就是真正需要成交的客户,大大减轻DB的读写压力
1)分层过滤的核心思想
- 通过在不同的层次尽可能地过滤掉无效请求。
- 通过CDN过滤掉大量的图片,静态资源的请求。
- 再通过类似Redis这样的分布式缓存,过滤请求等就是典型的在上游拦截读请求。
2)分层过滤的基本原则
- 对写数据进行基于时间的合理分片,过滤掉过期的失效请求。
- 对写请求做限流保护,将超出系统承载能力的请求过滤掉。
- 涉及到的读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题。
- 对写数据进行强一致性校验,只保留最后有效的数据。
最终,让“漏斗”最末端(数据库)的才是有效请求。例如:当用户真实达到订单和支付的流程,这个是需要数据强一致性的。
5.总结
高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一。
提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。前者垂直扩展可以通过提升单机硬件性能,或者提升单机架构性能,来提高并发性,但单机性能总是有极限的,互联网分布式架构设计高并发终极解决方案还是后者:水平扩展。
同时结合一些策略达到分流、限流的目的
1.对于秒杀这样的高并发场景业务,最基本的原则就是将请求拦截在系统上游,降低下游压力。如果不在前端拦截很可能造成数据库(mysql、oracle等)读写锁冲突,甚至导致死锁,最终还有可能出现雪崩等场景。
2.划分好动静资源,静态资源使用CDN进行服务分发。
3.充分利用缓存(redis等):增加QPS,从而加大整个集群的吞吐量。
4.高峰值流量是压垮系统很重要的原因,所以需要RocketMQ消息队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。
限流常用的几种算法
在具体开发中,尤其是RPC框架中,限流是RPC的标配,一般业务开发人员很少做限流算法开发,这也导致大部分开发人员不是很了解限流算法的原理,这里分享几种常用的限流算法,指出他们的优缺点,并通过代码实现他们。
计数器限流
你要是仔细看了上面的内容,就会发现上面举例的每秒阈值1000的那个例子就是一个计数器限流的思想,计数器限流的本质是一定时间内,访问量到达设置的限制后,在这个时间段没有过去之前,超过阈值的访问量拒绝处理,举个例,你告诉老板我一个小时只处理10件事,这是你的处理能力,但领导半个小内就断续断续给你分派了10件事,这时已经到达你的极限了,在后面的半个小时内,领导再派出的活你是拒绝处理的,直到下一个小时的时间段开始。
在具体开发中,尤其是RPC框架中,限流是RPC的标配,一般业务开发人员很少做限流算法开发,这也导致大部分开发人员不是很了解限流算法的原理,这里分享几种常用的限流算法,指出他们的优缺点,并通过代码实现他们。
计数器限流
你要是仔细看了上面的内容,就会发现上面举例的每秒阈值1000的那个例子就是一个计数器限流的思想,计数器限流的本质是一定时间内,访问量到达设置的限制后,在这个时间段没有过去之前,超过阈值的访问量拒绝处理,举个例,你告诉老板我一个小时只处理10件事,这是你的处理能力,但领导半个小内就断续断续给你分派了10件事,这时已经到达你的极限了,在后面的半个小时内,领导再派出的活你是拒绝处理的,直到下一个小时的时间段开始。
漏斗限流
漏斗限流,意思是说在一个漏斗容器中,当请求来临时就从漏斗顶部放入,漏斗底部会以一定的频率流出,当放入的速度大于流出的速度时,漏斗的空间会逐渐减少为0,这时请求会被拒绝,其实就是上面开始时池塘流水的例子。流入速率是随机的,流出速率是固定的,当漏斗满了之后,其实到了一个平滑的阶段,因为流出是固定的,所以你流入也是固定的,相当于请求是匀速通过的
RPC限流到底怎么做的?
微服务盛行的时代,一个Application可能对付发布多个服务(A,B两个服务),一个服务可能存在多个方法(A1,A2,B1,B2),而且一个Application通常会部署多台机器,我们通常的限流可能回对某个服务限流,也可能对某个服务下面的方法限流,一般情况下RPC的控制台中支持限流的可视化,可配置化。
从上图来看,浏览器触发配置中心的限流规则变更,配置中心通知监听了该规则的服务器,这个时候可能是客户端限流,也可能是服务端限流,取决于浏览器上的操作,假设是服务端限流,那么每个服务端启动一个限流算法(可能是上面算法中的任意一个),这个时候是每台机器都在限流,相当于单机限流,各不影响。
第一个问题:我们介绍了三种限流算法,比如计数器限流,会开启一个协程定时检测重置计数变量为0,如果一个应用有很多个服务,是否意味着要开启很多个协程,那么有人说协程轻量级的,没事,但要是Java中的线程呢,怎么解决,思路是延迟重置,服务开始时,设置计数阈值,同时记录当前时间,每当请求来临时,我们只允许在当前时间段内并且计数变量没有到达阈值的请求通过,否则拒绝,当过了当前时间段,我们重置计数变量,这样是不是就不用开启新的协程了,优化完的代码如下
//计数器限流,不用新开协程, 每次判断时,
第二个问题:上面我们假设是服务端限流,那么到底该用服务端限流还是客户端限流,我们看这样一个示例,有一个A服务,部署了10台机器(相当于10个服务提供者),但调用A服务的有100个消费者(客户端),假设我们每台机器的阈值是1000,你怎么分给100个客户端呢?你也不了解他们的调用量,就比较麻烦,所以一般情况下都是在服务端限流,因为你自己的服务你最清楚。什么时候用客户端限流呢?当你明确的知道某一个客户端调用量非常大,影响了其它客户端的使用,这时你可以指定该客户端的限流规则
第三个问题:我们上面提到的都是单机限流,还是我们的A服务,部署了10台,但有一台机器是1核2G,其余是4核8G的,这时限流就麻烦了,不能用统一标准限流了,那么在分布式应用程序中,有没有分布式限流的方法呢?这里提供几种思路:
- Nginx 层限流,一般http服务需要经过网关,Nginx层相当于网关限流
- Redis限流,redis是线程安全的,redis支持LUA脚本
- 开源组件Hystrix、resilience4j、Sentinel
分布式限流可以单独写好几篇文章了,后面会单开几篇文章写分布式限流的
以上,为个人工作之余一些浅薄看法,记录下来,与诸位共享,不足之处,多多指点,感激不尽
参考
设计一个高并发、高可用秒杀系统 - 知乎 (zhihu.com)
微服务-高并发下接口如何做到优雅的限流 - 阿伟~ - 博客园 (cnblogs.com)
高并发秒杀削峰系统解决方案详解 - hanease - 博客园 (cnblogs.com)
浅谈常见高并发解决方案
高并发场景下,6种方案,保证缓存和数据库的最终一致性!