Cover

浅谈 “高可用”

· Maintenance

前言

谈到企业的互联网发展,尤其是对于主要业务为互联网相关业务的企业,我们通常都会在开发的过程中频繁提到“高可用”一词。

高可用性(英语:high availability,缩写为 HA),IT术语,指系统无中断地执行其功能的能力,代表系统的可用性程度。是进行系统设计时的准则之一。高可用性系统与构成该系统的各个组件相比可以更长时间运行。

高可用性通常通过提高系统的容错能力来实现。定义一个系统怎样才算具有高可用性往往需要根据每一个案例的具体情况来具体分析。

引自 Wikipedia

高可用的主要目的是保障“业务的连续性”,即_在用户眼里业务永远正常对外提供服务_ 。高可用只诞生于良好架构方案和高昂成本中,正是因此,对于一个架构师来说,学习设计高可用架构是必需的。

我们通常会将一个庞大的系统拆分为多层,即分层思想,将其拆分为应用层、中间件、数据存储层等独立的“层”,每一层再拆分为细致的组件。而后使每个组件都对外提供服务,使其不孤立存在,而后使得服务具有意义。

架构的高可用,自然需要保证架构中所有组件以及其对外暴露服务都要遵循“高可用”设计。任何一个组件没有做到“高可用”,都意味着系统可能存在宕机的风险。

任何组件要实现“高可用”,都离不开“冗余”和“自动故障转移”这两个核心要素。单点服务是高可用的大敌,所以一个合格的高可用组件应进行集群化(至少应存在于两台机器),这样服务不会因为某一个节点的瘫痪而不可用,会有其他节点立刻进行顶替。

