可靠的系统是业务稳定、快速发展的基石。那么,如何做到系统高可靠、高可用呢?下面首先讲一下高可用需要面临的常见问题,再从技术方面介绍几种提高系统可靠性、可用性的方法。
下面的表格里,列出了高可用常见的问题和应对措施。
问题 | 典型案例 | 增大 MTBF | 减小 MTTR |
---|---|---|---|
程序、配置 Bug | 程序、配置 Bug | 提升研发、测试质量,灰度发布 | 监控告警、快速回滚 |
机器、机房故障 | 宕机、机房断电 | 硬件冗余、多机房 | 自动故障转移,切流到其他冗余机器、机房 |
突发流量 | 上游系统异常重试、外部攻击 | 上游系统容错调度防雪崩、流量配额、防攻击、防抓取 | 其他同容量不足 |
容量不足 | 主流程容量不足 | 容量规划、容量预警 | 限流、降级、熔断弱依赖、快速扩容 |
依赖服务故障 | 依赖服务失败率高、超时严重 | 弱依赖降级解耦,强依赖递归使用前述方法增强可靠性 | 熔断弱依赖 |
扩展是最常见的提升系统可靠性的方法,系统的扩展可以避免单点故障,即一个节点出现了问题造成整个系统无法正常工作。换一个角度讲,一个容易扩展的系统,能够通过扩展来成倍的提升系统能力,轻松应对系统访问量的提升。
一般地,扩展可以分为垂直扩展和水平扩展:
可扩展性系数 scalability factor 通常用来衡量一个系统的扩展能力,当增加 1 单元的资源时,系统处理能力只增加了 0.95 单元,那么可扩展性系数就是 95%。当系统在持续的扩展中,可扩展系数始终保持不变,我们就称这种扩展是线性可扩展。
在实际应用中,水平扩展最常见:
隔离,是对什么进行隔离呢?是对系统、业务所占有的资源进行隔离,限制某个业务对资源的占用数量,避免一个业务占用整个系统资源,对其他业务造成影响。
隔离级别按粒度从小到大,可以分为线程池隔离、进程隔离、模块隔离、应用隔离、机房隔离。在数据库的使用中,还经常用到读写分离。
在软件工程中,对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高,维护成本越高,因此对象的设计应使模块之间的耦合度尽量小。在软件架构设计中,模块之间的解耦或者说松耦合有两种,假设有两个模块A、B,A依赖B:
为什么要做限流呢?举一个生活中的例子,大家早上上班都要挤地铁吧,地铁站在早高峰的时候经常要限制客流,为什么呢?有人会觉得这是人为添堵。真是这样吗?如果不执行客流控制,大家想想会是什么场景呢?站台到处都挤满了乘客,就算你使出洪荒之力也不一定能顺利上车,且非常容易引发肢体碰撞,造成冲突。有了客流控制之后,地铁站才能变得秩序井然,大家才能安全上地铁。
一个系统的处理能力是有上限的,当服务请求量超过处理能力,通常会引起排队,造成响应时间迅速提升。如果对服务占用的资源量没有约束,还可能因为系统资源占用过多而宕机。因此,为了保证系统在遭遇突发流量时,能够正常运行,需要为你的服务加上限流。
常见的限流算法有:漏桶、令牌桶、滑动窗口计数。
按照计数范围,可以分为:单机限流、全局限流。单机限流,一般是为了应对突发流量,而全局限流,通常是为了给有限资源进行流量配额。
按照计数周期,可以分为:QPS、并发(连接数)。
按照阈值设定方式的不同,可以分为:固定阈值、动态阈值。
下面这张图,是漏桶的示意图。漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大时,会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。漏桶算法(Leaky Bucket)是网络世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。
漏桶算法可以使用 Redis 队列来实现,生产者发送消息前先检查队列长度是否超过阈值,超过阈值则丢弃消息,否则发送消息到 Redis 队列中;消费者以固定速率从 Redis 队列中取消息。Redis 队列在这里起到了一个缓冲池的作用,起到削峰填谷、流量整形的作用。
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。桶里能够存放令牌的最高数量,就是允许的突发传输量。
Guava 中的限流工具 RateLimiter,其原理就是令牌桶算法。
计数法是限流算法里最容易理解的一种,该方法统计最近一段时间的请求量,如果超过一定的阈值,就开始限流。在 TCP 网络协议中,也用到了滑动窗口来限制数据传输速率。
滑动窗口计数有两个关键的因素:窗口时长、滚动时间间隔。滚动时间间隔一般等于上图中的一个桶 bucket,窗口时长除以滚动时间间隔,就是一个窗口所包含的 bucket 数目。
滑动窗口计数算法的实现,可以查看这篇文章:降级熔断框架 Hystrix 源码解析:滑动窗口统计。
一般情况下的限流,都需要我们手动设定限流阈值,不仅繁琐,而且容易因系统的发布升级而过时。为此,我们考虑根据系统负载来动态决定是否限流,动态计算限流阈值。可以参考的系统负载参数有:Load、CPU、接口响应时间等。
详细内容请看:基于系统负载的动态限流 dynamic-limiter。
业务降级,是指牺牲非核心的业务功能,保证核心功能的稳定运行。简单来说,要实现优雅的业务降级,需要将功能实现拆分到相对独立的不同代码单元,分优先级进行隔离。在后台通过开关控制,降级部分非主流程的业务功能,减轻系统依赖和性能损耗,从而提升集群的整体吞吐率。
降级的重点是:业务之间有优先级之分。降级的典型应用是:电商活动期间关闭非核心服务,保证核心买买买业务的正常运行。
业务降级通常需要通过开关工作,开关一般做成配置放在专门的配置系统,配置的修改最好能够实时生效,毕竟要是还得修改代码发布那就太 low 了。开源的配置系统有阿里的diamond、携程的Apollo、百度的disconf。
降级往往需要兜底方案的配合,比如系统不可用的时候,对用户进行提示,安抚用户。提示虽然不起眼,但是能够有效的提升用户体验。
谈到熔断,不得不提经典的电力系统中的保险丝,当负载过大,或者电路发生故障时,电流会不断升高,为防止升高的电流有可能损坏电路中的某些重要器件或贵重器件,烧毁电路甚至造成火灾。保险丝会在电流异常升高到一定的高度和热度的时候,自身熔断切断电流,从而起到保护电路安全运行的作用。
同样,在分布式系统中,如果调用的远程服务或者资源由于某种原因无法使用时,没有这种过载保护,就会导致请求阻塞在服务器上等待从而耗尽服务器资源。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。而这种过载保护就是大家俗称的熔断器(Circuit Breaker)。
下面这张图,就是熔断器的基本原理,包含三个状态:
目前比较流行的降级熔断框架,是由 Netflix 开源的 Hystrix 框架。
众所周知,一个项目上线前需要经历严格的测试过程,但是随着业务不断迭代、系统日益复杂,研发工程师、产品经理、测试工程师等都在测试过程中投入了大量精力,而一个个线上故障却表明测试效果并不是那么完美。究其原因,目前的测试工作主要存在两方面问题:
解决上述问题可以使用模块级自动化测试。具体方案是:针对某一模块,收集模块线上的输入、输出、运行时环境等信息,在离线测试环境通过数据mock模块线上场景,回放收集的线上输入,相同的输入比较测试场景与线上收集的输出作为测试结果。
模块级自动化测试通过简化复杂系统中的不变因素(mock),将系统的测试边界收拢到改动模块,将复杂系统的整体测试转化为改动模块的单元测试。主要适用于系统业务回归,对系统内部重构场景尤其适用。
具体如何收集线上数据呢?有两种方法:
更多细节,可以查看下面参考文献中的文章:Qunar 自动化测试框架 ARES。
单点和发布是系统高可用最大的敌人。一般在线上出现故障后,第一个要考虑的就是刚刚有没有代码发布、配置发布,如果有的话就先回滚。线上故障最重要的是快速恢复,如果等你细细看代码找到问题,没准儿半天就过去了。
为了减少发布引起问题的严重程度,通常会使用灰度发布策略。灰度发布是速度与安全性作为妥协。他是发布众多保险的最后一道,而不是唯一的一道。在这篇文章来自 Google 的高可用架构理念与实践里提到:
做灰度发布,如果是匀速的,说明没有理解灰度发布的意义。一般来说阶段选择上从 1% -> 10% -> 100% 的指数型增长。这个阶段,是根据具体业务不同按维度去细分的。
这里面的重点在于 1% 并不全是随机选择的,而是根据业务特点、数据特点选择的一批有极强的代表性的实例,去做灰度发布的小白鼠。甚至于每次发布的 第一阶段用户(我们叫 Canary/金丝雀),根据每次发布的特点不同,是人为挑选的。
发布之前必须制定详细的回滚步骤,回滚是解决发布引起的故障的最快的方法。
为什么要做故障演练呢?就跟在测试业务功能时,不仅要测试正常的请求能否正确处理,也要测试异常的请求能否得到适当的处理一样。站在全局的角度看,我们也希望保证某个机器或某个服务挂掉时,尽量不影响系统整体的可用性,技术上要靠无状态服务、冗余部署、降级等。实际中如何测试这样的异常情况呢?
Netflix 开源了一个工具 Chaos Monkey,这是一套用来故意把服务器搞下线的软件,可以用来测试系统的健壮性和恢复能力。
在文章阿里如何做到百万量级硬件故障自愈里,介绍了如何实现硬件故障预测、服务器自动下线、服务自愈以及集群的自平衡重建,真正在影响业务之前实现硬件故障自动闭环策略,对于常见的硬件故障无需人工干预即可自动闭环解决。
AWS 有一个 CloudTrail 系统,专门记录重大活动事件,可以简化安全性分析、资源更改跟踪和问题排查工作。系统发布、配置变更是引发故障的一大因素,微服务化的系统架构里,有时某个底层系统的变更,引起反映、出现故障的往往是上层直接面对用户的系统。有了事件系统,出现故障后,可以快速查看在故障时间点,相关联系统是否有变更,是否是引起故障的根本原因?
事件系统的出现,可以帮助应用开发者快速定位“底层系统变更引发上层系统异常”这一类故障的根本原因。
技术 | 解决什么问题 |
---|---|
扩展 | 通过冗余部署,避免单点故障 |
隔离 | 1. 避免业务之间的相互影响 2. 机房隔离避免单点故障 |
解耦 | 减少依赖,减少相互间的影响 |
限流 | 遇到突发流量时,保证系统稳定 |
降级 | 牺牲非核心业务,保证核心业务的高可用 |
熔断 | 减少不稳定的外部依赖对核心服务的影响 |
自动化测试 | 通过完善的测试,减少发布引起的故障 |
灰度发布 | 灰度发布是速度与安全性作为妥协,能够有效减少发布故障 |
自动化运维 | 出现故障时,通过自动化运维来进行解决,肯定比人工干预要快,可以有效减少故障时间 |
事件系统 | 快速定位故障根本原因 |
在这篇文章中,我们探讨了一些提供系统可靠性的技术方案。关于高可用的更多问题可以看看这篇文章 陈皓:关于高可用的系统,这篇文章的核心在于提出:
5个9的SLA在一年内只能是5分钟的不可用时间,5分钟啊,如果按一年只出1次故障,你也得在五分钟内恢复故障,让我们想想,这意味着什么?
如果你没有一套科学的牛逼的软件工程的管理,没有牛逼先进的自动化的运维工具,没有技术能力很牛逼的工程师团队,怎么可能出现高可用的系统啊。
是的,要干出高可用的系统,这TMD就是一套严谨科学的工程管理。