高可用的设计原则
1 系统拆分(微服务拆分)
最常见的是按照业务维度拆(比如电商场景的商品服务、订单服务等),也可以按照核心接口和非核心接口拆,还可以按照请求去拆(比如To C和To B,APP和H5)。
在我们的系统体积过于庞大或者承载不了大量的请求时,就要考虑拆分系统,将复杂问题简单化或将流量分散不同子系统分担压力。可以按照以下几个维度进行拆分:
-
系统维度:比如电商系统,我们可以拆分成商品、支付、优惠券多个子系统。
-
功能维度:将系统按功能再次拆分。
-
读写维度:按照读写比例将服务拆分成读服务和写服务。
-
模块维度:将系统按照基础架构、消息队列、分库分表 、组件等模块进行拆分维护。
2 服务无状态
什么是无状态?服务器不保存状态,对单次请求的处理不依赖别的请求就是无状态,主要是为了在应对高并发时方便水平扩展。
服务无状态,才可以动态扩容。
3 限流
对超过系统处理能力的请求直接拒绝或者返回错误码。
限流也是我们应对高并发的一种方案。我们当然希望,在高并发大流量过来时,系统能全部请求都正常处理。但是有时候没办法,系统的CPU、网络带宽、内存、线程等资源都是有限的。因此,我们要考虑限流。
如果你的系统每秒扛住的请求是一千,如果一秒钟来了十万请求呢?换个角度就是说,高并发的时候,流量洪峰来了,超过系统的承载能力,怎么办呢?
这时候,我们可以采取限流方案。就是为了保护系统,多余的请求,直接丢弃。
可以使用Guava的RateLimiter单机版限流,也可以使用Redis分布式限流,还可以使用阿里开源组件sentinel限流。
面试的时候,你说到限流这块的话?面试官很大概率会问你限流的算法.
4 熔断降级
熔断降级是保护系统的一种手段。当前互联网系统一般都是分布式部署的。而分布式系统中偶尔会出现某个基础服务不可用,最终导致整个系统不可用的情况, 这种现象被称为服务雪崩效应。
为了应对服务雪崩, 常见的做法是熔断和降级。最简单是加开关控制,当下游系统出问题时,开关打开降级,不再调用下游系统。还可以选用开源组件Hystrix、sentinel来支持。
熔断,其实是对调用链路中某个资源出现不稳定状态时(如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致联机错误。
熔断的主要方式是使用断路器阻断故障服务器的调用。
断路器有三种状态:关闭、打开、半打开
-
关闭(Closed)状态:在这个状态下,请求都会被转发给后端服务。同时会记录请求失败的次数,当请求失败次数在一段时间超过一定次数就会进入打开状态
-
打开(Open)状态:在这个状态下,熔断器会直接拒绝请求,返回错误,而不去调用后端服务。同时,会有一个定时器,时间到的时候会变成半打开状态。目的是假设服务会在一段时间内恢复正常
-
半打开(Half Open)状态:在这个状态下,熔断器会尝试把部分请求转发给后端服务,目的是为了探测后端服务是否恢复。如果请求失败会进入打开状态,成功情况下会进入关闭状态,同时重置计数
5 业务降级
保证核心服务,牺牲非核心服务,必要时进行熔断;或者核心链路出问题时,有备选链路。
除了通过开源组件来实现降级外,还有一种业务降级的方式,比如,通过开关,使得高峰期可以关闭一些非核心服务,来保障核心服务;或者直接切断一些业务入口;
业务降级通常是通过开关来任务配置处理的。
降级是系统保护的一种重要手段
为了使有限资源发挥最大价值,我们会临时关闭一些非核心功能,减轻系统压力,并将有限资源留给核心业务
比如电商大促,业务在峰值时刻,系统抵挡不住全部的流量时,系统的负载、CPU 的使用率都超过了预警水位,可以对一些非核心的功能进行降级,降低系统压力,比如把商品评价、成交记录等功能临时关掉。弃车保帅,保证 创建订单、支付 等核心功能的正常使用
总结下来:降级是通过暂时关闭某些非核心服务或者组件从而保护核心系统的可用性。
6 故障转移
对等节点的故障转移,Nginx和服务治理框架均支持一个节点失败后访问另一个节点。
非对等节点的故障转移,通过心跳检测并实施主备切换(比如redis的哨兵模式或者集群模式、MySQL的主从切换等)。
故障转移:一般指主备切换、缩短故障时间
当系统出现故障时,首要任务不是立马查找原因,考虑到故障的复杂性,定位排查要花些时间,等问题修复好,SLA也降了好几个档。更好的解决方案就是:故障转移
故障转移:当发现故障节点的时候,不是尝试修复它,而是立即把它隔离,同时将流量转移到正常节点上。这样通过故障转移,不仅减少了MTTR提升了SLA,还为修复故障节点赢得了足够的时间。
-
①:对等节点可直接转移切换
-
②:节点分主备时,转移时需要进行主备切换
如何发现故障并自动转移?
一般采用某种故障检测机制,比如心跳机制,备份节点定期发送心跳包,当多数节点未收到主节点的心跳包,表示主节点故障,需要进行切换
切换到哪个备节点?
一般采用paxos、raft等分布式一致性算法,在多个备份节点中选出新主节点
主备切换大致分为三步:
1)故障自动侦测(auto-detect),采用健康检查,心跳等手段自动侦测故障节点
2)自动转移(failover),当侦测到故障节点后,采用摘除流量、脱离集群等方式隔离故障节点,将流量转移到正常节点
3)自动恢复(failback),当故障节点恢复正常后,自动将其加入集群中,确保集群资源与故障前一致
7 灰度发布
灰度发布,能支持按机器维度进行小流量部署,观察系统日志和业务指标,等运行平稳后再推全量
8 扩容+切流量
如果是突发的流量高峰,除了降级、限流保证系统不跨,我们可以采用这两种方案,保证系统尽可能服务用户:
-
扩容:比如增加从库、提升配置的方式,提升系统/组件的流量承载能力。比如增加MySQL、Redis从库来处理查询请求。
-
切流量:服务多机房部署,如果高并发流量来了,把流量从一个机房切换到另一个机房。
9 监控告警
全方位的监控体系,包括最基础的CPU、内存、磁盘、网络的监控,以及Web服务器、JVM、数据库、各类中间件的监控和业务指标的监控
10 灾备演练
类似当前的“混沌工程”,对系统进行一些破坏性手段,观察局部故障是否会引起可用性问题。
11 多活策略
容灾备份策略并不能保证万事大吉
在一些极端情况,如:机房断电、机房火灾、地震、山洪等不可抗力因素,所有的服务器(主、备)可能都同时出现故障,全部无法对外提供服务,导致整体业务瘫痪。
为了降低风险,保证服务的24小时可用性,我们可以采用多活策略
常见的多活方案有:同城多活、两地三中心、三地五中心、异地双活、异地多活等
12 超时设制
在分布式环境下,服务响应慢可能比宕机危害更大,失败只是暂时的,但调用延迟会导致占用的资源得不到释放,在高并发情况下会造成整个系统崩溃
如何合理设置超时时间?
收集系统之间的调用日志,统计比如说 99% 的响应时间是怎样的,然后依据这个时间来指定超时时间
超时处理策略?
- ①:服务超时释放资源,响应失败
如数据库配置超时时间,超时则终止操作,释放资源
jdbc配置:connectTimeout:表示等待和MySQL建立socket连接的超时时间,默认值0,表示不超时,单位毫秒,建议30000
socketTimeout:表示客户端和MySQL建立socket后,读写socket时的等待超时时间
- ②:由于网络波动、节点异常的原因导致的请求超时,可以采用服务降级的方式,为请求提供兜底的数据响应,避免用户界面处于长时间停顿
13 接口重试、幂等
重试主要体现在远程的RPC调用,受网络抖动、线程资源阻塞等因素影响,请求无法及时响应。
为了提升用户体验,调用方可以通过 重试 方式再次发送请求,尝试获取结果。
接口重试是一把双刃剑,虽然客户端收到了响应超时结果,但是我们无法确定,服务端是否已经执行完成。如果盲目地重试,可能会带来严重后果。比如:银行转账。
重试通常跟幂等组合使用,如果一个接口支持了 幂等,那你就可以随便重试。
14 MQ 消息可靠性保证
MQ场景的消息可靠性保证,包括producer端的重试机制、broker侧的持久化、consumer端的ack机制等。
15 压测
设计高并发系统,离不开最重要的一环,就是压力测试。就是在系统上线前,需要对系统进行压力测试,测清楚你的系统支撑的最大并发是多少,确定系统的瓶颈点,让自己心里有底,最好预防措施。
压测完要分析整个调用链路,性能可能出现问题是网络层(如带宽)、Nginx层、服务层、还是数据路缓存等中间件等等。