背景
故有的思维会影响创新,在传统的软件设计考虑高可靠性,主要方法论是”防“,处处保护,让系统的每一处能长时间运行,不中断地提供服务。事实上电信级高可用性(HA)也只能宣称达到5个9,这意味着一年也就只有5分半钟的中断时间。但每增加一个9却实施成本非常地高,有些是建立在硬件可靠基础之上,并且不少是实验数据或理论上支持。传统的思维认识,在泥沙上建房子不可靠的。但软件架构设计,即完全不一样,在不可靠的基础设施上构建上可靠的系统,那才是真正NB的。
依稀记得云计算刚出来时,大家都是持怀疑态度:性能下降的虚拟化技术、安全不可控的网络、变化复杂的资源管理,在其上如何构建可靠稳定的软件系统?事实上,Netflix完全基于AWS云基础设施,认为都有可能发生任何的故障(Failure),更何况资源也不掌握在自己手上。Netflix基于Design for Failure
理念却构建出用户无感知的高可用系统,支撑他的业务飞速发展。事实上,故障无所不在,尤其是在云计算环境中:
- 资源层次:电能失效,整个数据中心不可用;部分计算失效,网络不通,存储IO高等
- 应用层次:资源泄露;软件Bug;系统处理能力不足等
- 数据层次:数据丢失;数据不一致等
概念解读
既然故障不可避免,何不让故障尽早的暴露,尽快的恢复。设计时针对故障场景而设计,一切假定在故障失效下如何处理,局部的失效不影响整体的可用性。这就是Design for Failure
的核心理念。这个设计理念其实也跟人类社会很像:一个人的细胞代谢,只要有新的细胞补上就行;一个组织中,高度细分工作,几个人的离开,不影响整体的运转。Design for Failure
不仅仅是高可用性设计,而是一种新的设计理念,有别于传统,通过单点的可靠性达到整体的高可用性。以Netflix公布的数据来看,每个EC2实例平均生命周期只有36个小时,每个单点不断地重生,才能达到整体的高可用性。其关键实施要点总结如下:
- 容错:当系统中出现了各种故障时,系统能够自动隔离故障而不影响系统对外的服务质量。
- 冗余:提供系统冗余配置,当系统发生故障时,冗余的快速介入并承担已发生故障的工作。
以一个运行在云环境中的应用为例,Design for Failure
理念需要按如下步骤来考虑:
- 每个应用程序组件必须部署在冗余的云组件/服务上,有很少或没有失败的共同点,即不存在单点故障;
- 每个应用组件必须对基础设施不作任何假设,它必须能够在不停机的情况下适应基础设施的变化;
- 每个应用程序组件应该是分区容忍,换句话说,它应该能够生存的网络延迟(或通信损失)的节点上;
- 借助于自动化工具,必须能编排应用程序,以便响应失败或其他基础设施的变化等等。
案例分析
一个单点的故障,我们可能针对性地很容易解决,这可能是头痛医头的做法。但一个系统软件往往没有那么简单,举例来说,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。这就是我们常说的雪崩效应
。所以我们非常有必要分析系统中的各种依赖关系。不同的层次来Design for Failure
,不同的技术组合来解决问题。
以Netflix的系统架构来简单分析一下,看它是如何分层解决问题的:
接入层:
AWS ELB
典型的部署架构都是多地区(Region)、多可用区(Zone)的部署。负责四层负载分发,支持跨Region调用,它解决是当一个Region不可用的分发。
Zuul
Zuul负责七层分发,提供动态路由,监控,弹性,安全等。Zuul可以通过加载动态过滤机制,从而实现以下各项功能:
- 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求;
- 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论;
- 动态路由: 以动态方式根据需要将请求路由至不同后端集群处;
- 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平;
- 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求;
- 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群;
- 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近;
- 金丝雀测试:金丝雀版本实现精确路由;
- 故障注入:结合故障注入工具,从前端自动注入故障;
服务层
Eureka
Eureka为所有Netflix服务提供服务注册集中管理,当然它也是可以分Zone分Region集群部署的。它与Zookeeper不同是:Zookeeper侧重于CP,而Eureka侧重于AP;服务注册信息支持跨Region的复制。
- Eureka服务端用作服务注册,提供服务实例信息注册与同步;
- Eureka客户端用用服务发现,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持。
Ribbon
由于Eureka是非强一致性,服务实例状态并非是实时性,服务调用可能失败或超时。所以Ribbon作为客户端组,配合Eureka一起使用,作为服务路由均衡的补充。
- Ribbon客户端提供一系列完善的配置选项,比如连接超时、重试、重试算法等,
- Ribbon内置可插拔、可定制的负载均衡组件,支持多种均衡策略:简单轮询负载均衡;加权响应时间负载均衡;区域感知轮询负载均衡;机负载均衡。
在选择服务器时,该负载均衡器会采取如下步骤:
- 负载均衡器会检查、计算所有可用区域的状态。如果某个区域中平均每个服务器的活跃请求已经达到配置的阈值,该区域将从活跃服务器列表中排除。如果多于一个区域已经到达阈值,平均每服务器拥有最多活跃请求的区域将被排除。
- 最差的区域被排除后,从剩下的区域中,将按照服务器实例数的概率抽样法选择一个区域。
- 从选定区域中,将会根据给定负载均衡策略规则返回一个服务器。
Hystrix
Hystrix提供分布式系统使用,提供延迟和容错功能,隔离远程系统、访问和第三方程序库的访问点,防止级联失败,保证复杂的分布系统在面临不可避免的失败时,仍能有其弹性。
- 隔离模式:简单说就是为每个依赖调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
- 熔断模式:目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
上述两种模式的实施,是服务速错,服务降级的基础。
数据层
EVCache
VCache是一个数据缓存服务,专门为Netflix的微服务提供低延迟,高可靠性的缓存解决方案。它是基于memcached的内存存储,专门为云计算优化,适合对强一致性没有必须要求的场合。它不需要处理全局锁,群体读写,事务更新,部分提交和回滚,和其他一些分布式一致性的复杂设计。
- 跨区可用:一个地区的的会员切换到另外一个地区,会在新的地区缓存中没有老地区的数据,称为cold cache,缓存会保存着重新计算需要的临时数据,这些数据如果从持久层存储获得将会非常昂贵,所以这种数据写入到本地缓存,并必须复制到所有地区的缓存中,以便服务于各个地区会员使用。
- 复制延迟:在跨区域复制变慢的情况下,不会影响性能和本地缓存的可靠性,所有复制都是异步的,复制系统能够在不影响本地缓存操作情况下悄悄地短时间中断。不需要一个完美的复制系统,可以接受EVcache一定限度的延迟和不一致,只要能满足应用和会员的需要就行。
其它
Cassandra是一个NoSQL数据库,是购买一家商业公司的服务,主要是用于各种Session的存储,并且支持跨区的同步复制。S3主要用于数据的备份。
总结
Netflix在每层上都考虑了失效,如何处理,但它每一层都没有做到尽善尽美,但不同层次的组合,却做到几乎完美的高可用性。当然Netflix构建高用性的系统还不只是我上面所列出的组件或工具。列出关键的部分是为了表达出Design for Failure
的理念是:故障不可避免,可以分层次的设计,通过多个技术方案组合应用,从而达到故障隔离,冗余恢复,实现整体的高可用性。