提高可用性,应该从全局,而非单点技术考虑(包括研发流程、团队文化等),提升可用性是一个过程,需要长时间的积累。文章简要介绍高可用架构设计关键点,希望对在做系统架构设计的新同学有一点帮助,能够对系统可用性有个整体认知

1.概述

架构设计一个处理软件复杂度的过程,同时应该遵循TradeOff原则,结合项目实际情况落地增强系统可用性

1.1. 可用性 vs 可靠性

  • 可用性(N个9表述): 系统可用的时间,以丢失的时间为驱动,公式: PA=Uptime/(Uptime+Downtime)
  • Uptime: 系统可用时间
  • Downtime: 系统不可用时间
  • 可靠性: 系统无失效时间间隔,以发生的失效个数作为驱动,公式: PA=MTBF/(MTBF+MTTR)
  • MTBF: 平均故障时间(mean time between faiulre)
  • MTTR: 平均故障修复时间(mean time to repair)

可用性等级:

等级几个9年停机时长常用技术
基本可用99%87.6h负载均衡
较高可用99.9%8.8h灰度发布、快速回滚、自动化发布
高级可用99.99%53min微服务、数据库、缓存集群、容错、监控、弹性伸缩
极高可用99.999%5min异地多活、智能运维

1.2. 可用性减低的原因

  • 发布: 服务升级、数据迁移导致服务可能中断,80%的故障是由于发布造成的
  • 故障: 服务Bug、系统宕机、内存溢出、网络波动等导致服务可能中断
  • 压力: 突发事件导致服务处理不过来,导致服务中断
  • 外部依赖: 外部依赖服务故障,导致调用异常

1.3. 提升高可用原则

  • 20/10/5原则,提升系统架构高可用的上限
  • Design for fail原则,提升系统韧性
  • 知己知彼原则,提升系统透明度,识别系统潜在风险,提前做好预案

2. 服务发布

思考切尔诺贝利事件,流程操作规范的重要性

发布关注点:

  • 发布流程CheckList、CR
  • 发布策略: 错峰、灰度流量逐步放开
  • 回滚策略: 异常问题及时回滚
  • 运维值守

2.1. 成熟机制和流程

2.1.1. 蓝绿部署

部署蓝绿两套集群,通过负载均衡关联,新旧蓝绿交替滚动发布,回退简单,负载均衡切换到旧版本即可(当存在数据隔离后,实际情况操作比较复杂),相关特点:

  • 自动化基础设施依赖
  • 全面监控
  • 两套环境隔离,有相互影响风险(比如: 存储服务无法很好蓝绿处理)
  • 难点数据结构变更,如何同步数据,故障时候,如何回滚
  • 支持流量快速切换(实际有困难)

2.1.2. 灰度发布/金丝雀发布

  • 流程:从负载均衡剔除节点(流量踢干净)、节点升级、自动化接口测试、加入负载均衡、监控故障、逐步灰度其他节点
  • 意义:减少故障范围、尽早用户反馈和数据收集
  • 灰度控制:内部->外部1%->5%->10%->全网

3. 容错设计 Design for failure

容错设计是为了确保错误发生时,能够从容应对

3.1. 消除单点

  1. 服务冗余设计
  2. 服务无状态
  3. 故障转移(比如nginx+keepalive、redis哨兵、mysql主从切换)

3.3. 服务分级

产品功能梳理核心流程,找到核心服务,划分等级,确认服务关键程度

服务级别划分依据示例
1级服务核心业务流程,一旦故障业务遭受重大损失注册、登录、支付
2级服务用户体验影响严重,一旦故障,关键业务还可用,用户体验影响严重搜索、评论
3级服务用户体验影响轻微,一旦故障,正常流程不受影响,不常用的功能不可用个人信息
4级服务多为管理维护,用户不受影响,用户不会直接访问统计、排行

3.4. 服务降级设计

  1. 服务降级前置条件是先梳理清楚服务核心等级,预先配置中心定义好降级开关
  2. 降级方式
    • 关闭功能: 业务JS控制少掉了某个功能
    • 请求短路:返回缓存数据
    • 简化流程:注册成功的提示短信不发
    • 延迟执行: 比如定时任务
    • 关闭定时任务
    • 低精确度返回:比如报名人数、在线人数

3.5. 超时重试

主调服务请求被动服务,可能存在成功、失败、超时3类状态,频繁发起重试,可能加重消费者负担,造成更严重的事故。

重试策略的关键因子:

  1. 超时时间: 包括调用超时时间、下游服务处理时间
  2. 重试总次数(retrycount):多次重试可能对下游造成更大压力
  3. 重试间隔时间(intervalTimey):间隔多久重试
  4. 重试间隔时间衰减(weakTime): 时间退避/衰减算法

