高可用

如何设计一个高可用系统?

高可用描述的是一个系统在大部分时间都是可用的,可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的。

一般情况下,我们使用多少个9来评判一个系统的可用性,比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001%的时间是不可用的,这样的系统就是非常非常高可用的了,当然,也会有系统如果可用性不太好的话,可能连9都上不了。

哪些情况会导致系统不可用?

  • 黑客攻击
  • 硬件故障,比如服务器坏掉。
  • 并发量/用户请求量激增导致整个服务宕掉或者部分服务不可用。
  • 代码中的坏味道导致内存泄漏或者其他问题导致程序挂掉。
  • 网站架构某个重要的角色比如 Nginx 或者数据库突然不可用。
  • 自然灾害或者人为破坏。

有哪些提高系统可用性的方法?

注重代码质量

我觉得这个是最最最重要的,代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。大家都喜欢谈限流、降级、熔断,但是我觉得从代码质量这个源头把关是首先要做好的一件很重要的事情。如何提高代码质量?比较实际可用的就是 CodeReview,不要在乎每天多花的那1个小时左右的时间,作用可大着呢!

另外,安利这个对提高代码质量有实际效果的宝贝:
1.sonarqube:保证你写出更安全更干净的代码!(ps: 目前所在的项目基本都会用到这个插件)

2.Alibaba 开源的 Java 诊断工具 Arthas 也是很不错的选择

3.IDEA 自带的代码分析等工具进行代码扫描也是非常非常棒的。

使用集群,减少单点故障

先拿常用的 Redis 举个例子,我们如何保证我们的 Redis 缓存高可用呢?答案就是使用集群,避免单点故障。当我们使用一个 Redis 实例作为缓存的时候,这个 Redis 实例挂了之后,整个缓存服务可能就挂了。使用了集群之后,即使一台 Redis 实例,不到一秒就会有另外一台 Redis 实例顶上。

限流

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

超时和重试机制设置

一旦用户请求超过某个时间的得不到响应,就抛出异常。这个是非常重要的,很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候,尤其适合设置超时和重试机制。一般我们使用一些 RPC框架的时候,这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法在处理请求。重试的次数一般设为3次再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)

熔断机制

超时和重试机制设置之外,熔断机制也是很重要的。 熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标,当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败,让当前系统立即切换依赖其他备用服务。比较常用的是流量控制和熔断降级框架是 Netflix的 Hystrix 和 alibaba 的Sentinel.

异步调用

异步调用的话我们不需要关心最后的结果,这样我们就可以用户请求完成之后就立即返回结果,具体处理我们可以后续再做,秒杀场景用这个还是蛮多的。但是,使用异步之后我们可能需要 、适当修改业务流程进行配合,比如用户在提交订单之后,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功。除了可以在程序中实现异步之外,我们常常还使用消息队列,消息队列可以通过异步处理提高系统性能(削峰、减少响应所需时间)并且可以降低系统耦合性。

使用缓存

如果我们的系统属于并发量比较高的话,如果我们单纯使用数据库的话,当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据,因为缓存存储在内存中,所以速度相当地快

其他

核心应用和服务优先使用更好的硬件

监控系统资源使用情况增加报警设置

注意备份,必要时候回滚。

灰度发布: 将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的部分服务器即可

定期检查/更换硬件:如果不是购买的云服务的话,定期还是需要对硬件进行一波检查的,对于一些需要更换或者升级的硬件,要及时更换或者升级。

微服务中的雪崩问题

微服务架构下,系统被拆分成了很多微服务(比如电商系统可能分为用户服务、商品服务、订单服务等服务),这些微服务协同工作对外提供服务,一个用户请求可能会需要多个服务参与。

在没有任何措施的情况下,如果某个服务调用出现了异常(比如请求的响应时间变长),那就会波及到调用这个服务的所有相关服务,从而引起了一系列连锁反应,最终导致 整个服务链路崩溃,这就是 微服务中的雪崩。

