什么是高可用
简单来讲就是我们系统如何提高对外的服务时间,想要系统达到100%可用基本是不太可能的,也内有一个专门的衡量标准SLA(全称:Service Level Agreement),也就是有几个9的高可用性:
- 90% (1个9):系统全年有36.5天不可用;
- 99% (2个9):系统全年有3.65天不可用;
- 99.9% (3个9):系统全年有8.76小时不可用;
- 99.99% (4个9):系统全年有52.56分不可用;
- 99.999% (5个9):系统全年有5.26分不可用;
每家公司都在为提高9的个数而努力,当然这不能只靠研发部门就能做到的,需要所有部门的相互配合包括:测试,运维,数据中心,基础架构等等;本文仅从研发的角度来看看如何提高系统的可用性。
如何提高
想要提高系统的可用性,我们需要整合多种手段来保证,当然在使用各种手段之前,作为研发人员我们首先是要保证自己代码的高质量,这是一切的前提,如果不能保证代码的质量,再多高可用的手段都是徒劳;下面具体看看都有哪些常用手段;
负载均衡
高可用很重要的一个手段就是避免单点,为啥要避免单点,因为每台机器都会有出问题的概率,当某一台机器出现问题,其他机器节点同样可以提供相同的服务,来保证系统的可用性;从整个系统来看,每一层都需要避免单点,从系统的接入层到服务层到最后的数据层都避免单点的情况下才能保证整个系统的高可用;这时候负载均衡器就起了至关重要的功能,一个负载均衡器包含如下一些核心功能:
- 操作单元配置;
- 负载均衡算法;
- 失败重试;
- 健康检查;
负载均衡器一方面避免了单点的出现,另一方面多个节点共同提供服务,提供整个系统的能力;每一层都有各自的负载均衡器:
- 接入层:最常见的就是
Nginx
了,通过配置文件简单配置一下即可,提供了多种负载均衡算法选择,健康检查等功能; - 服务层:服务层主要的就是微服务框架比如
Dubbo
,Spring Cloud
等,内部都集成了负载均衡策略,使用起来也是非常方便; - 数据层:这一层相比其他层每个节点是有状态的,不能简单的横向扩展,一般的做法是为数据做分片处理,每个节点存放部分数据,同时每个节点提供备节点,出现问题可以实时切换。
隔离
隔离在为了系统在发生异常时,能将此异常限定在一定范围之内,不会产生蝴蝶效应,导致整个系统不可用;隔离的手段有很多比如线程隔离、进程隔离、集群隔离、机房隔离等等,这里重点看一下离开发人员更近的线程隔离;
看一个最常见的场景,在微服务架构下,某一条调用链中的某一个服务出现问题,导致线程阻塞,然后阻塞越积越多,占满所有的io线程,最终当前服务无法接受数据,直至奔溃;这时候线程隔离就起作用了,可以将io线程和业务线程分开,这样业务线程出现问题不至于影响到io线程;
一些框架提供了自己的线程模型,比如Dubbo
的Dispatcher调度器可以配置消息的处理线程,包含了多种处理方式:
all
所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。direct
所有消息都不派发到线程池,全部在 IO 线程上直接执行。message
只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。execution
只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。connection
在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
当然可以使用第三方工具来实现隔离,比如Sentinel
、Hystrix
;提供了线程池隔离、信号量隔离;
限流
我们每个系统其实都有一个处理峰值,当接近峰值继续接受请求的时候,会导致整个系统响应缓慢;为了保护系统,需要拒绝处理过载的请求,这时候就需要用到限流 ;常见的限流算法:
- 令牌桶限流
- 漏桶限流
- 计数器限流
一般我们做限流可以在接入层限流,也可以在业务层做限流,下面简单看一下都有哪些限流工具:
- 接入层限流:
OpenResty
在nginx
的基础上做了功能扩充,其中有一项就是限流模块,使用lua-resty-limit-traffic
模块进行限流; - 业务层限流:业务层可以做进程内的限流、也可以做分布式限流;进程内限流可以直接使用
guava
来实现,内置了很多限流算法的实现;分布式限流主流使用Redis+lua
和OpenResty+lua
来实现;
降级
当我们的系统出现访问量大增,比如遇到大促的情况,如果出现系统资源不够用的情况下,可以优先把资源给核心功能,对非核心功能可以做降级处理,保证核心功能的可用性,这是一种有损的手段;常见的降级手段:
- 自动开关降级:可以通过系统自动做统计然后做降级处理,比如超时降级、统计失败次数降级等;
Sentinel
提供了多种自动降级策略,可以通过控制台直接配置; - 人工开关降级:给非核心功能模块配置降级开关,比如遇到大促期间,可以手动开启开关实现降级;
- 读服务降级:对于一些一致性要求不高的场景,读服务可能在每一层都做缓存比如:接入层缓存、应用层本地缓存、分布式缓存、DB/RPC服务,可以根据实际情况做读降级处理;
- 写服务降级:写服务做降级处理一般都是做异步化处理,比如将数据写入MQ,然后接收MQ数据再写入数据库;
超时重试
为什么要设置超时时间,因为如果不设置超时时间,可能因为某个请求无法即时响应导致整个链路处于长时间等待状态,这种请求如果过多,直接导致整个系统瘫痪,抛出超时异常其实也是及时止损;系统的每一层几乎都可以设置超时时间比如:数据库超时、缓存超时、RPC超时、网关超时、Web超时、Httpclient超时等;所以对于开发人员来说超时时间的设置至关重要;
重试往往伴随着超时一起出现,因为超时可能是因为某些特殊原因导致暂时性的请求失败,也就是说重试是有可能出现请求再次成功的;限制一定的重试次数(常见设置为2次),也是很有必要的;
其他
除了以上几种开发人员比较关注的高可用手段,还有一些其他的手段:
- 回滚机制:当上线新功能时,我们需要提供部署版本的回滚机制,这样如果生产出现问题,可以快速回滚将影响降到最低;数据回滚机制,游戏行业遇到重大问题时,会使用回档处理;代码版本回滚,回滚到上一个稳定的版本;
- 监控系统:对系统做全链路监控,保证相关人员可以第一时间发现系统问题;
- 压测预案:电商系统一般在大促之前会对系统进行全链路压测,并对出现的问题提供应急预案;
- 灰度发布:当要上线新功能,首先只是更新少量的服务节点,通过路由权重,让少部分用户体验新版本,如果没有什么问题,再更新所有服务节点;这样可以在出现问题把影响面降到最低,保证了系统的可用性;
- 攻防演练...
总结
以上介绍了要想实现一个高可用系统的各种手段,但不是说我们实现一个高可用的系统,就要把上面的手段一股脑的实现一遍;需要根据不同的系统类型、处在何种阶段、以及不同的系统模块等进行对应的取舍。
更多
感谢关注
可以关注微信公众号「回滚吧代码」,第一时间阅读,文章持续更新;专注Java源码、架构、算法和面试。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。