假设任意一个节点的可用性为90%,那么两台机器的集群就是 1(1001 - (100\\% - 90\\%)^2 = 99\\% 。由此可知,想要提高可用性,增加冗余的机器是一个简单实用的选择。

仅有“冗余”,并不完善。机器出现问题后的切换过程仍然费时费力,而且容易出错,所以我们还要借助工具实现“全自动的故障转移”,以此达到实现近实时的故障转移的目的,近实时的故障转移才是高可用的主要意义。

在业界通常用“9”的数量衡量一个系统的可用性:

可用级别系统可用性年平均宕机月平均宕机周平均宕机日平均宕机
不可用90%876 小时73 小时16.8 小时144 分钟
基本可用99%87.6 小时7.3 小时1.68 小时14.4 分钟
较高可用99.9%8.76 小时43.8 分钟10.1 分钟1.44 分钟
高可用99.99%52.56 分钟4.38 分钟1.01 分钟8.64 秒

实现 99% 可用性已经是很简单的事了,毕竟如今是云计算时代,而且如此宕机时间已经严重影响业务了,对于此类公司或许只得一死了当。

大型企业的主要业务通常都要求五个九以上,因为系统的一时故障往往会影响数以万计的人们。(当然要求归要求,实现不了也很正常,比如前段时间几个大厂云连着崩…)

微服务架构

目前大多数国内互联网企业都会采用微服务架构:

微服务架构
微服务架构

微服务架构

可以看到架构主要分以下几层

  • 接入层:主要由 F5 硬件或 LVS 软件来承载所有的流量入口,通常伴有较大规模的DDOS流量清洗机器。
  • 负载均衡层:大多为Nginx,主要负责分发流量和进行限流等。
  • 网关层:主要负责流控、风控、协议转换等。
  • 站点层:主要负责调用基本服务时对 Json 数据的装配 ,并返回给客户端。
  • 基础服务层:其实与站点层都属于微服务,是平级关系,只不过基础服务属于基础设施,能被上层的各个业务层调用。
  • 存储层:也就是数据库,如 MySQL、Oracle、Postgres 等,一般由 Service 调用返回给站点层。
  • 中间件:Redis、MQ 等,主要起到加速访问数据等功能,在下文中我们会简单介绍下各个组件的作用。

接入层 & 负载均衡层

这两层的高可用都与 Keep Alive 息息相关,因此我们可以合在一起看。

接入层 & 负载均衡层
接入层 & 负载均衡层

接入层 & 负载均衡层

两个 LVS 以主备形式对外提供服务,此时仅master工作,backup在master宕机后会进行实时接管。在主备机器使用Keep Alived软件,以此使得backup可以实时监测master的运行状态。

在master宕机后,弹性IP会迅速转移到backup,也就是我们常说的“IP漂移”,以此解决LVS的高可用。

Keep Alived 的 HeartBeat 检测通常通过 ICMP 或 TCP 端口扫描来检测,同样它也可以用来检测 Nginx 的端口,以此实现及时对不正常的 Nginx 实例进行剔除的操作。

微服务层

在负载均衡之下,“网关层”、“站点层”、“基础服务层”共同构成了最关键的微服务架构组件部分。当然这些组件之间还需要通过RPC框架例如Dubbo、gRPC才能通信。

所以微服务要实现高可用,就意味着这些RPC框架也要提供支撑微服务高可用的能力,我们就以Dubbo为例学习它是如何实现高可用的:

Dubbo 架构
Dubbo 架构

Dubbo 架构

思路大致如下:

  1. Provider 向 Registry 注册服务
  2. Consumer 向 Registry 订阅和拉取 Provider 服务列表
  3. Consumer 根据其负载均衡策略选择 Provider 并发送请求
  4. Provider 不可用时会被 Registry 监听到并推送给 Consumer,而后被移除

以此,实现了故障的自动转移。

不难看出,在此之中,Registry 就实现了类似 Keep Alived 的作用。

中间件

对于 Redis、ZooKeeper 等中间件服务,实现高可用也是较为重要的。

ZooKeeper

我们以 ZooKeeper 为例:

ZooKeeper 架构
ZooKeeper 架构

ZooKeeper 架构

我们可以从图中看出,ZooKeeper 的主要角色如下:

  • Leader(一个集群只能有一个Leader)
    • 事务请求的唯一调度和处理者:保证集群事务处理的顺序性。执行所有 Follower 的写请求,以此保证事务的一致性。
    • 集群内部个服务器的调度者:处理好事务请求后,将数据广播同步到各个 Follower,统计Follower 写入成功的数量。超过半数成功,Leader 就会认为写请求提交成功,并通知所有 Follower commit 这个写操作。保证若是集群崩溃恢复或重启,这也写操作也不会丢失。
  • Follower
    • 处理客户端非事务请求,转发事务请求给 Leader 服务器。
    • 参与事务请求 Proposal 的投票。
    • 参与 Leader 选举的投票。

其中的主要问题也不难看出,Leader 只有一个,存在单点隐患。ZooKeeper 为了解决此问题,会使 Leader 和 Follower 用 HeartBeat 机制保持连接,在 Leader 出问题时由 Follower 投票选举替代的 Leader (ZooKeeper Atomic Broadcast,专为 ZooKeeper 设计的一种支持崩溃恢复的一致性协议)。

除去 ZooKeeper ,业界还有 Paxos、Raft 等协议算法,也可用于 Leader 选举。

Redis

Redis 具有两种部署模式:“主从模式” 和 “Cluster 分片模式”。Redis 的高可用需要根据其部署模式决定。

主从模式
Redis 主从模式 架构
Redis 主从模式 架构

Redis 主从模式 架构

主从模式即一主多从(一个或者多个从节点)。其中主节点主要负责读写,而后将数据同步到多个从节点上。Client 也可以对多个从节点发起读请求,以此减轻主节点的压力。

但和 ZooKeeper 一样,由于只有一个主节点,存在单点隐患,所以必须引入第三方仲裁者的机制来判定主节点是否宕机以及在判定主节点宕机后快速选出某个从节点来充当主节点的角色。

这个第三方仲裁者在 Redis 中我们一般称其为“哨兵”(Sentinel),当然哨兵进程本身也有可能挂掉,所以为了安全起见,需要部署多个哨兵(即哨兵集群)。

Redis 主从模式+哨兵集群 架构
Redis 主从模式+哨兵集群 架构

Redis 主从模式+哨兵集群 架构

哨兵集群通过 Gossip(流言)协议来接收关于主服务器是否下线的信息,并在判定主节点宕机后使用 Raft 协议来选举出新的主节点。

Cluster 分片集群模式

主从模式看似完美,但存在以下几个问题

  • 主节点写的压力难以降低。因为只有一个主节点能接收写请求,如果在高并发的情况下,写请求如果很高的话可能会把主节点的网卡打满,造成主节点对外无法服务。
  • 主节点的存储能力受到单机存储容量的限制。因为不管是主节点还是从节点,存储的都是全量缓存数据,那么随着业务量的增长,缓存数据很可能直线上升,直到达到存储瓶颈。
  • 同步风暴:因为数据都是从 Master 同步到 Slave 的,如果有多个从节点的话,Master 节点的压力会很大。

为了解决以上问题,Cluster 分片集群就应运而生。

将数据分片,每一个分片数据由相应的主节点负责读写,这样就有多个主节点来分担写的压力,且每个节点只存储部分数据,也就解决了单机存储瓶颈的问题。

但需要注意的是,每个主节点都存在单点问题,所以需要针对每个主节点做高可用。

Redis Cluster 分片集群架构
Redis Cluster 分片集群架构

Redis Cluster 分片集群架构

在 Proxy 收到 Client 执行的 Redis 的读写命令后,首先会对 Key 进行计算得出一个值,如果这个值落在相应 Master 负责的数值范围(一般将每个数字称为槽,Redis 一共有 16384 个槽)之内,那就把这条 Redis 命令发给对应的 Master 去执行。

可以看到每个 Master 节点只负责处理一部分的 Redis 数据,同时为了避免每个 Master 的单点问题,也为其配备了多个从节点以组成集群,当主节点宕机时,集群会通过 Raft 算法来从从节点中选举出一个主节点。

当然,成本也是爆炸的,以至于我至今为止都没操作过Cluster 分片集群的部署。

MQ (Kafka)

MQ,即 Message Queue 消息队列。

MQ的高可用通常也是利用数据分片来提升高可用和水平扩展能力。

Kafka 高可用集群架构
Kafka 高可用集群架构

Kafka 高可用集群架构

可以看到每个 Topic 的 Partition 都分布式存储在其它消息服务器上,这样一旦某个 Partition 不可用,可以从 follower 中选举出 leader 继续服务。

不过不同于ES、Redis Cluster 的是,Follower Partition 属于冷备,也就是说在正常情况下不会对外服务,只有在 Leader 挂掉之后从 Follower 中选举出 Leader 后它才能对外提供服务。

存储层

存储层通常为数据库。数据库,也是一套系统能够持久运行的基本条件,这里我们以 MySQL 为例来简单地讨论一下其高可用设计。

参照以上的高可用设计,你会发现 MySQL 的高可用思想与上述其他架构都是类似的,它也是分主从和分片(即我们常说的分库分表)两种架构。

MySQL 主从架构

主从架构与 LVS 类似,一般使用 Keep Alived 的形式来实现高可用:

MySQL 主从架构
MySQL 主从架构

MySQL 主从架构

如果 Master 宕机了,Keepalived 也会及时发现,于是从库会升级主库,并且弹性IP也会“漂移”到原从库上生效,所以说大家在工程配置的 MySQL 地址一般都会使用弹性IP或私有DNS解析域以保证高可用。

MySQL 分片架构

在数据量达到一定水平后,分库分表也是必要的操作。就像 Redis 的分片集群一样,需要针对每个主配备多个从。

MySQL 分片集群架构
MySQL 分片集群架构

MySQL 分片集群架构

总结

观察以上架构中的组件,你会发现,高可用架构会随着数据量的提升而变得更加精密复杂,就比如从一主多从集群到多主从集群。但在这其中,数据之间的同步更是一大难题。所以多数组件依然采用一主的形式,然后再在主和多从之间同步。

但需要注意的是,做好每个组件的高可用之后,整个架构并不一定就真的可以做到完全高可用。在生产上还有很多突发情况会让我们的系统面临挑战,例如瞬时流量(例如抢购环节等)、恶意攻击(DDOS 等)、代码内存泄露(导致程序不响应)、部署到生产环境时出错(Facebook 宕机便是由此)、机房断电(可依靠异地容灾)等。

所以在做好架构的高可用的同时,我们还需要在做好系统隔离、限流、熔断、风控、降级,对关键操作限制操作人权限等措施以保证系统的可用。

Comments

Send Comments

Markdown supported. Please keep comments clean.