可靠的系统是业务稳定、快速发展的基石。那么,如何做到系统高可靠、高可用呢?下面从技术方面介绍几种提高系统可靠性、可用性的方法。 扩展扩展是最常见的提升系统可靠性的方法,系统的扩展可以避免单点故障,即一个节点出现了问题造成整个系统无法正常工作。换一个角度讲,一个容易扩展的系统,能够通过扩展来成倍的提升系统能力,轻松应对系统访问量的提升。 一般地,扩展可以分为垂直扩展和水平扩展:
可扩展性系数 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、接口响应时间等。 降级业务降级,是指牺牲非核心的业务功能,保证核心功能的稳定运行。简单来说,要实现优雅的业务降级,需要将功能实现拆分到相对独立的不同代码单元,分优先级进行隔离。在后台通过开关控制,降级部分非主流程的业务功能,减轻系统依赖和性能损耗,从而提升集群的整体吞吐率。 降级的重点是:业务之间有优先级之分。降级的典型应用是:电商活动期间关闭非核心服务,保证核心买买买业务的正常运行。 业务降级通常需要通过开关工作,开关一般做成配置放在专门的配置系统,配置的修改最好能够实时生效,毕竟要是还得修改代码发布那就太 low 了。开源的配置系统有阿里的diamond、携程的Apollo、百度的disconf。 降级往往需要兜底方案的配合,比如系统不可用的时候,对用户进行提示,安抚用户。提示虽然不起眼,但是能够有效的提升用户体验。 熔断谈到熔断,不得不提经典的电力系统中的保险丝,当负载过大,或者电路发生故障时,电流会不断升高,为防止升高的电流有可能损坏电路中的某些重要器件或贵重器件,烧毁电路甚至造成火灾。保险丝会在电流异常升高到一定的高度和热度的时候,自身熔断切断电流,从而起到保护电路安全运行的作用。 同样,在分布式系统中,如果调用的远程服务或者资源由于某种原因无法使用时,没有这种过载保护,就会导致请求阻塞在服务器上等待从而耗尽服务器资源。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。而这种过载保护就是大家俗称的熔断器(Circuit Breaker)。 下面这张图,就是熔断器的基本原理,包含三个状态: 目前比较流行的降级熔断框架,是由 Netflix 开源的 Hystrix 框架。 发布相关模块级自动化测试众所周知,一个项目上线前需要经历严格的测试过程,但是随着业务不断迭代、系统日益复杂,研发工程师、产品经理、测试工程师等都在测试过程中投入了大量精力,而一个个线上故障却表明测试效果并不是那么完美。究其原因,目前的测试工作主要存在两方面问题: 解决上述问题可以使用模块级自动化测试。具体方案是:针对某一模块,收集模块线上的输入、输出、运行时环境等信息,在离线测试环境通过数据mock模块线上场景,回放收集的线上输入,相同的输入比较测试场景与线上收集的输出作为测试结果。 模块级自动化测试通过简化复杂系统中的不变因素(mock),将系统的测试边界收拢到改动模块,将复杂系统的整体测试转化为改动模块的单元测试。主要适用于系统业务回归,对系统内部重构场景尤其适用。 具体如何收集线上数据呢?有两种方法: 更多细节,可以查看下面参考文献中的文章:Qunar 自动化测试框架 ARES。 灰度发布 & 回滚单点和发布是系统高可用最大的敌人。一般在线上出现故障后,第一个要考虑的就是刚刚有没有代码发布、配置发布,如果有的话就先回滚。线上故障最重要的是快速恢复,如果等你细细看代码找到问题,没准儿半天就过去了。 为了减少发布引起问题的严重程度,通常会使用灰度发布策略。灰度发布是速度与安全性作为妥协。他是发布众多保险的最后一道,而不是唯一的一道。在这篇文章来自 Google 的高可用架构理念与实践里提到:
发布之前必须制定详细的回滚步骤,回滚是解决发布引起的故障的最快的方法。 其他
总结
在这篇文章中,我们探讨了一些提供系统可靠性的技术方案。关于高可用的更多问题可以看看这篇文章 陈皓:关于高可用的系统,这篇文章的核心在于提出:
参考资料 |
|