作者:中国移动云能力中心 —— 胡建华
概要:高可用设计师应用软件架构设计的最基本要求,无论一个产品处于初创阶段还是快速增长期,作为一款商业软件面向用户提供服务,那么可用性的设计是必须予以考虑的。
一、基本概念
可用性:Availability,系统可以被使用的时间的描述,即uptime,计算方式:
A = uptime/(uptime + downtime),其中uptime和downtime分别为可用/不可用时间。
我们经常形容的“几个九”,最多情况下指的就是系统可用性,当然可用性越高越好,同时越高的可用性就代表着越多的资源投入,需要根据实际业务发展的阶段进行权衡,不同级别的可用性通常采取的技术手段可参见下表:
可用性等级 | 可用性数值 | 年最大停机时间 | 常用的技术手段 |
基本可用 | 99% | 88 h | 负载均衡 |
较高可用 | 99.9% | 8.8 h | +自动化部署 |
高级可用 | 99.99% | 53 min | +微服务+应用监控+容错机制+弹性伸缩 |
极高可用 | 99.999% | 5 min | +异地多活+容灾 |
二、影响系统可用性的因素
到底是什么降低了可用性?影响系统可用性的因素都有哪些?
影响可用性的因素
可以看得出来,系统的可用性除了受到技术管理类因素(如不规范的变更等)的影响之外,更多的是来自于系统架构初期的设计方面,如不准确的流量预测、欠考虑的资源冗余方案等。
三、可用性设计的常见方案
作为软件开发者来说,最常见的系统可用性方案,如负载均衡、数据库主备等,对于云产品借助云平台还可能实现了资源的弹性伸缩,以上几点方案均属于常见方案,不做重点介绍,我们重点聊一聊架构设计中的另外两个经常被忽视的可用性方案:容错和限流。
某种意义上来说,前面所描述的负载均衡和数据库主备集群都属于容错的范畴,具体来说是对于系统运行时阶段的容错设计,相比开发阶段的容错设计成本更低,也更容易想到。其实开发或者设计阶段的容错设计对于系统可用性来说才是“关键所在”。
1、容错设计
程序是人开发的,错误当然就是不可避免的,一个好的容错方案可以大大提高软件对于错误的兼容程度,使得系统在遇到突发错误时候仍能够保证一定的可用性。
(1)避免单点
如采用负载均衡策略,相对直接的一种容错方案,通过部署多个节点解决故障节点业务承接的问题,一般节点越多,容错效果就越好。
(2)服务降级
服务降级,是一种“牺牲局部、保全整体”的思想,在系统面临崩坍时,实现保证主线业务的存活。服务降级的主要方式参考如下:
- 关闭非核心功能
- 如电商平台在“618,双11大促”时候,发货的时效性并不是最重要的,那么就可以关闭物流发货服务,空闲出来的资源则可以优先保障产品、订单和计费服务的正常运转;
- 一般的软件系统,通常允许关闭系统日志功能、延迟发通知类短信(非验证类)等。
- 简化系统流程
- 请求短路:当系统流量超过了预定的值时可以简化系统流程,如原本需要查询缓存(90%)+数据库(10%)的组合场景,即使10%的流量比例也会造成数据库的崩坍,可以通过配置实时修改缓存更新的时间的方式,调大缓存命中率,实现缓存部分承载比例的进一步提升,甚至让请求100%命中缓存。另一种思想,则采取更为粗暴的处理方式,直接返回预定格式结果,请求直接在入口处得以折返。上述两种方式均采用了短路的方式对于系统的流程进行了简化,容错性能获得提升;
- 裁剪枝节:指的是系统主流程之外的业务枝节,也即管理类服务,常见的管理类服务包括定时任务、数据采集任务等,该两类任务往往对于系统的CPU和网络IO有一定的影响,在系统流量超标时,可以考虑关闭定时任务、数据采集任务等(如统计类、对账类服务等)。
- 同步变异步
- 业务异步接管:当系统流量压力变大,导致后端部分数据无法及时处理时,将同步任务修改为异步接管的方式,如引入消息队列暂时承接请求,当真实业务恢复后,再进行最终的数据处理,当然此种处理方式会导致数据的一致性方面产生损耗。
- “写”接管:和队列类似,但是承载的不是业务数据,而是在数据库“写”的层面,在数据库前面架构一层缓存,专门接受写请求,降低数据库的“即时写”压力。这是一种变相的异步特性。
- 必备要求
服务降级在落地上有一定的要求,首先,需要对于所有服务进行优先级的排序,编排若降级模型(如A服务压力大,关闭C服务),另外最关键的一点即是“支持一键配置”,支持一键切换,实现对于预定服务模块的关闭、流程的干预,否则服务降级则为空谈。
(3)重试机制
超时重试是比较典型的架构容错设计思想,但是重试机制不可肆意运用,无限制的重试机制对于服务来说可能会是一种灾难。重试的实现方试包括编码和开源框架两种,参考如下:
- 基于编码的重试
对于Java类应用,利用Try-catch原生机制实现重试,即在catch里面直接通过编码实现重试,对于一些高级一点的策略,增加重试时间间隔和重试总次数的控制。此种方式下产生的重试编码和业务逻辑代码严重耦合,通常需要引入异步方式实现,无形中增加了对于线程安全的控制成本。
- 基于开源框架的重试
基于注解的Spring-tryer工具,支持对于重试方法最大执行次数、延迟间隔的设置。
基于Guvua-retrying框架工具,实现了比Spring-tryer更多的策略配置支持,如随机重试、智能等待时间策略、单次限制时间、停止策略等。对于通知类的应用程序,建议添加重试逻辑,使用Guvua-retrying是个不错的选择。
(4)隔离设计
隔离设计的初衷是将错误控制在一定范围,避免错误的传播,对于交易型的业务系统一定要进行“隔离设计”。常见的隔离设计包括如下四种。
系统隔离性设计方法
2、限流设计
限流设计是一种面向未知的设计思想,在流量超过实际容量时,拒绝容量之外的请求以达到保护系统的目的,对于高并发类业务系统,限流是必备选项。
- 限流算法
常见的限流算法主要包括如下三种:
- 固定窗口算法/fixed window
该算法不是真正以上的平滑限流,固定的窗口一般不会设置的太短,通常为分钟级别,然而分钟级别并不能实现精确的流量限制,在窗口交替处可能会带来突发的流量涌入。
不过该算法实现简单,业务开发人员可以利用缓存等常见方案实现,对于一般的系统来说,可以基本满足需求。
- 漏桶算法/Leaky bucket
通过队列来缓冲请求,先进先出,整体上保持出桶的流量是稳定平缓的,该方式下的队列帮助系统实现了“削峰填谷”的效果。
- 令牌桶算法/token bucket
每秒放入x个token,桶最多可以放入m个token;每到达一个请求就消耗掉一个token,该算法是一种改进型的漏桶算法,只要桶内有令牌就支持突发的流量流出队列,所以该算法相对漏铜算法,支持一定程度的突发流量。
(2)限流方案
在微服务架构下,各服务之间实际承载的业务请求量是有很大差距的,所以限流策略不能一概而论的开展,需要做针对性的限流。常见的限流方案参考如下:
- Guvua限流
实现了令牌桶算法,Guava通过提供限流工具类RateLimiter来实现限流目的,该工具提供两种令牌桶的细分算法:
- 平滑突发限流:SmoothBursy
- 平滑预热限流:SmoothWarmingUp
- 专业限流工具
- Sentinel
- Kong
- Nginx限流
在如Nginx类的负载均衡软件上进行的限流,属于“全局入口限流”,实现最粗粒度的限流目的。Nginx的限流主要有以下几种方式:
- 连接数限流(ngx_http_limit_conn_module)
限制并发连接数,流量异常识别、恶意攻击,支持通过健值设置的连接数限制
Limit_conn_zone $remote_request_ip zone=req_server_ip rate=50r/s
实现针对IP地址的每秒50最大连接数的限制。
- 请求限制(ngx_http_limit_req_mobule)
限制请求数,通过漏桶算法实现
Limit_req_zone $remote_request_ip zone=req_server_ip rate=50r/s
Limit_req zone=req_server_ip burst=5
实现针对IP地址的每秒50最大请求数的限制。
四、小结
可用性设计是软件架构设计的最基本要求,可用性设计常常被开发人员所忽略,尤其在云计算时代,可以凭借云原生产品帮助产品轻松实现运行态高可用,而本文所提出的容错和降级方案对于产品设计态阶段仍然具有参考价值。
版权声明 (原创):本文内容由移动云用户自发贡献,版权归原作者所有,移动云开发者社区不拥有其著作权,亦不承担相应法律责任。如果您发现本社区有涉嫌抄袭的内容,可填写举报信息,一经查实,本社区将立刻删除涉嫌侵权内容。