重试模式:

  1. 简单重试:try-catch-redo,重试一次
  2. 策略重试: try-catch-redo-retry straegy,重试策略决定是否重试,关键因子(retrycount、intervalTimey、weakTime)

重试模式比较通用,尝试对业务解耦

  • 何种条件重试: 符合指定策略才重试
  • 何时重试: 立即重试、间隔重试、时间衰减重试、随机退避重试
  • 重试次数:不超过重试次数最大限度重试

3.6. 隔离策略

  • 进程、线程池
  • 机器隔离
  • 集群隔离
  • 地域隔离
  • 用户、租户隔离

3.7. 熔断器(防止雪崩)

服务雪崩:服务提供者的不可用导致服务消费者也不可用,并将不可用逐级放大的过程。
熔断器就是放在主调服务一侧,阻断主调请求避免对下游服务提供者造成压力。当被调大量超时下,主调服务主动熔断,防止服务进一步拖垮,一旦情况变好,主调重新尝试,最终让系统恢复

熔断器三种状态:

  • 打开: 断路状态,调用请求被禁止,快速失败/业务降级返回
  • 闭合: 通路状态,调用请求被允许放行;当在闭合下,区间时间内累计错误次数达到阈值,则将闭合状态变为打开状态,服务被熔断
  • 半打开:熔断器允许部分请求到达下游,当在半打开下,若依旧有调用失败,则返回打开状态;若请求顺利,调用成功,则返回到闭合状态

注意:

  1. 禁止一个熔断器控制多个服务
  2. 熔断后,主调方应该做好快速失败、业务降级、不显示/显示缓存值返回
  3. 任务干预,支持通过开关预留,这次手动强制开启或关闭熔断器

4. 流控设计(过载保护)

流控,通过流量控制保护服务自身免被压垮,起到超出部分被拒绝,承受范围内请求被正常处理

4.1. 限流算法

4.1.1. 固定窗口(fixed window)

每个时间片内窗口内,允许访问的总次数。

缺点是算法存在临界值2倍限流量情况,同时在下个时间片内出容易现流量蜂拥,容易形成踩踏现象

4.1.2. 漏桶算法(Leaky Bucket)

  • 水流入(请求生产): 漏入桶中,桶满则溢出
  • 漏桶(队列): FIFO队列
  • 水流出(请求消费): 以一定速率从桶内取出请求消费

适用场景: 秒杀场景,削峰填谷

4.1.3. 令牌桶算法(Token Buket)

一个时间窗口内通过的数据量,通过以QPS、TPS衡量

  1. 创建一个可放指定数量(M)令牌的桶(队列)
  2. 每间隔一定时间片,放入一个令牌到桶中(定时令牌生成器),桶满则溢出
  3. 每当R个请求到达时,从桶内取出min(M,R)个令牌,若桶内令牌不够,则将请求缓存或者丢弃(对比网卡的环形队列作用)

4.2. 流控策略

需要配合压测结果、资源环境,对每个服务单独进行配置,一般采用经验值

流控的几点注意:

  1. 尽量在请求入口处做收拢,比如GW层(nginx)、业务出入口、公共基础服务
  2. 流控阈值可能会随着服务的迭代也会变化,比如迭代后服务耗时增加了,那么对应的流控速率就应该更小了,这点需要注意
  3. 阈值不要设置过大,否则起不到流控作用

4.4. 容量预估

对系统容量做到知根知底,ab、LoadRunner、Jmeter这类都比较片面,很难系统模拟生产环境数据,采用全链路可以规避这些问题

全链路压测进行容量评估,几点注意:

  1. 核心流程:28原则,确保真正核心的流程被压测到,这里就需要有张整体的服务调用网络拓扑图,并梳理出核心链路
  2. 隔离方式:独立压测环境,压测效果和隔离效果好,但成本高;生产混合,通过参数识别,在框架和服务处染色处理,资源隔离性不好;
  3. 缩小依赖范围:这样可以更加有的放矢,识别链路的关键瓶颈

PS: 链路有短板效应,需要准确的识别到链路的短板,方便后期做好性能提升,增加系统整体吞吐

5. 故障演练 - Chaos

The best way to avoid failure is to fail constantly

故障演练可以检测业务应用处理失败的能力,以及团队对故障的应对反应(包括通过监控日志、快速故障定位、应急措施),避免当故障真正发生时候,团队人员手忙脚乱,不知所措。

6. 数据迁移 - 目标是Zero Downtime

做数据迁移时候,也可能对系统可用性有影响,目标是无停机时间,常见迁移方式:

  • 逻辑分离,物理不分离
    • 新老服务,双写同一个数据库/缓存不同表
    • 过渡方案
  • 逻辑分析力,物理分离
    • 需要做数据同步,比如通过工具读取binlog实现数据双向同步
    • 业务应用同时写两个库
    • 老系统写消息通过写到消息中间件,消费消息中间件实现同步

关键问题: 数据一致性问题