高可用系统设计

168 阅读5分钟

高可用一般是描述我们开发的系统状态,如何判断系统的高可用,可以从系统的可用性进行计算。可用性一般从时间维度或者请求次数去计算,比如用户使用该系统 100 次,99 次返回成功,那么该系统的可用性就为 99%。本篇文章主要介绍如何设计一个高可用系统。

不可用的因素

  1. 代码问题。代码中存在内存泄漏或者内存溢出的问题等
  2. 基础设施导致服务不可用。当服务所依赖的基础设施出现异常,比如依赖的数据库掉盘等因素。
  3. 访问量徒增。在某一个时间段内,由于工具或者用户自然访问量徒增导致服务挂掉不可用。 .....

如何提高可用性

1、提高代码质量

一般一次线上应急或者系统故障 80% 以上都是由线上变更所导致的,所以我认为提高代码质量是提高可用性最基础重要的工作。提高代码质量可以在开发过程中使用一些组件扫描代码,比如使用 SonarQube 服务、Alibaba 代码扫描工具等,在开发完成后在团队内组织 code review,测试阶段严格把关

2、使用集群,减少单点故障

使用集群是我们提供服务高可用的重要手段。集群可以使用副本冗余的方式避免单点故障。同时正确使用集群模式,也能够提高系统并发。比如 Redis 使用集群模式提高可用性。Redis 的集群模式一般有两种,分别是哨兵模式(Sentinel)和集群模式(Cluster)。

  • 哨兵模式:哨兵模式是指一个 Master 节点,多个 Cluster 节点。哨兵通过监听 Master 的状态,在 Master 不可用时将 Cluster 节点转化成 Master 节点。主节点负责单机模式下的所有功能,包括处理读写请求,数据备份,同步数据给从节点等。从节点并不负责处理读请求,仅仅负责异常状态转移功能。但可以通过配置只读从节点来提高系统的并发性。
  • 集群模式:集群模式适用于数据量大的场景,在集群模式下,会将数据进行分区存储到不同的实例上,每个实例都是一个节点,分为主节点和从节点。

集群和分布式的区别

集群是由若干台服务器和网络组成的计算资源池,通过将数据进行分片,每个分片由不同的机器进行计算,并通过某些方式进行数据的合并,最终得到一个整体的计算结果。

分布式是指将一个大型系统拆分为多个子系统,并且子系统运行在不同的计算机上,通过信息交互互相协同工作,完成整个系统功能。

3、使用限流熔断

限流是保护系统的重要手段,可以避免系统因为过多的请求而导致系统崩溃。常见的限流策略为:

  • 计数器限流:通过计数器限制单位时间内的请求数量。例如可以使用 Redis 的 Incr 命令统计单位时间内的请求次数
  • 令牌桶算法:将请求封装成令牌放到令牌桶中,每来一个请求取走一个令牌,当桶中没有足够的令牌则拒绝提供访问。
  • 漏桶算法:将请求看作水流,漏桶看作限流的设备,每个请求插入到漏桶中后,以固定的速度进行流出,如果流入的速度过快,则会发生桶溢出,拒绝访问。
  • 原始的基于时间窗口限流方案:如计数器 + 固定窗口 / 滑动窗口等。

单机限流

单机限流可以使用 Google Guava 自带的限流工具类 RateLimiter,其基于令牌桶算法,可以应对突发流量。

github.com/google/guav…

另外,Bucket4j 是⼀个不错的基于令牌/漏桶算法的限流库

github.com/vladimir-bu…

Resilience4j 是⼀个轻量级的容错组件,其灵感来⾃于 Hystrix。⾃Netflix 宣布不再积极开发 Hystrix 之后,Spring 官⽅和 Netflix 都更推荐使⽤ Resilience4j 来做限流熔断。

Resilience4j 地址: github.com/resilience4…

分布式限流

  • 分布式组件限流:可以使用分布式组件进行限流,比如 Sentinel 或者 Redis 进行限流,一般是使用 Redis + Lua 脚本完成限流功能。
  • 网关限流:可以使用网关进行限流,比如 Spring Cloud Getwag 实现分布式限流。
4、熔断机制

熔断是一种系统容错和防止系统雪崩的方案,当下游依赖发生超时异常,进行熔断可以防止本服务的线程资源耗尽,避免发生雪崩。

比较常用的流量控制和熔断降级框架是 NetFlix 的 Hystrix 和 Alibaba 的 Sentinel

5、异步调用

异步代表着不需要依赖下游服务的返回而处理逻辑进行返回,提升系统的高可用。

在一个用户注册流程中,可以将一些逻辑进行异步处理,比如注册过程中发送短信的逻辑,将其解耦后将提高接口的响应时间,并且当发送短信的逻辑失败之后也不会影响注册整个流程。

除了使用线程完成异步逻辑,我们通常使用消息队列完成异步调用,消息队列除了异步还有削峰和解耦的作用。注意使用消息队列是一把双刃剑,额外引入一个组件需要考虑系统的稳定性。

6、使用缓存

使用缓存可以增加接口的响应时间,减少对数据库的依赖,比如当请求的 QPS 比较高的情况下,如果不增加缓存,直接对数据库进行操作,会将数据库打挂。使用缓存的同时也要注意消息的一致性。

7、其他方式

使用以下方式也会提高系统的可用性

  • 增加报警监控
  • 使用好的硬件基础设施
  • 注意备份,必要时进行回滚 / 使用金丝雀进行发布