很多小伙伴估计要说了,微服务之间进行调用(RPC或者 HTTP)的时候,一般会有 超时和重试机制 来确保服务的成功执行,是不会整个服务链路崩溃出现这种问题的。这样想的话,那就有问题了。超时和重试机制很实用,但微服务下仅仅有超时重试机制是不行的,高并发场景下,依然可能有大量请求挤压的问题发生。

举个例子:一个用户请求需要经过服务 1、服务 2、服务3和服务 4。假如服务2的调用出现异常,导致需要 10 秒才能返回响应,这就必然会导致 10 秒内该请求线程会一直处于阻塞状态。

image-20240425233709596

随着时间的推移,如果系统收到的请求越来越多的话,会导致大量请求阻塞等待积压在服务1和服务 2直接导致连接池爆满,系统负载过高,最终导致服务1和服务2 崩溃。

image-20240425233730030

由于微服务中的服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。随着时间推移,依赖于服务1和服务2的服务最终也会变得不可用,形成级联失败,最终导致整个服务链路崩溃。

高并发场景下,这种情况是绝对不允许出现的,后果极其严重。出现这种问题,至少也是生产环境 P0 级事故。

微服务中导致雪崩的原因

流量突然爆发:这个是导致雪崩问题的很常见的一个原因。在某些情况下(比如节假日、活动、网站被大博主推荐、出现热点性事件),系统的流量可能会突然猛增,某个服务顶不住的话就直接挂掉了,进而影响到其他服务。

代码出现 bug:代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害,大家都喜欢谈限流、降级、熔断,但是我觉得从代码质量这个源头把关是首先要做好的一件很重要的事情。

调用第三方服务出现异常:调用第三方服务比如微信支付接口的时候出现超时。

缓存雪崩和击穿:大量请求直接访问数据库,把数据库打垮了。

硬件故障或者网络异常:比如服务器坏掉、服务所在的网络突然出现问题。

如何避免微服务中的雪崩问题?

服务限流

服务限流就是对用户请求的速率进行限制,保证流量在一个可控的范围内,避免瞬时的大量请求击垮系
统。

常见的限流算法有固定窗口计数器算法、滑动窗口计数器算法、漏桶算法、令牌桶算法。

单机限流可以直接使用 Google Guava 自带的限流工具类 RateLimiter基于令牌桶RateLimitere算法,可以应对突发流量。
除了最基本的令牌桶算法(平滑突发限流)实现之外,Guava 的 RateLimiter 还提供了 平滑预热限流的算法实现。平滑突发限流就是按照指定的速率放令牌到桶里,而平滑预热限流会有一段预热时间,预热时间之内,速率会逐渐提升到配置的速率

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

相对于,Guava 的限流工具类来说,Bucket4j提供的限流功能更加全面。不仅支持单机限流和分布式限流,还可以集成监控,搭配 Prometheus 和 Grafana 使用。Spring Cloud Gateway 中自带的单机限流的早期版本就是基于 Bucket4j 实现的。后来,替换成了Resilience4j

分布式限流常见的方案总结:

借助中间件架限流:可以借助 Sentinel 或者使用 Redis 来自己实现对应的限流逻辑。

网关层限流:比较常用的一种方案,直接在网关层把限流给安排上了。不过,通常网关层限流通常也需要借助到中间件/框架。就比如 Spring Cloud Gateway 的分布式限流实现 RedisRateLimiter 就是基于 Redis+Lua 来实现的,再比如 Spring Cloud Gateway 还可以整合 Sentinel 来做限流
如果你要基于 Redis 来手动实现限流逻辑的话,建议配合 Lua 脚本来做。

服务熔断和降级

服务降级是从系统功能优先级的角度考虑如何应对系统故障。当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。

服务熔断是应对雪崩效应的一种微服务链路保护机制。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。

