设计一个高可用的系统应该考虑哪些问题
# 高可用:如何设计一个高可用系统?
一篇短小的文章,面试经常遇到的这个问题。本文主要包括下面这些内容:
1高可用的定义
2哪些情况可能会导致系统不可用?
3有些提高系统可用性的方法?只是简单的提一嘴,更具体内容在后续的文章中介绍,就拿限流来说,你需要搞懂:何为限流?如何限流?为什么要限流?如何做呢?说一下原理?。
# 什么是高可用?可用性的判断标准是啥?
高可用描述的是一个系统在大部分时间都是可用的,可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的。
一般情况下,我们使用多少个 9 来评判一个系统的可用性,比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的,这样的系统就是非常非常高可用的了!当然,也会有系统如果可用性不太好的话,可能连 9 都上不了。
描述 | 通俗叫法 | 可用性级别 | 年度停机时间 |
---|---|---|---|
基本可用性 | 2个9 | 99% | 87.6小时 |
较高可用性 | 3个9 | 99.9% | 8.8小时 |
具有故障自动恢复的可用性 | 4个9 | 99.99% | 53分钟 |
极高可用性 | 5个9 | 99.999% | 5分钟 |
# 哪些情况会导致系统不可用?
1 黑客攻击;
2 硬件故障,比如服务器坏掉。
3 并发量/用户请求量激增导致整个服务宕掉或者部分服务不可用。
4 代码中的坏味道导致内存泄漏或者其他问题导致程序挂掉。
5 网站架构某个重要的角色比如 Nginx 或者数据库突然不可用。
6 自然灾害或者人为破坏。
7......
# 有哪些提高系统可用性的方法?
# 注重代码质量,测试严格把关
我觉得这个是最最最重要的,代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。大家都喜欢谈限流、降级、熔断,但是我觉得从代码质量这个源头把关是首先要做好的一件很重要的事情。如何提高代码质量?比较实际可用的就是 CodeReview,不要在乎每天多花的那 1 个小时左右的时间,作用可大着呢!
另外,安利这个对提高代码质量有实际效果的宝贝:
1 sonarqube :保证你写出更安全更干净的代码!(ps: 目前所在的项目基本都会用到这个插件)。
2 Alibaba 开源的 Java 诊断工具 Arthas 也是很不错的选择。
3 IDEA 自带的代码分析等工具进行代码扫描也是非常非常棒的。
# 使用集群,减少单点故障
先拿常用的 Redis 举个例子!我们如何保证我们的 Redis 缓存高可用呢?答案就是使用集群,避免单点故障。当我们使用一个 Redis 实例作为缓存的时候,这个 Redis 实例挂了之后,整个缓存服务可能就挂了。使用了集群之后,即使一台 Redis 实例,不到一秒就会有另外一台 Redis 实例顶上。
# 负载均衡
保证服务集群可以进行故障转移。
当服务宕机后,负载请求进行转移,来达到高可用。
其他的一些负载均衡:
- 服务和服务RPC --- RPC 框架 提供负载方案 (DUBBO,SpringCloud)
- 数据集群需要负载均衡(mycat,haproxy)
负载均衡具体可看文章:负载均衡 (opens new window)
# 限流
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。——来自 alibaba-Sentinel (opens new window) 的 wiki。
具体限流算法可以看文章: 限流
# 超时和重试机制设置
一旦用户请求超过某个时间的得不到响应,就抛出异常。这个是非常重要的,很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候,尤其适合设置超时和重试机制。一般我们使用一些 RPC 框架的时候,这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法在处理请求。重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。
# 熔断机制
超时和重试机制设置之外,熔断机制也是很重要的。 熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标,当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败,让当前系统立即切换依赖其他备用服务。 比较常用的是流量控制和熔断降级框架是 Netflix 的 Hystrix 和 alibaba 的 Sentinel。
# 降级
在降级前需要对系统进行梳理,判断系统是否可以丢丢卒保帅,从而整理出那些可以降级,那些不能降级。
一般
: 比如,有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级。警告
: 有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警。错误
: 比如,可用率低于90%,或者数据库连接池用完了,或者访问量突然猛增到系统能承受的最大阈值,此时,可以根据情况自动降级或者人工降级。严重错误
: 比如,因为特殊原因数据出现错误,此时,需要紧急人工降级。
降级按照是否自动化可分为:自动开关降级和人工开关降级。 降级按照功能可分为:读服务降级和写服务降级。 降级按照处于的系统层次可分为:多级降级。
降级的功能点主要从服务器端链路考虑,即根据用户访问的服务调用链路来梳理哪里需要降级。
2、页面降级
在大型促销或者抢购活动时,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级。
3、页面片段降级
比如,商品详情页中的商家部分因为数据错误,此时,需要对其进行降级。
4、页面异步请求降级
比如,商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,则可以进行降级。
5、服务功能降级
比如,渲染商品详情页时,需要调用一些不太重要的服务(相关分类、热销榜等),而这些服务在异常情况下直接不获取,即降级即可。
6、读降级
比如,多级缓存模式,如果后端服务有问题,则可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景。
7、写降级
比如,秒杀抢购,我们可以只进行Cache的更新,然后异步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
8、自动降级
当服务中错误出现次数到达阀值(99.99%),对服务进行降级,发出警告。
# 熔断和降级有什么区别?
熔断和降级是在分布式系统和微服务架构中常见的两个概念,用于处理服务之间的故障和不可用情况。它们有以下区别:
- 熔断(Circuit Breaking):熔断是一种故障保护机制,用于防止故障蔓延到整个系统,并提高系统的稳定性。当某个服务或组件出现故障或变得不可用时,熔断器会中断对该服务的请求,并在一段时间内不再尝试请求,从而避免对故障的持续请求。熔断器会跟踪请求的成功率和错误率,如果错误率超过预设阈值,则触发熔断操作。熔断器通常会返回一个预先定义的默认响应,而不是将请求传递到故障的服务。
- 降级(Fallback):降级是一种优雅的退化机制,用于在系统资源有限或出现故障时保持系统的可用性。当系统负载过高、资源不足或某个服务不可用时,可以通过降级将某些功能或服务暂时关闭或替换为备用的简化版本。降级通常会提供一些基本的功能或缓存的数据,以满足用户的最基本需求,而不会执行复杂或计算密集型的操作。通过降级,系统可以继续正常运行,尽管可能功能上有所损失。
总的来说,熔断是一种故障保护机制,用于防止故障蔓延和避免对故障的持续请求;而降级是一种资源管理和可用性保障的策略,通过关闭或简化功能来保持系统的可用性。两者都是为了提高系统的稳定性和性能,但关注的角度和应用场景有所不同。
# 异步调用
异步调用的话我们不需要关心最后的结果,这样我们就可以用户请求完成之后就立即返回结果,具体处理我们可以后续再做,秒杀场景用这个还是蛮多的。但是,使用异步之后我们可能需要 适当修改业务流程进行配合,比如用户在提交订单之后,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,**甚至出库后,再通过电子邮件或短信通知用户订单成功。**除了可以在程序中实现异步之外,我们常常还使用消息队列,消息队列可以通过异步处理提高系统性能(削峰、减少响应所需时间)并且可以降低系统耦合性。
# 使用缓存
如果我们的系统属于并发量比较高的话,如果我们单纯使用数据库的话,当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据,因为缓存存储在内存中,所以速度相当地快!
# 使用隔离
1、线程隔离
线程隔离指的是线程池隔离,一个请求出现问题不会影响到其他线程池。
2、进程隔离
把项目拆分成一个一个的子项目,互相物理隔离,不进行相互调用。
3、集群隔离
将集群隔离开,使互相不影响。
4、机房隔离
分不同的机房进行部署,杭州机房;北京机房;上海机房;
5、读写隔离
互联网项目中大多是读多写少,读写分离,扩展读的能力,提高性能,提高可用性。
6、动静隔离
将静态资源放入nginx,CDN,从而达到动静隔离,防止页面加载大量静态资源
7、热点隔离
将热点业务独立成系统或服务进行隔离,如秒杀,抢购。
读热点一般使用多级缓存
写热点一般使用缓存加消息队列的方式
# 系统压测
压测一般指性能压力测试,用来评估系统的稳定性和性能,通过压测数据进行系统容量评估,从而决定是否需要进行扩容或缩容。
线下压测: 通过如JMeter、Apache ab压测系统的某个接口(如查询库存接口)或者某个组件(如数据库连接池),然后进行调优(如调整JVM参数、优化代码),实现单个接口或组件的性能最优。 线下压测的环境(比如,服务器、网络、数据量等)和线上的完全不一样,仿真度不高,很难进行全链路压测,适合组件级的压测,数据只能作为参考。
线上压测: 线上压测的方式非常多,按读写分为读压测、写压测和混合压测,按数据仿真度分为仿真压测和引流压测,按是否给用户提供服务分为隔离集群压测和线上集群压测。 读压测是压测系统的读流量,比如,压测商品价格服务。写压测是压测系统的写流量,比如下单。写压测时,要注意把压测写的数据和真实数据分离,在压测完成后,删除压测数据。只进行读或写压测有时是不能发现系统瓶颈的,因为有时读和写是会相互影响的,因此,这种情况下要进行混合压测。 仿真压测是通过模拟请求进行系统压测,模拟请求的数据可以是使用程序构造、人工构造(如提前准备一些用户和商品),或者使用Nginx访问日志,如果压测的数据量有限,则会形成请求热点。而更好的方式可以考虑引流压测,比如使用TCPCopy复制
# 系统优化和容灾
拿到压测报告后,接下来会分析报告,然后进行一些有针对性的优化,如硬件升级、系统扩容、参数调优、代码优化(如代码同步改异步)、架构优化(如加缓存、读写分离、历史数据归档)等。
不要直接复用别人的案列,一定要根据压测结果合理调整自己的案例。
在进行系统优化时,要进行代码走查,发现不合理的参数配置,如超时时间、降级策略、缓存时间等。在系统压测中进行慢查询排查,包括Redis、MySQL等,通过优化查询解决慢查询问题。
在应用系统扩容方面,可以根据去年流量、与运营业务方沟通促销力度、最近一段时间的流量来评估出是否需要进行扩容,需要扩容多少倍,比如,预计GMV增长100%,那么可以考虑扩容2~3倍容量。
# 应急预案
在系统压测之后会发现一些系统瓶颈,在系统优化之后会提升系统吞吐量并降低响应时间,容灾之后的系统可用性得以保障,但还是会存在一些风险,如网络抖动、某台机器负载过高、某个服务变慢、数据库Load值过高等,为了防止因为这些问题而出现系统雪崩,需要针对这些情况制定应急预案,从而在出现突发情况时,有相应的措施来解决掉这些问题。
应急预案可按照如下几步进行:首先进行系统分级,然后进行全链路分析、配置监控报警,最后制定应急预案。
# 其他
1核心应用和服务优先使用更好的硬件
2监控系统资源使用情况增加报警设置。
3注意备份,必要时候回滚。
4 灰度发布: 将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器即可
5 定期检查/更换硬件: 如果不是购买的云服务的话,定期还是需要对硬件进行一波检查的,对于一些需要更换或者升级的硬件,要及时更换或者升级。
6.....(想起来再补充!也欢迎各位欢迎补充!)