服务高可用总结

作者 Gavin 日期 2019-02-24
服务高可用总结

通过近来工作中的经验与学习,本文总结下自己对服务保障高可用的一些体悟,本次时间有限,找时间需要将每个节点再详细介绍下。

所谓的服务高可用主要是指服务正常运行的时间百分比,业界主要是用 N 个9来衡量。具体体现在服务的接口正确性以及延时上。
以一年拿365天计算(8760小时),得到的可用时间表为:

可用百分比 不可用时间 计算规则
99.9 8.76 h 8760 * 0.001
99.99 52.6 m 8760 * 0.0001
99.999 5.26 m 8760 * 0.00001

保证服务的高可用,主要从两方面入手:

  • 保证服务的健壮性,做好容灾、容错
  • 提高代码质量,规范上线流程,做好测试,减少线上 bug 和事故

容灾、容错

异常输入、输出处理

对于外部输入需要保持严格的不信任,日后你才能保证十分的安心。严格校验输入内容和参数的格式等,如有异常输入,直接抛出 err。
对于服务输出内容,也需要规范化,对上游负责。对于异常的输入输出,需做好日志,以及详细的 err 信息,方便后续的 case跟踪,并做相应的报警监控。

逻辑错误、异常处理

对于逻辑内部出现的错误和异常需要及时上抛,可在上层对错误和异常进行统一处理

重试机制

对于重试也不是针对每个服务有最好,当下游服务出现较大延时时,请求量又比较大的情况,重试则会导致雪崩,所以我们要看下服务的定位是否允许出现访问失败、请求量是否很大。

  1. 针对可以出现失败,请求量很大的服务场景,我们的服务可以做成有损的,这样能保证绝大多数请求是可用的。
  2. 如果服务不允许出现失败的情况,比如涉及到积分、金额等重资产的情况,则需要进行增加重试机制。
    对于重试,我们可以采用以下方案:
    • 需要引入节点管理机制,保证不会重试故障节点,防止造成资源的浪费
    • 记录服务错误率
      • 当错误率过高时可减少重试次数、或者取消重试
      • 错误率变低时可按照请求百分比进行梯度恢复重试,防止增加重试后造成下游请求量激增

超时管理

超时时间我们在工程中一般会控制连接超时和服务超时,一般情况连接超时会控制的较低,下游的响应时长应小于上游的服务超时,否则如果上游有重试的话会导致下游服务雪崩。

节点管理

采用投票机制来决定一个节点是否可用,主要功能如下:

  • 负载均衡
  • 故障节点摘除
  • 故障节点恢复(可用性检查)
  • 最小节点比例保留(防止雪崩)
  • 熔断机制

连接管理

长连接相对短连接在耗时上具有天然的优势。连接管理需要配合节点管理来做。高性能的连接管理需要保持接口的尽可能简单,并发锁粒度尽可能小。

异地多活

异地多活架构里比较重要的是数据一致性,路由控制一致性。

  • 数据一致性

    • 低延时
    • 数据强一致性(或者最终一致性)
      一般来说会在数据底层做一致性,如 mysql 的主从;也可以考虑在业务层做双写(尽量不要用,会侵入服务),双写的模式也可按照单机房或者多机房服务来灵活调整。
      单机房服务比较简单,直接往多个机房数据中心同步数据即可。多机房服务也可以通过 proxy 进行互写,A 机房的数据经过 proxy 直接写到 A 机房,同时 proxy 负责写入 B,…机房,最终每个机房都会有全量的数据。
  • 路由控制一致性
    要求用户不管在 A 机房还是在 B 机房,所请求数据的链路是一致的,最终的访问数据源都是一个单元块。

过载保护

对于系统的过载保护,可以简单总结为:开源节流。
雪崩:雪崩是指当外部访问请求量激增时,超过了服务的承受能力,导致服务请求堆积、延时增大,最终导致服务不可用的现象。一般我们会采取下面几种方式来避免雪崩现象:

降级

对于每个服务而言,都会有核心业务和非核心业务,所谓降级就是在系统压力过大时保证核心业务的正常运行,必要的情况可以牺牲非核心业务的运转。比如交易系统,我们首先要保证订单创建和支付流程的能够走下去,其余业务可以按照优先级进行适当关闭。
所以我们首先需要将服务的模块划分优先级,每个模块增加相应的开关,划定过载等级,当系统过载时,按照过载等级自动关闭相应的功能。当然我们还需要预留一个手动干预的入口。

限流