个人建议使用阿里开源的 Sentinel 来做服务熔断和降级,功能全面,文档丰富,社区也比较活跃。而且Sentinel还支持限流,限流问题也跟着解决了,

横向扩容

如果某一个服务需要处理的流量比较大的话,我们直接对其进行横向扩容也就是部署更多服务实例,这样的话,就可以处理更多的请求了。

降级和熔断有什么区别?

什么是降级

降级是从系统功能优先级的角度考虑如何应对系统故障

降级是在面临系统压力或异常情况下,为了保障核心功能的正常运行而主动减少或关闭非关键功能或服务的过程。降级通常是作为系统容错和稳定性设计的一部分,目的是确保系统在面临异常情况或高负载时仍能够提供基本的核心功能,而不至于完全瘫痪或崩溃。

降级的方式有哪些?

  1. 功能降级
    • 功能降级是指在面对高负载或异常情况时,暂时关闭一些非关键的功能或服务,从而释放系统资源,保障核心功能的正常运行。例如,在电商网站中,可以在高峰期暂时关闭商品推荐功能,以减轻数据库和服务器的压力。
  2. 数据降级
    • 数据降级是指在数据处理过程中,降低对数据的处理要求或减少数据的精确度,以提高系统的响应速度和吞吐量。例如,在日志系统中,可以降低对日志的存储精度或缩短日志的保留周期,以减少磁盘空间占用和数据库压力。
  3. 服务降级
    • 服务降级是指在系统资源有限或出现异常情况时,暂时关闭一些服务或切换到备用服务,以保障核心服务的稳定运行。例如,在微服务架构中,可以对某些微服务进行降级处理,如调整服务的响应时间阈值、限制服务的并发处理能力等。
  4. 页面降级
    • 页面降级是指在前端页面设计中,针对不同的网络环境或设备性能,提供不同级别的页面体验。例如,在移动端应用中,可以针对网络较差的情况提供简化版页面或加载速度更快的页面,以提升用户体验。
  5. 系统降级
    • 系统降级是指在系统整体运行时,主动关闭或减少某些系统组件或模块的功能或资源占用,以保障系统的基本运行能力。例如,当系统遇到内存泄漏或异常频发时,可以关闭一些不必要的后台任务或临时降低系统的并发处理能力,以确保系统的稳定性。

具体的降级策略

延迟服务:比如发表了评论,重要服务,比如在文章中显示正常,但是延迟给用户增加积分,只是放到一个缓存中,等服务平稳之后再执行。

在粒度范围内关闭服务(片段降级或服务功能降级):比如关闭相关文章的推荐,直接关闭推荐区

页面异步请求降级:比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;

页面跳转(页面降级):比如可以有相关文章推荐,但是更多的页面则直接跳转到某一个地址

写降级:比如秒杀抢购,我们可以只进行 Cache 的更新,然后异步同步扣减库存到 DB,保证最终一致性即可,此时可以将 DB 降级为 Cache。

读降级:比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求
不高的场景。

什么是熔断

熔断(Circuit Breaker)是一种设计模式和技术,用于提高分布式系统的稳定性和容错性。熔断器模式的核心思想是在面对故障或异常情况时,暂时切断对某个服务的访问,避免连锁故障并快速失败,从而保护系统的正常运行。熔断器模式通常用于微服务架构和分布式系统中。

微服务之间的数据交互是通过远程调用来完成的。服务A调用服务 B,服务 B调用服务C,某一时间链路上对服务C的调用响应时间过长或者服务C不可用,随着时间的增长,对服务C的调用也越来越多,然后服务C崩溃了,但是链路调用还在,对服务B的调用也在持续增多,然后服务B崩溃,随之A也崩溃,导致雪崩效应

服务熔断是应对雪崩效应的一种微服务链路保护机制。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。


高可用
http://example.com/2024/04/10/项目/微服务/高可用/
作者
PALE13
发布于
2024年4月10日
许可协议