前言

如果说一款互联网产品的界面和体验是吸引用户入坑的基础,那么产品的整体稳定便决定了客户后续对该产品的使用体验中是否足够如丝般顺滑,甘愿掏腰包付费。系统的稳定性对于任何一家互联网公司都非常重要,没有人会为了一款经常三天两头宕机的产品买单,也不会有开发、运维会乐意为了这些故障去背锅。下面我以自己这几年来的经历和思考,抛砖引玉,聊聊系统高可用实践的各个方面,将自己所思所想分享给大家。

墨菲定律,指的凡是有可能会出错的事情就一定会出错,其作用上至于个人、组织、国家,下至于生活、工作、系统都逃不开。这世上没有一个百分百没bug的系统,任何一个线上的服务能够长期正常运行,但只要把时间拉的足够长,任何可能发生的概率便是必然。我们本身是没法保证任何系统100%的可用性,但是我们还是可以采用一些方法来提升可用性。

系统的可用性定义

计算机系统的可用性定义为:MTTF/ (MTTF+MTTR) *100%,计算机系统的可用性用平均无故障时间( MTTF)来度量,即计算机系统平均能够正常运行多长时间,才发生一次故障,系统的可用性越高,平均无故障时间越长。可维护性用平均维修时间(MTTR)来度量,即系统发生故障后维修和重新恢复正常运行平均花费的时间,系统的可维护性越好,平均维修时间越短。

那么,我们可以看出,提高可用性无非两个途径:

  1. 提高MTTF,少出故障
  2. 减少MTTR,快速修复故障

7*24小时无间断服务可用性:

目前市面上大的公有云厂商对外提供的服务等级协议(SLA)可达到4个9,但其实按照每年公有云时不时爆出的故障新闻来看,真正要达到4个9的很少,这里面主要考虑的是投入和产出比,即成本和效率。

从上图可以看出,随着可用性的提高,投入的成本会呈现指数级增长,这就需要我们不断去权衡这两者的比例了。不同时间不同产品对此定义的规则比例不同,需要具体问题具体分析。例如,以时间维度来看,新产品前期用户少,那么在这块的软、硬件投入成本会少,相应对高可用的要求便不会高,而以产品维度来看,客户密集使用产品如商城则需要重视并持续投入人力进行系统优化和软、硬件服务扩容,说白了其实也是一种商业策略,背后更多的是从产出利润考虑。

如何提升系统高可用

在提升整体系统高可用方面我们有哪些手段,在接下来的总结介绍中我将会围绕该图结合公司具体案例进行介绍,欢迎童鞋们拍砖。

服务无状态

服务无状态化,其判断依据是两个来自相同发起者的请求在服务器端是否具备上下文关系,本质指的是服务在开发部署过程中我们可以无视服务自身内部的逻辑,在系统到达瓶颈时直接进行服务实例扩容(例如扩容tomcat/svr等),均无需考虑用户侧的请求是否请求到哪台实例。

而与此相对的是服务的有状态化,即当我们进行实例扩容时要特别注意正在请求的用户不能请求到新的实例,例如该tomcat服务存有客户的session,一旦更换实例其session将失败,于用户侧的体验则是退出重新登录,影响用户体验等等。

服务要设计为无状态的,主要从下面几个维度来考虑的

  1. 无锁事务编程

注意,事务无锁编程并不适合所有业务,各业务也需要根据自身的实际情况进行评估与研发部署。 无锁事务的实现方式之一是采用队列串行化的事务执行,这样可以保证在所有事务请求中均保证始终只有一个事务在处理,其二是采用消息队列的方式来实现数据的最终一致性,消除锁带来的竞争和水平扩展限制。

  1. 模块功能拆分

即将数据信息统一放在一个独享的地方供其他业务服务调用,即抽离独立服务模块,这样可以保证该数据的单一功能维护以及业务的上下文信息解耦,利于业务服务水平扩展。

例如在公司业务中因为各管理态各web均需要判断用户会话信息,为了保证nginx端转发web的负载均衡,针对用户登录会话的数据均统一由SessionSvr收归维护管理,web扩容缩容均对用户登录会话不会有任何影响。

负载均衡

上文提到的nginx负载均衡则是针对高可用系统架构中单点服务出现问题可以将影响降到最低,如果说单点故障对业务的影响面是100%的话,那么多实例部署的负载均衡对业务的影响可以减少到1/n,同时也大大减少了后端服务的压力,这就决定了负载均衡的算法及如何做到服务的负载均衡了(注意以下介绍均是软负载均衡,硬件级的如F5不做介绍)。

  1. Dns负载均衡

该层属于运维层面的领域,其原理是将客户端需要访问的域名解析到不同的数据中心不同的线路上,比如通过IP地理信息数据库解析到最近的线路,或者权衡不同线路的繁忙度解析到空闲的线路等等。典型的应用如CDN,通过DNS负载均衡,将不同地区的用户解析到离该区域最近的地段,加快用户访问速度,减少后端压力。在公司的服务案例中各个资源域名均采用CDN服务,而客户绑定的网站线路则解析到不同的机器IP来分摊不同的机器压力,减少故障影响面,提升客户访问速度。

  1. Lvs负载均衡

Lvs负载均衡属于TCP/IP四层负载均衡,其实现原理是采用VIP路由转发的模式将客户端请求转发到不同的真实后端IP来实现负载均衡。目前在公司业务里,针对的是CDN回源的线路进行LVS负载均衡部署,因为属于TCP/IP网络层面的技术实现,在此不做过多介绍。

  1. Nginx负载均衡

