云原生已经改变了我们构建和运行应用程的理念,他正对传统应用形成冲击,当我们在考虑如何使用容器化来改变我们的应用程序时,云原生已经发生了变化,它不同的变化着,推动我们技术革新。
作为云的入口,阿里云容器服务(以下简称:ACK)当之无愧是佼佼者,ACK在过去几年中形成了跨越式的增长,从一个云的追随者成为了云的领航者,在快速奔跑的这几年间已经变得愈发成熟。对于这种快速增长的业务,我们时如何保证应用的可用性的呢?下面就揭示一下其中的关键点。
提高应用程序可用性的要点
构建一个高可用,可伸缩的应用程序不是一件容易的事情,他不会向天上掉馅饼一样砸到你的头顶。问题总会以你从未预期的方式出现,让你精心审计的功能对所有用户都停止服务。
这些可用性问题通常会在你意向不到的地方出现,甚至一些最严重的我哪天会来自于你认为最可能出现的地方。很少有人能够预料到问题会在何时发生,也不可能依靠测试来发现所有的问题。许多问题都是系统性的问题,从来不仅仅是代码的问题。
为了发现这些可用性的问题,我们需要后退一步,系统的去了解应用程序的运行机制。ACK在稳定性能力建设中,总结出来了一下5个我们常常去关注的点,他支持我们业务高速、高可用的运行。
-
时刻考虑应对故障。
-
时刻考虑如何伸缩,系统,业务架构都必须是可伸缩的。
-
风险管理,如何缓和风险。
-
监控永远是最重要的,不仅要有还必须得是“火眼金睛”。
-
做好准备
时刻考虑应对故障
"所有事情每时每刻都会失败"。我们应当提前为应用程序和服务发生故障做出计划;假如我们的应用程序发生了故障,那么它是如何发生的呢?我们在设计和实现方面都优先从可用性出发。
功能设计
我们在功能开发和设计的过程中,对每种实现做了严格的评审,从根源上避免了糟糕的设计,设计一定要是符合业务未来增长的,一定是符合用户习惯的。
其次实现过程中,通常优先考虑设计模式,用好的设计模式来提升我们软件的可用性。通过使用设计模式,例如重试机制、熔断机制,它帮助我们避免了很多因网络问题,依赖组件不稳定导致我们服务功能受损的问题,故障率得到提升。即使出现问题,我们也能够限制问题的影响范围,即使某些功能出现了问题,依然能够提供其他功能供用户使用。
考虑依赖
随着系统越来越庞大,我们的服务依赖者10+外部服务。我们时刻关注着依赖服务的状态,如果我们依赖的组件出现了故障,我们会怎么做?是否有降级措施?如何进行重试?假如问题是一个无法恢复的故障(例如:ZB机房全面断电)我们该怎么做?如果是一个软件故障(例如:Panic)呢?如果上面几个问题你都解决了的话,那么我要恭喜你。
对于依赖组件:我们主要是解决的是依赖服务故障时,我们如何降低我们的损失,尽可能的保证服务的可用性。通常有这些手段来减缓影响范围:
-
对于一些数据服务,可以加入缓存,我们优先读缓存,这样很有效降低依赖影响
-
推动依赖产品,将自己的服务改造成高可用的服务
-
增加降级措施,一旦依赖服务不可用了,我们可以关闭对应的功能点。例如:ECS有问题,我们可以关闭和ECS相关的功能,避免用户创建集群,或者扩容集群。
流量分析
对于我们的服务,我们也需要考虑流量来源、流量的大小。如果出现问题的原因时系统某个用户,我们怎么做?我们是否能够处理海量的请求?如何限制海量的流量?如何处理传入的垃圾数据?在数据量很大的情况下,我们怎么做?
业务一定是不断增长的(我们肯定是)。用户的流量会越来越大,我们必须时刻想着如何应对这些海量的请求。有些时候,用户的应用程序可能有一个bug,导致向我们的应用程序拼命的发送请求。压力过大可能会拖垮我们的服务,一个错误的SQL也能拖垮我们的数据库。
对于上面说到的问题,ACK在实践过程中,我们做到了很多流量保护的措施,避免流量拖垮我们的系统。
-
API维度限流。
-
用户唯独限流,对于一个API,我们对每个用户的流量做了限制。限制在我们系统响应能力的中位数。
-
每个参数都经过了严格的校验,避免错误的值导致功能受损。
-
服务Region化,避免中心化部署的压力(正在设计)
-
服务采用云原生方式部署,充分利用云的弹性能力。
时刻考虑如何伸缩
我们的系统在正常平稳的运行,并不意味着它明天还能够继续正常运行。大多数web应用程序应用程序的流量都是不断增加的,一个今天产生一定量的网站,明天流量可能远比你想象的大的多(淘宝就是一个非常典型的例子)。当你构建系统是,不能只考虑当前流量,还要考虑未来的流量。所以我们需要:
-
设计出能够增加数据库数量和容量的架构,当然了我们现在有RDS、PolarDB,能够省不少事。
-
要考虑限制数据量伸缩的因素,如果数据达到极限容量,会出现什么?我们需要提前限制,并解决掉。
-
我们的应用自身要是高可用的,是需要要支持弹性的,我们的应用事有状态的,我们做了优雅退出,防止状态中断。
-
我们的应用程序部署方式,或者说应用程序服务器,必须要是弹性的。
-
静态流量优先使用CDN来降低网络延时
-
应该静态的动态资源,我们就让他静态。
风险管理
保持系统高可用,需要消除系统中的风险。当系统发生故障时,通常我们在这之前已经将故障原因确定为了风险。因此,确定风险时提高可用性的一个重要方法,我们系统中通常会出现下面这些风险:
-
系统奔溃的风险,例如:Panic
-
数据库奔溃的风险,例如:容量上限、慢SQL
-
返回结果不正确的风险,例如:节点信息不一致
-
网络链接失败的风险,例如:Timeout
-
新部署/新发布分支出现故障,变更引起的故障。
这么多的风险,我们需要消除才能收获系统的高可用。但是当系统越来越复杂时,消除所有的风险也变得越来越不可能实现。保持一个大型系统的高可用,我们更多的是来管理系统的风险,知道这些风险是什么(也就是我们一定要知道),哪些风险是可以接受的(这种通常是解决不了的),已经规避/缓和风险的手段。
风险缓和
风险管理中的一部分便是风险缓和,风险缓和是指当问题发生时,我们知道如何去尽可能的降低问题所带来的影响。缓和意味着及时当服务和资源不可用时,依然尽可能的保证我们系统是以完整的状态在提供服务。
风险缓和需要考虑哪些事情会出错,并且立即制定应急预案。
举个,对于淘宝来讲我们支付出问题,但不影响用户浏览商品,这就是一种风险缓和。
风险管理经常会暴露应用程序中未知的,需要立即修复的问题。它还可以用来处理已知的故障问题,减少故障恢复时间,降低爆炸半径。
可观测性
我们的系统咋线上跑着,如果没有监控,没监控。那就行人在黑夜中行走,前路永远是未知的。除非你看到问题发生,不然你不会知道应用程序中存在着什么问题。我们必须确保我们对应用程序进行了全方位的监控,这能让我们可以从外部和内部2个视角了观察应用程序的运行状况。
监控
监控的程度取决于应用程序的特点和要求,我说说我们的系统具备哪些监控。
- 基础监控:主要针对应用程序服务器
-
- CPU、MEM、IO、Load1,5,10 ....
- 应用监控:
-
- RT、QPS 、PV 、UV ....
-
配置变化监控:配置项变化,证书变化
-
性能监控
-
功能监控:引用程序功能,例如:创建集群、删除集群
-
API 可用率监控
-
依赖服务监控
-
测试:e2e,UI,接口测试等
报警
可观测中还有重要的一部分,那就告警。没有告警的监控,意义不大,除非你找几个人天天看着大屏上的指标,当然这也很有效。
当问题发生时,我们必须要能通知道相关人员,以便问题可以得到快速解决,将影响面降到最低。
当我们对应用程序和服务进行了监控之后,我们便可以开始寻找业务运行的趋势(这一步很重要,可以很有效的缓解告警轰炸),明确了趋势和规律之后,我们便可以针对一些异常值配置告警,然后逐步优化它。
报警从来不是无脑报,随意的报警将没任何价值。所以:
-
报警尽可能时准确的,这通常是一个不断优化的过程
-
报警指标一定是能反馈问题的
-
报警响应机制必不可少。报警了,没人响应,报警也就失去了它的作用了。
-
报警应该是能升级的,避免长时间无响应
-
电话报警是必要的。SLS里面很容易配置
做好准备
用确定的方式来应对问题
我们一定沉淀出来标准的流程和规范,并将其整理到大家都能访问的公共仓库,团队每个人都能查阅。该手册一定要是步骤清晰的,所有操作都是经过验证的。其次我们应该备注上相关系统和服务的联系人列表,以便我们不能解决时,能找到负责人。当无法用操作文档来解决问题时,我们必须要将问题上报,所以我们也需要备注,升级联系人的列表
所有的流程、办法、手册都应该提前准备好,以便当问题出现时,值班同学能够准确知道如何在不同的情况下进行操作,快速恢复服务。
案例:容器服务专有云便维护了自己的知识库,并可通过钉钉机器人进行呈现,非常便捷。
时刻准备着
我们不能预测到未来会发生什么,不知道问题会出现在什么地方,什么时间发生。但是我们可以假设他们会发生,尤其是当我们的系统面临越来越对的用户需求,变得越来越复杂的时候,时刻做好准备,是降低问题严重性的可靠方法。