在无法扩容的情况下,应对激增流量(流量毛刺)的方法之一还有限流。限流常用在发送和接受速率限制上,此处我们是使用在接受速率限制上。工程上常用的几种限流方法:

  • 计数器:一般会在单位时间内设定一个请求峰值,每来一个请求,计数加1,知道到达请求峰值后不再接受请求,当到达单位时间时会将计数清0。
    此方法的缺点是,假设单位时间是1分钟,假设90%流量堆压在最后1秒,则还是有可能会把服务压垮。
  • 滑动窗口:为了应对计数器限流的不足,滑动窗口法将单位时间划分成N个窗口。假设单位时间为1分钟,划分为6窗口,每个窗口为10s。在每个1分钟内的最后一个窗口会把上1分钟的最后一个窗口的流量清0,而不是粗犷的把整个单位时间(1分钟)的流量清0,计算流量是否到达峰值时还是以相近的单位时间内所有窗口的流量和累加,这样就有效避免了计数器法带来的弊端。
    理论上窗口越多,限流会越平滑。
  • 漏桶算法:请求流量会进入一个桶中,桶中的流量会像水一样按照一定的速率露出然后被处理。所以漏桶法能有效限制访问速率,但是无法应对突发流量。
  • 令牌桶算法:前提会有一个桶,按照一定的速率不断的将令牌放入桶中,如果桶中的令牌超过容量,则丢弃。当有 N 个流量突然到来时,如果桶中有超过 N 个令牌,则可以立马取走,不需要等待。这也是令牌桶法和漏桶法的区别。
缓存

缓存主要是应对激增流量对于 DB 的压力考虑的。关于缓存的优点这里不再赘述。

可扩展性,自动化扩缩容

需要结合k8s 等产品进行管理,主要需要关注几个主要的扩缩容条件:CPU、MEM、REQUEST 等。

全链路压测:防患于未然。

主要可以知道系统的瓶颈能力,在容易出现流量激增的场景下可以及时进行扩容。可以在全链路的请求参数中添加压测标识来区分压测流量或者正常流量。

发布

开发

开发过程中做好单元测试和自测,80%的bug 都是由异常引起的,开发人员最需要担心的是服务的异常情况,所以一定要对异常情况做相关处理。
一定不要相信上游传给你的参数,要做必要的参数校验!
一定不要相信上游传给你的参数,要做必要的参数校验!
一定不要相信上游传给你的参数,要做必要的参数校验!

部署

现在各厂都是自动化部署了,这个没有什么特别需要介绍的。

测试

自测

开发人员不仅要完成开发,还要对自己的代码质量负责,不能开发完就丢给测试了。本着中小、非重要功能尽可能自测上线,重大功能才提测的原则!

mock 工具

一个好的 mock 工具是开发人员完成自测的利器!这是我对一个 mockserver 的定义,目前也正在做这个:

  1. 支持web配置
    1. 服务名称可配
    2. 接口名称可配
    3. rpc类型支持:http、thrift …
    4. 校验功能
      1. 参数名
      2. 参数类型
      3. 参数合法范围
      4. header 校验
    5. 返回值
      1. 支持自定义返回结果,支持json配置、结构体配置
      2. 支持随机性返回结果
      3. 支持特定返回结果,返回结果支持导入
    6. 接口的参数支持导入
    7. 支持限流
    8. 支持自定义耗时
  2. 使用方便
    1. 能方便的切换环境
  3. 部署方便
    1. 尽可能的减少依赖,易部署

运维

监控、报警

监控报警是感知服务是否异常的一个主要途径,可以从系统和业务层划分。
系统层面可重点关注:CPU、MEM、磁盘空间、端口存活、是否反复重启、gc等
业务层面可重点关注:QPS、ERROR数、数据时效性等

自动化 case 查询

对于常用的 case 查询,可以进行工具化,提高查询效率,做到一键查询,快速定位。

标准化

日志

日志等级划分

常见的日志等级划分切: INFO、DEBUG、TRACE、WARN、ERROR

打印时机

defer:

  • 上游输入最好打出
  • 输出有必要时可打出

不是所有的出错都需要打上 ERROR 标签,非阻塞性的 err 可以考虑打上 WARN 标签。

错误日志信息字段

需要包含基本字段如下:文件名、函数名、行数、栈信息、traceid、time、详细错误内容

开发规范

这个仁者见仁,智者见智,一个开发小组的做好统一,尽量和公司统一就好。

接口规范

服务内做好统一,以 json 为例将数据可放入 data 层,thrift 可单独匹配一个 data struct。

1
2
3
4
5
{
"errno":0,
"errmsg":"ok",
"data": {}
}