浅谈「高可用」架构设计
# 1. 引言
坚守 Design For Failure 的架构理念。
作为一个后端开发工程师,不同的岗位如:初级研发工程师、高级研发工程师或者架构师,负责的系统类型可能是:
- 负责一个功能
- 负责一个系统模块
- 负责一个系统
- 负责多个系统或业务条线
即时是一个再小的系统,小到一个小小的功能模块,都对可用性有一定的要求,针对重要的功能模块,甚至还要求做到「高可用」。「高可用」这个概念,根据不同的可用性程度实现方案会有所不同。
「如何设计一个高可用架构」,我比较认可 AWS 提出的"Design for failure and nothing will fail" 最佳实践,即面向失败进行系统设计,必须考虑系统所有可能发生故障或不可用的场景,并假设这些可能都会发生,倒逼自己设计足够健壮的系统。
# 1.1 墨菲定律
在系统设计时,应该多思考墨菲定律,即:"如果事情有变坏的可能,不管这种可能有多小,它总会发生",对应到系统设计时需要考虑的点:
- 任何事情没有表面看起来那么简单。
- 所有的事情都会比你预计的时间长。
- 可能出错的事总会出错。
- 如果你担心某种情况发生,那么它就更有可能发生。
# 1.2 康威定律
在系统划分时,也必须要思考康威定律,即:"设计系统的架构受制于产生设计的组织的沟通结构",通俗的说:
- 软件架构是公司组织架构的反映。
- 应该按照业务闭环进行系统拆分/组织架构划分,实现闭环/高内聚/低耦合,减少沟通成本。
- 如果沟通出现问题,那么就应该考虑进行系统和组织架构的调整。
- 在合适时机进行系统拆分,不要一开始就把系统/服务拆的非常细,虽然闭环,但是每个人维护的系统多,维护成本高。
# 1.3 业务关心的问题
如果我们想做成「高可用」系统,至少要解决以下业务关心的问题:
- 如何评估系统高可用?
- 如何监控系统高可用?
- 如何保证系统高可用?
简而言之,完成一个系统的设计、开发、交付的工作不难,难的是如何保证系统的高可用。
本文和大家一起探讨目前主流的高可用的一些技术方案,探讨如何做到「永不消失的网站/APP」。
# 2. 可用性度量与故障分类
# 2.1 可用性度量
根据谷歌 SRE 手册,给出了定义和简要说明。
SLI:Service Level Indicator,服务等级指标。
"对所提供的服务水平的某些方面进行仔细定义的定量测量"。比如:对于一个网站来说,一个常见的 SLI 就是请求正常响应的百分比。
SLO:Service Level Object,服务等级目标。
"由 SLI 衡量的服务水平的目标值或值范围",是团队设置的。是围绕 SLI 构建的目标。通常是一个百分比,并与时间范围(比如:阅读、季度、年度等)挂钩。通常用一连串的 9 来度量。
SLA:Service Level Agreement,服务等级协议。
"与您的用户签订的明确或隐含的合同,其中包括满足(或过错)他们所包含的 SLO 的后果",由业务而不是工程师、SRE或操作人员设定的。SLA 是一个外部指标,因此与 SLO 的目标不同。SLA 是与用户达成的业务协议,规定了一定程度的可用性。工程团队知道 SLA,但没有设置它们。相反,团队设置 SLO 比 SLA 更严格,给自己一个缓冲。是企业围绕 SLO 发布的协议,它要求在不满足 SLO 时,向客户补偿的协议。
MTBF:Mean Time Between Failture,平均故障间隔。
表示两次故障的间隔时间,也就是系统「正常运行」的平均时间,这个时间越长,说明系统稳定性越高。
MTTR:Mean Time To Repair,故障恢复时间。
表示系统发生故障后「恢复的时间」,这个值越小,故障对用户的影响越小。
SLO 或者 SLA 的计算值
SLO/SLA = (1 - 年度不可用时间/年度总时间) × 100% = MTBF / (MTBF + MTTR) * 100%
从公式中可以看出,
SLA 等于 4 个 9,也就是可用时长达到 99.99%,不可用时长则为是 0.01%,一年是 365 天,8760 个小时,一年的不可用 时长就是 52.6 分钟。
SLA 等于 3 个 9,就相当于一年不可用时长等于 526 分钟。
SLA 等于 5 个 9,就相当于一年不可用时长等于 5.26 分钟。
可以发现,用 SLA 等于 4 个 9 作为参照物,少个 9 相当于小数点往后移一位,多个 9 相当于小数点往前移一位。整理换算成年月天的表格如下。
系统可用性 % | 宕机时间/年 | 宕机时间/月 | 宕机时间/周 | 宕机时间/天 |
---|---|---|---|---|
90%(1个9) | 36.5天 | 72小时 | 16.8小时 | 2.4小时 |
99%(2个9) | 3.65天 | 7.2小时 | 1.68小时 | 14.4分钟 |
99.9%(3个9) | 8.76小时 | 43.8分钟 | 10.1分钟 | 1.44分钟 |
99.99%(4个9) | 52.6分钟 | 4.38分钟 | 1.01分钟 | 8.66秒 |
99.999%(5个9) | 5.26分钟 | 25.9秒 | 6.05秒 | 0.87秒 |
小贴士
京东淘宝大部分是 4 个 9。
举个例子
假如我有一个网站:http://baidu.com
,监控指标是请求正常响应数。
时间:从 2022年1月1日 到 2022年3月18号,请求数据如下:
月份 | 总请求数 | 错误数 | 故障宕机时长 |
---|---|---|---|
1 月份 | 500 | 20 | 0 min |
2 月份 | 600 | 10 | 10 min |
3月1号~3月18号 | 400 | 15 | 0 min |
那么对应的 SLI、SLO、SLA 是多少?
SLI = 1-(20+10+15)/(500+600+400)=97%
SLO = 1-(10/79天×24×60)=99.991%
SLA:假设我们是给第三方做的网站,并签订了 SLO 达不到 99.99%,就要赔偿多少钱,可以根据上面的 SLO,再根据协议签订的 SLA 协议,算出补偿的金额,但是我们通常也把 SLA 值直接等于 SLO 的值,叫的比较多的是 SLA。
在实际的业务中,要综合考虑 SLI 和 SLO,比如:流量的低峰期和高峰期业务不可用 1 分钟,对业务的影响评估完全不同,因此,又有了如下较好的高可用评估手段(某种情况下 SLI 可能被用来直接作为高可用评估手段)。
高可用评估:基于一段时间(比如 1 年)的停机影响的请求量占比,进行评估,公式如下:
高可用评估 = 停机时间影响请求量/总的请求量
# 2.2 故障分类
分类 | 描述 | 权重 |
---|---|---|
事故级故障 | 严重故障,网站整体不可用 | 100 |
A类故障 | 网站访问不顺畅或核心功能不可用 | 20 |
B类故障 | 非核心功能不可用,或核心功能少数用户不可用 | 5 |
C类故障 | 以上故障以外的其他故障 | 1 |
故障分计算公式:
故障分 = 故障时间 × 故障权重
# 3. 系统可用性的挑战
我们都知道,系统发生故障其实是不可避免的,尤其是越大的系统,发生问题的概率也越大。这些故障一般体现在 3 个方面:
- 硬件故障:CPU、内存、磁盘、网卡、交换机、路由器
- 软件问题:代码 Bug、版本迭代
- 不可抗力:地震、水灾、火灾、战争
具体常见的服务不可用的场景又有:
- DNS 被劫持
- CDN 服务不可用
- 应用服务器及数据库服务器宕机
- 网络交换机失效
- 硬件故障:硬盘损坏、网卡松掉
- 环境故障:机房停电、空调失灵、光缆被挖掘机挖断
- 代码 bug
- 黑客攻击
- 促销引来大量用户访问
- 第三方合作伙伴的服务不可用
- 超时设置不合理导致系统奔溃
- 限流措施不到位,导致负载过高时系统奔溃
- 解耦不彻底,导致某个服务挂掉时所有服务受到影响
- ……
# 4. 高可用的有效手段
# 4.1 深入理解业务
技术人很容易犯的一个错误,「离代码很近,离用户很远!」,在高可用架构中,我认为要做好高可用架构设计,深入理解业务是重中之重。
深入业务是做好高可用架构的前提
架构设计与系统演进:架构要结合具体的业务场景来设计。
理解业务这件事:
- 产品需求不等于业务诉求
- 产品提出要实现的系统功能未必等于业务想要解决的问题
- 怎么在规定的时间内搞定这个需求
举个例子
产品需求是这样的:
- 1 W 载客量
- 200 km/h 速度
- 10 级 抵抗风浪
开发侧的理解:
- 需要多少钢材
- 多少工人、几个发动机
- 船舱结构要如何设计
实际上业务的诉求是:"安全达到对岸",业务其实不关心你是修船、造车、开车。实际的开发过程中很多人不关心需求的源头,不能真正理解业务。
大部公司中,技术处于价值创造的末端。
用户的真实需求 -> 业务 -> 运营 -> 产品 -> 技术
,每一层信息都会被加工、处理、拆分,技术看到的问题距离最想解决的问题可能会很远,没有搞清楚问题的源头而去解决问题,结果会很糟糕。
再举个例子
订单作为交易的载体需要承载大量的数据:
- 订单系统的演进完全跟着业务需求走。
- 很难判断是否应该让这些数据落到订单上。(存储在一个无法管理的 JSON 字段中)
表面上看,是系统设计和实现不够好,实际上是:没有在深度理解业务的基础上对交易系统进行建模,确定边界与能力范围。
如何理解业务的小技巧:
- 不要盲信产品 "永远不要试图用战术上的勤奋,去掩盖你战略上的懒惰"
- 技术团队要建立走进业务的机制
- 实际去体验业务会让你建立很强的认识感与同理心
- 只有站在他们的角度你才能看到他们的痛点,才会思考技术是不是能解决你原本未必知道或者关注的问题
不要让业务机制成为"一次性作秀",必须明白,技术同学对业务的直观感受大多来自线上的产品和系统,这和直接接受用户有很大的差别。
当我们理解了业务,还可以做以下架构设计来保证高可用。
# 4.2 负载均衡
- 应用服务器负载均衡:即将用户的请求通过负载均衡服务器分发到多个 web 服务器上,负载均衡服务器还可以保证单某个 web 服务器不可用(也包括应用程序发布)时,能够自动将该部分流量转发到其他web服务器上。
- HTTP 负载均衡:
- DNS 负载均衡:用户浏览器(用户请求域名解析) -> DNS 服务器 ->返回 IP 地址 -> 用户使用真实 IP 进行浏览器请求 -> Web 服务器集群
- 反向代理负载均衡:
- IP 层负载均衡:
- 数据链路层负载均衡:(用的最多的方案)
大型互联网一般采用两级负载均衡,DNS 服务器解析出来的 IP 地址是负载均衡服务器的 IP 地址,这样就不会将真实的服务器的 IP 地址暴露出来。
# 4.3 限流、降级、熔断
案例
网关系统 -》商品系统 -》促销系统/积分系统
出现流量高峰时,虽然商品系统很容易扩容,但对于商品依赖的其他服务,就不会有实时性的响应。那么出校或积分系统就可能因为无法承担大流量,请求处理缓慢,直到所有线程资源被占满,无法处理后续的请求。
此时,积分系统的响应时间变长,其他依赖服务的整体请求的响应时间也会因此变长,整体服务甚至会发生宕机。即服务雪崩。
雪崩现象:即局部故障最终导致了全局故障。
要怎么避免雪崩呢?对于系统可用性,你要通过三个方面来解决:分别是"评估"、"检测" 和 "保证"。
解决的思路是:在分布式系统中,当检测到某一个系统或服务响应时长出现异常时,要想办法停止调用该服务,让服务的调用快速返回失败,从而释放此次请求持有的资源,这就是架构设计中经常提到的降级和熔断机制。
熔断设计的原理:参考了电路中保险丝的保护原理。在微服务架构中,服务的熔断机制是指:在服务 A 调用服务 B 时,如果 B 返回错误或超时的次数超过一定阈值,服务 A 的后续请求将不再调用服务 B。这种设计方式就是断路器模式。
降级设计的原理:降级设计是站在系统整体可用性上考虑问题:当资源和访问量出现矛盾时,在有限的资源下,放弃部分非核心功能或者服务,保证 整体的可用性,熔断也是降级的一种手段。
- 限流:通过对并发访问进行限流,降级并发请求的数量来保护系统,避免过载。
- 降级:关闭部分非核心功能,降低对系统的资源消耗,保证系统在高并发的情况下仍然保持可用。
降级的最终目的是保证核心服务可用,即时是有损的。
降级设计的一些原则
- 服务降级
- 读操作降级:做数据兜底服务,将兜底数据提前存储在缓存中,当系统触发降级时,都操作直接降级到缓存,从缓存中读取兜底数据,如果此时缓存中也不存在查询数据,则返回默认值,不再请求数据库。
- 写操作降级:将之前直接同步调用写数据库的操作,降级为先写缓存,然后再异步写入数据库。
- 功能降级: 就是在做产品功能上的取舍,既然在做服务降级时,已经舍掉了非核心的服务,那么同样的产品功能层面也要相应的进行简化。可以通过简化降级开关控制功能的可用或不可用。另外,在设计降级时,离不开降级开关的配置,一般是通过参数化配置的方式存储在配置中心,手动或自动开启开关,实现系统降级。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅,从而梳理出那些必须誓死保护,哪些可降级。
- 超时次数超过重试次数降级
- 限流降级
- 服务不可用降级
# 4.4 隔离
隔离是指将系统或资源分割开。
系统隔离:是为了在系统发生故障时,能限定传播范围和影响范围,即故障发生后不会出现滚雪球效应,从而保证只有出问题的服务不可用,其他服务还是可用的。
资源隔离:通过隔离来减少资源竞争,保证服务间的相互不影响和可用性,常用的隔离手段有:
- 线程隔离:主要是指线程池隔离,在实际使用时,我们会把请求分类,然后交给不同的线程池处理。当一种业务的请求处理发生问题时,不会将故障扩散到其他线程池,从而保证其他服务可用。(我们可以在过滤器中,至少封装两个线程池:核心业务线程池、非核心业务线程池)
- 进程隔离:部署多实例,通过负载均衡进行路由转发,通过进程隔离使得某一个子系统出问题时不会影响到其他子系统。
- 集群隔离:部署多个服务形成集群,提升系统容量。比如:随着调用方的增多,当秒杀服务被刷会影响到其他服务的稳定性时,应该考虑为秒杀服务提供单独的服务集群。
- 机房隔离:每个机房的服务都有自己的服务分组,本机房的服务只调用本机房的服务,不进行跨机房调用。其中一个机房发生问题时,通过 DNS/负载均衡将请求全部切到另一个机房,或者考虑服务自动重试其他机房的服务,从而提升系统可用性。
- 读写隔离:通过主从模式将读和写集群分离,常见的读写隔离有 MySQL 读写隔离和 Redis 读写隔离。当主集群不可写的时候,从集群还可以用。当从集群大量读导致从集群挂掉,主集群还可以提供写的能力。
- 快慢隔离:有些请求和服务比较慢,会导致全部资源(如线程池被耗尽),因此需要对比较慢的操作限制资源的使用量。
- 动静隔离:将动内容和静态内容资源隔离,一般将静态资源(如:JS/CSS)放在 CDN 上。
- 爬虫隔离:在实际的一些业务中,爬虫比例和正常流量的比例可能超过一半,而一些系统会因为爬虫流量过大导致服务不可用。一种方法是通过限流解决,另一种方法是将爬虫引流到单独集群。
- 热点隔离:热点服务独立成系统或单独服务,对于读热点,我们常用多级缓存来搞定,对于写热点常用缓存+队列模式削锋。
服务隔离:服务隔离的目的是防止因为某些服务抖动而造成整个应用内的所有服务不可用,可以分为:
- 应用内线程池隔离:采用 Servlet 3 异步化,并为不同的请求按照重要级别分配线程池,这些线程池是相互隔离的,提供监控接口以便发现问题并及时进行动态调整。
- 部署/分组隔离:不同的消费方式提供不同的分组,不同的分组之间相互影响,避免某个分组某个应用乱用,导致整个分组服务不可用。
- 拆应用隔离:如果一个服务调用量巨大,那么我们应该把这个服务单独拆出去,做成一个应用,减少因其他服务上线或者重启导致影响该应用。
消息队列隔离:通过消息队列来实现异步解耦。生产者和消费者通过消息队列进行解耦,当消费者发生故障的时候,生产者可以继续向消息队列发送数据,而不会影响生产者。另外,还可以进行削峰填谷的作用。
# 4.5 服务治理
- 服务分级:根据服务的重要程度来决策丢弃请求。如何为服务确定级别:
- 1 级:系统最关键的服务,如果出现故障会导致用户或业务产生重大损失,比如:登录服务、流媒体服务、权限服务、数专服务等。
- 2 级:对于业务非常重要,如果出现会导致用户体验受到影响,但是不会完全无法使用我们的系统,比如:排行版服务、评论服务等。
- 3 级:会对用户造成较小的影响,不容易注意或很难发现,比如:用户头像服务、弹窗服务等。
- 4 级:即使失败,也不会对用户体验造成影响,比如:红点服务等。
服务分级的应用场景:
- 核心接口运营质量周报。比如,每日邮件推送 1 级和 2 级的观测数据。
- SLA:针对 1 级服务和 2 级服务,制定 SLO。
- API 网关根据服务分级限流,优先确保 1 级服务通过。
- 重大项目参考服务重要程度制定优先级计划,如容灾演练,大型活动压测等。
用户分级:根据用户的重要程度来决策丢弃请求。
拆分系统:将系统拆分为多个子系统虽然增加了复杂性,但是却得到了跟多的好处,这里以商品服务为例。
- 数据异构系统存储的数据是原子化数据,这样可以按照一些维度对外提供服务;
- 数据同步系统存储的是聚合数据,可以为前端展示提供高性能的读取;
- 前端展示系统分为商品详情页和商品介绍,可以减少相互影响。
防御性编程(契约精神):防备上游、做好自己、怀疑下游。
# 4.6 数据治理
# 4.6.1 数据闭环
即数据的自我管理,或者说是数据都维护在自己的系统里,不依赖于任何其他系统,即去依赖化的好处是别人抖动不会影响到自身系统。数据闭环包括以下几个方面:
- 数据异构:数据闭环的第一步,即将各个依赖系统的数据拿过来,按照自己的要求存储起来。
- 数据原子化:原子化数据,这样未来我们可以对这些数据再加工处理,从而响应快速变化的需求。
- 数据聚合:将多个原子数据聚合为一个大 JSON 数据,这样前端展示只需要一次获取。
- 数据存储:选择合适的数据存储类型。
# 4.6.2 数据维度化
数据应该按照维度和作用进行维度化,这样可以分离存储,进行更有效地存储和使用。
示例数据的维度比较简单,比如:
- 商品基本信息:包括标题、扩展属性、特殊属性、图片、颜色尺码、规格参数等。
- 商品介绍信息:包括商品维度商家模板、商品介绍等。
- 非商品维度的其他信息:包括分类信息、商家信息、店铺信息、店铺头、品牌信息等。
- 商品维度其他信息(异步加载):包括价格、促销、配送至、广告词、推荐配件、最佳组合等。
# 4.6.3 长期治理
- MySQL 大表
- Redis大 key
- 冷热数据
- 历史数据归档
- ……
# 4.7 超时与重试
特别注意
太多故障是因为没有设置超时或者设置得不对而造成的。而这些故障都是因为没有意识到超时设置的重要性而造成的。如果不设置超时,则可能会导致请求响应慢,慢请求累积导致连锁反应,甚至造成应用雪崩。
有些中间件或者框架在超时时会进行重试(如设置超时重试两次),读服务天然适合重试,但写服务大多不能重试(如写订单,如果写服务是幂等的,则重试是允许的),重试次数太多会导致多倍请求流量,即模拟了 DDos 攻击,后果可能是灾难,因此,务必要设置合理的重试机制,并且应该和熔断、 快速失败机制配合。在进行代码 Review 时,一定要记得 Review 超时与重试机制。
具体注意事项如下:
哪些地方已经有了超时参数的设置?
哪些地方需要我们自己显示的设置超时的参数?
将超时时间设置为 TP999 和 Max 之间的值,但因此可能会带来 0.1% 的失败。如果搭配重试,可以将失败的比例降低到 0.0001%(即两次都失败,0.1%*0.1%)。即使使用了重试一次,你的接口性能也会较好。比如设置超时时间为大于上述 TP999 的值,比如 500ms,重试一次最大的耗时才为 1s,远比上述的 Max 低。
# 4.8 日志监控告警
# 4.8.1 日志
常见的日志种类有:
- 网关日志:比如 Kong或Nginx反向代理记录的请求日志。
- WAF日志:如果整个网络拓扑中包括防火墙模块,也需要注意防火墙侧的日志,哪些请求被 WAF 拦截。
- 服务端日志:实际服务运行时产生的日志,特别注意日志中包括warn级别以上的日志。
- 中间件日志:这里主要关注中间件的包括warn级别以上的日志,特别是中间件的慢、延迟或者problem的日志,如:MySQL 慢日志、Redis 慢日志、ElasticSearch 慢日志、MySQL死锁日志。
# 4.8.2 监控
根据监控层次可以划分为:
- 资源监控:服务器(容器、物理机)等,指标包括:CPU 使用率、内存使用率、健康状态等。
- 中间件监控:Kafka、Redis、ElasticSearch、MySQL等,指标包括:使用率、容量、失败(堆积)等。
- 服务监控:服务器请求量、失败率、耗时等,维度包括:集群、服务模块、接口等。
- 业务监控:指标:在线人数、订单情况等。
常用的监控工具有:
- APM(如:Zipking、Skywalking、Cat 等)
- Prometheus + Grafana
# 4.8.3 告警
针对上面的日志和监控的指标,需要配置预警或告警通知,当问题发生的时候,能够第一时间通知到责任人,需要特别注意对告警进行治理,减少告警风暴。
# 4.9 预发布与灰度发布
灰度就是在生产环境进行小范围测试(这个观点是错误的),它本身是为了对抗"未知的不确定性",需要更加谨慎地进行灰度,确保即使问题真的在生产环境出现,造成的影响也是可控的。
- 预发布:比如有一台预发布服务器,不和域名解析和负载均衡服务器连接在一起,但是却有线上的机器的所有环境,只有内部的工程师才可以访问到。
- 灰度发布:灰度就是在生产环境进行小范围测试 (这个观点是错误的),它本身是为了对抗"未知的不确定性",需要更加谨慎地进行灰度,确保即使问题真的在生产环境出现,造成的影响也是可控的。
# 4.10 回滚机制
可回滚的设计:可回滚的本质是系统的兼容性设计与实现,比如常见的"只增不改"。
# 4.11 组件高可用
数据库的高可用
读写分离:保证读和写的资源隔离。
- 读高可用:通过数据库的主从模式,多个从服务器,当读操作读取的某个从服务器挂掉,会迁移到其他可用的从服务器上。
- 写高可用:通过数据库的主主模式,即正常操作的时候写操作会写到主服务器 A,当主服务器 A 失效的时候,写操作会被发送到主服务器 B。
# 4.12 架构冗余
一般来说,建设一个机房的要求是非常高的,地理位置、温湿度控制、备用电源等等,机房厂商会在各方面做好防护。即时这样,我们每隔一段时间还会看到这样的新闻:
- 2015年5月27日,杭州某地光纤被挖断,近 3 亿用户长达 5 小时无法访问支付宝
- 2021年7月13日,B 站部分服务器机房发生故障,造成整站持续 3 小时无法访问
- 2021年10月9日,富途证券服务器机房发生电力闪断故障,造成用户 2 个小时无法登录、交易
- ……
解决思路:
- 冗余单节点:不仅是机器,还包括网络,比如:通信线路不仅要有移动的也要有联通的,再有异地多活等架构。
- 水平扩展:无状态的计算节点容易扩展,而数据库则通过水平分库来实现。
异地多活机房架构
避免数据修改冲突,类似 MySQL 的主主模式。
架构演进路线:👉 单机架构 -> 主从副本 -> 同城灾备 -> 同城双活 -> 两地三中心 -> 伪异地双活 -> 异地双活 -> 异地多活
# 4.13 性能分析与调优
性能分析与调优旨在把系统打造成一个高可用、高可靠的系统;性能分析的目的是找出性能瓶颈与风险在哪里?性能调优就是用更少的资源提供更好的服务,成本利益最大化。
性能调优场景的手段:
- 空间换时间。内存、缓存就是典型的空间换取时间的例子。利用内存缓存从磁盘上取出的数据,CPU 请求数据直接从内存中获取,从而获取比从磁盘读取数据更高的效率。
- 时间换空间。当空间称为瓶颈时,切分数据分批次处理,用更少的空间完成任务处理。上传大附件的时候经常用这种方式。
- 分而治之。把任务切分,分开执行,也方便并行执行来提高效率,Hadoop 中的 HDFS、MapReduce 都是这个原理。
- 异步处理。业务链路上有任务时间消耗较长,可以拆分业务,减少阻塞影响。常见的异步处理机制有 MQ(消息队列),目前在互联网应用中大量使用。
- 并行。用多个进程或者线程同时处理业务,缩短业务处理时间,比如:我们在银行办业务时,如果排队人数较多时,银行会加开柜台。
- 离用户更近一点。比如:CDN 技术,把用户请求的静态资源放在离用户更近的地方。
- 一切可扩展。业务模块化、服务化(同时无状态化)、良好的水平扩展能力。
性能分析方法:
性能分析是一个大课题,不同的架构、不同的应用场景、不同的程序语言分析的方法若有差异,抽象一下大致分为两类:
- 自底而上。通过监控硬件及操作系统性能指标(CPU、内存、磁盘、网络等硬件资源的性能指标)来分析性能问题(配置、程序等问题)。因为用户请求最终是由计算机硬件设备来完成的,做事的是 CPU。
- 自顶而下。通过生成负载来观察被测试的系统性能,比如:响应时间、吞吐量;然后从请求点由外及里一层一层地分析,从而找到性能问题所在。
系统性能关注点:
系统资源包括 CPU、内存、存储介质等。一般硬件瓶颈的表现如下:
CPU 利用率高
CPU 利用率又分为系统 CPU (Linux 系统为例,操作系统占用CPU)与用户 CPU (用户程序占用的CPU,比如我们运行的引用系统),过高原因常见有:
- 计算量大。比如:运算、连接查询、数据统计。
- 非空闲等待。比如:IO 等待、资源竞争(统一资源被不同不同线程请求,而此资源又需要保持一致性,只能前一个释放后一个再访问,这样导致的等待)。
- 过多的系统调用系统调用,即调用操作系统提供的程序接口。比如:Java 项目中写日志,会调用系统接口进行日志操作,这样会导致系统 CPU 使用率比较高。
- 过多的打断。终端是 CPU 用来响应请求的机制,比如键盘的输入、鼠标的点击等都会产生中断,中断是通知 CPU 有任务需要响应,CPU 停下正在执行的程序来响应当前的中断。
内存吃紧 内存吃紧的原因比 CPU 要简单的多,多数是过多的页交换与内存泄漏。 Java 程序运行在 JVM 之上,JVM 的内存设置也是有限制的,有时候 JVM 堆内存中有些对象无法回收,久而久之就没有空间来容纳新的对象,最后导致了 JVM 崩溃,这也就是内存溢出,回收不了的这种现象就是内存泄漏,这往往是由于程序原因硬引起的。
磁盘繁忙 磁盘繁忙,即数据读写频繁。我们知道,磁盘介质的读写是物理动作,所以速度受限。如果频繁地对磁盘进行读写,因为磁盘的平均导致的 CPU 等待的情况会激增。虽然现在有了 SSD,但 SSD 相当昂贵,所以磁盘的瓶颈问题是相对突出的问题。数据玩笑话,不做任何分析也可以说磁盘瓶颈是系统性能风险。
网络流量过大 高并发系统由于访问量大,带宽需求会比较大,导致网络拥堵。比如:一个PV(访问一个页面的单位)100K,同一时刻 10 万用户在访问,那么此时占用带宽大约就是:100K * 100000 = 977MB,换算成 bit/s 就是 7.8Gbit/s。
# 4.14 性能测试与自动化测试回归
# 4.14.1 性能测试
性能测试的首要任务在于一定要明白性能测试的背景和目的(比如:你是为了找到系统的瓶颈,还是为了验证系统在指定流量下能否扛得住),而不是直接认为 C 端接口就要进行性能测试,背景和目标不明确会导致整个性能测试沦为一次业务秀,达不到预期要保证线上高可用的目的。
性能测试的难点在于,如果用线下的测试环境去仿真线上的环境,你的报告中需要让人信服「测试环境的压测报告可以用来反馈线上环境」,比如:报告中要给出线上环境的机器情况、主数据等,线下环境的机器是怎么去匹配这些。
性能测试至少包括以下几种类型的测试:
- 基准测试:一般是单业务场景、单用户的场景来执行脚本,同时设置合理的用户思考时间如200m
- 配置测试:设计业务的场景来进行配置的优化,并给出配置测试的目标方向,即:主要优化哪些配置(比如:JVM、Tomcat、MySQL 连接池等)。
- 稳定性测试:稳定性测试的目的是验证在当前软硬件环境下,长时间运行一定负载,确定系统在满足性能指标的前提下是否运行稳定。
- 负载测试:负载测试的目的是版主我们找出性能问题与风险,对系统进行定容定量;为系统优化、性能调整提供数据支撑。
特别提醒的是,性能测试的报告中一定会有一项:「系统风险」。这个也是性能测试报告最重要的内容之一。
比如:给出随着系统的规模增加,首要风险在哪里?第二瓶颈又是什么?哪些情况的操作对系统会有重大的风险。本次性能分析报告是否能够合理的给生产环境的性能合理的建议,如不能,给出哪里有风险。。
如何写一份合格的性能测试报告,可以参考这篇文档:待补充
# 4.14.2 自动化测试回归
在自动化测试领域一般会有两个层次的自动化:
- 接口自动化
- UI 自动化
另外,这篇文章中给出了这一种针对读服务无状态的情况下,给出了一种《如何基于流量回放实现读服务的自动化测试回归》,感兴趣的可以去实验下。
通过更长时间的回放尽可能地覆盖更多的业务场景,但也并没有足够的证据表明,一定不会出现漏测。对于此种问题,可以借助一些代码覆盖率的工具,如 Java 里的 JaCoCo,来统计一次回放后被测系统的代码覆盖率,通过数据来判断是否存在可能的漏测。
# 4.15 故障演练
针对故障演练,一般是通过混沌工程项目来实现的,比如:阿里开源的chaosblade (opens new window)是一款简单易用、功能强大的混沌实验注入工具。
🎯 故障演练的目的:
- 这些措施在故障发生时是否真的有效?
- 处理流程与沟通协作是否通畅?
技术 Leader 要化被动为主动,有意识地推进故障演练,不论是以注入还是回放的形式制造可控的故障,以此验证应急处理的机制流程和预先设计的灾备方案是否有效。
演练的演化过程
先在测试环境检验,后面才开始在生产环境进行有预案的演练,最后才有可能进行真正的随机故障演练。
🛎 希望大家不要用一次次的重大事故来让团队成员慢慢理解系统稳定性的重要性。
# 4.16 故障处理原则
问题:如果线上出现告警问题,你会如何处理? 对于线上故障,要有应急响应机制,具体包括:
故障处理原则
- 应急响应的目标:
- 线上故障发生时,以快速恢复服务为第一优先级,避免或减少故障带来的损失,避免或减少故障对客户的影响。
- 线上故障发生后,及时总结经验教训,提高团队的应急水平。
- 线上故障发生前,积极预防,尽可能避免或减少故障发生。
- 应急响应的原则:
- 首要任务,应在第一时间恢复服务。
- 影响重大(比如受影响用户范围大,受损资金多,关键功能受阻等),应立即升级处理。
- 如果不能短时间解决问题,应及时升级处理并尽可能止损。
- 应急响应流程:
- 事前预防、问题监控、事中应对、故障定位、故障解决、事后总结、故障回顾、改进措施
故障处理流程:客户报告故障或监控系统发现故障(故障开始时间)-> 提交故障给相关部门接口人 -> 故障接手&处理 -> 故障处理完毕,故障归档(故障结束时间)-> 确认故障归属记入绩效考核。
# 4.17 应急预案
大型促销备战:
- 成熟团队
- 运营提升设计能力
- 设计提升运营效率
- 完善的流程和规范
- 事件分级与处理流程
- 系统分级与治理规范
- 系统变更流程与规范
- 值班与联络制度
- ……
- 积极预防问题
- 系统评估
- 吞吐能力
- 容量/流量
- 响应速度
- 系统升级
- 扩展/拆分
- 异步化
- 使用缓存
- 跨机房部署
- SLA 规划与确认
- 吞吐能力
- 响应时间
- 可用性
- 降级方案
- 验证
- 线下压测
- 线上局部压测
- 线上军演
- 系统体检
- 系统评估
- 及时发现问题
- 监控/报警
- 系统整体指标
- 系统可用性
- 系统处理能力
- 系统负载
- 数据
- 主业务流程
- 监控/报警
- 迅速决策/处理
- 应急预案
- 扩容
- 流控控制
- 降级
- 故障转移
- 培训
- 演练
- 现场值班
- 明确分工和绩效
- 应急预案
# 4.18 CheckList 保驾护航
发布 CheckList 服务端这边给出了:服务端上线 CheckList 模板,大家可以参考一下。
稳定性 CheckList
历史是最好的老师,总结分析过去发生的事故,并沉淀相关的经验,以此梳理出围绕事故隐患的风险点 CheckList。
# 5. 总结
在写这篇文章前,我认真的整理了市面上大部分的高可用架构的文章,并根据自己的工作经验总结归纳出来,由于高可用设计的范围是在太广,因此每个模块的内容没办法面面俱到,大家有任何补充可以在评论里面评论。