属于应用层的负载均衡,也是业务需求最多的负载均衡部署,nginx是目前互联网公司应用最多的高性能http反向代理服务,尤其在高并发下nginx依然保持着较低的内存占用和较高的响应速度,在大型网络部署中均是首选。

Nginx支持不同的负载均衡策略,负责将客户端请求转发到对应的web服务器,在业务web性能不足时进行web扩容部署并配置nginx多机轮询。

  1. 负载均衡策略

在大型网络架构上,负载均衡有多种方式,而负载均衡算法有很多种,其中:

  • 轮询负载均衡:

基本上是最简单的负载均衡算法,基本思路就是对所有的服务器节点按顺序分配,每个服务器的概率是等同的,这种情况适合于服务器的性能等指标一样的情况,也是目前公司在用的基本策略。

  • 随机负载均衡:

通过随机算法从服务器列表中随机选取一台服务器进行访问。由概率论可以得知,随着客户端调用服务端的次数增多,其实际效果趋近于平均分配请求到服务端的每一台服务器,也就是达到轮询的效果。

  • hash负载均衡:

采用hash算法的负载均衡,其主key可以为请求IP,用户AID,URL等,通过hash计算得出的hash值和总权重的余数作为挑选server的依据,该算法好处之一便是可以将某一相同特征的请求只固定特定后端服务器处理。

  • 最小负载负载均衡:

当将请求发送到某个服务端,该服务端活跃数就+1,每完成1个请求,活跃数就减1,之后每次请求分配的时候,先看当前活跃数。活跃数最小的优先选;若活跃数相同,按权重随机分配。

故障转移

对于一个要求高可用和高稳定性的集群系统来说,故障转移是其中必须设计实现的一环。以公司mysql故障时的处理为例,当系统无法做到自动故障容灾时,一旦发生故障,那么就需要人员及时介入,将故障的节点摘除并替换成可用的节点以保障服务的继续可用,但这样会带来几个问题,一是出现故障时的及时响应问题,一旦出现故障人员不在现场,那么恢复时间将延长到小时级,这对很多业务将是致命的;二是对技术人员的要求高,需要及时知道定位问题并且了解公司业务进行故障切换。

故障转移(Failover)则是采用系统监控和故障时自动转移的方式,当系统监控到某个节点故障时,通过某些机制将该故障节点自动摘除,并将备用节点顶上,以此快速恢复业务,后续人员排查问题时也有足够的时间去排查定位具体问题。

柔性降级

假设很不幸,某挖掘机不小心挖断了机房某条网线,系统的可用服务节点都挂了,这时候怎么办?

相信直接抛出一个错误页面对于客户和产品体验来说都是不愿看到的,一个产品的合理设计是在当后端有故障时,对于客户的产品故障体验感知是尽量为0,这就要求我们要对系统区分哪些是核心功能,哪些是次要功能,前期做好资源倾斜和隔离,后期在故障发生做出取舍,当然这也是目前我们产品设计所欠缺的。

  1. 核心可用

区分产品哪些是核心功能,哪些是次要功能决定着优先展现在用户面前的是什么。核心观点是在前期研发中需要投入资源优先保证核心服务的稳定,次要服务故障时不影响系统主流程的执行。这是至关重要的,比如,当一个简单的客户评论展现服务下线时,其产品不应该直接报错,而是有选择的关闭评论功能,于用户侧而言则仅仅是无法查看或者发表评论而已,不影响客户的商品下单。

那么系统功能在设计之初就要如何考虑到呢?

  • 前后端分离,采用异步加载数据(例如评论)
  • 近源存储数据,后端异常时采用近源缓存数据展现
  • 前端预埋备用方案,当主方案不可用时采用备方案,参考httpdns思路
  1. 降级开关

在商城交易下单环节,可能需要调用A、B、C三个接口来完成,但是其实A和B是必须的,C只是附加的功能(例如在下单的时候做一下推荐),可有可无,在平时系统没有压力,容量充足的情况下,调用下没问题,但是用户秒杀促销大促环节,系统CPU和内存已经满负荷了,这时候其实完全可以不去调用C接口。怎么实现这个呢?改代码上线这时候已经不实际了,此时便需要有一个统一的开关对特定的服务进行降级处理,减少服务的资源消耗。

  1. 服务熔断

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用,这种牺牲局部,保全整体的措施就叫做熔断。

以下面业务模型为例子:

起初,在正常的用户请求下所有服务的调用均保持稳定运行,然而当外网请求出现网络顺应长时,或者在某个用户在高并发下触发的大量请求涌进来,导致服务瞬间无法应对大量的用户请求,所有线程均忙于处理该请求,无法再响应其他正常用户的请求,这种因为单个服务接口故障导致的业务受损会把影响面扩大到整体客户业务群,严重的会直接导致所有请求阻塞,用户和系统不断刷新重试进而造成系统雪崩现象。

服务熔断机制在于系统当检测到某个请求qps过高或者请求耗时过长时直接进行数据限流或者丢包的机制来保证大部分客户都能够正常请求,而不会直接击垮后端服务,这便是熔断的主要机制,当然针对后端网络请求采用异步也能够提供服务吞吐率,降低服务负载的主要手段。

结语

任何一套完备的系统,所有服务模块均是一环套一环,环环相扣的。短板效应在此高可用高性能要求下显得尤其显眼,这也就要求我们在设计一套高可用系统时必须全面去了解业务背后的架构及故障风险。而往往每套系统的解决方案也并不是一刀切直接套用,每个业务背后都有其产品特点,需要做到具体问题具体分析,具体场景具体解决。很少有能够直接解决问题的银弹,这都需要要求我们不断去深入业务模块,不断思考。