1.4 云原生应用

云原生应用程序的关键在于提供弹性、敏捷性、可操作性和可观察性。弹性的概念隐含了允许应用程序失败而不是试图阻止程序失败的意思。敏捷性允许应用快速部署和快速迭代,这就需要引入DevOps文化。可操作性是指从应用程序内部控制应用程序的生命周期,而不是依赖外部进程和监视器。可观察性是指应用程序需要提供信息以反映应用程序的状态。目前实现云原生应用程序所需特性的常用方法有:

● 微服务。

● 健康状况报告。

● 自动测量数据。

● 弹性。

● 声明模式而不是响应模式。

1.4.1 微服务

传统应用程序是以单个实体为目标进行管理和部署的,这也是国内软件行业常用的开发方式,简称为单体应用程序。单体应用程序的好处是显而易见的,但是它无法解决面向大量互联网用户提供服务的并发量问题,且使得开发过程变得臃肿,开发进程变得缓慢,维护也越来越困难。

解决这些问题最好的方法之一就是分解单体应用为众多小的服务模块。如图1-5所示,这些服务模块相互独立,使得开发人员可以独立维护这些小系统,而且开发和维护过程也变得敏捷。分解成微服务后,各服务的编写语言也可以自行确定,只需要遵守总体的API优先和通信要求即可。

图1-5 微服务架构

微服务更像是UNIX哲学的实践和改造。UNIX哲学是“程序应该只关注一个目标,并尽可能把它做好。让程序能够互相协同工作。”例如UNIX命令行上的统计文件数量,通过下面的命令管道就可以把列表和统计两个命令串联起来使用。

微服务也是如此,服务更专注于其用途,也就是只应做一件事,并把这件事做好。

但是微服务不能等同于云原生架构,微服务只是云原生文化的一种实现。

1.4.2 健康状况报告

为了能够由软件控制一切,应用程序必须提供可供管理软件监测的度量指标。而一个应用程序的度量指标只有创建应用程序的作者最清楚,因此在应用程序中内置度量指标是最好的设计方式。这要求各应用程序提供必要的端点,供管理软件访问以判断应用程序状态。例如Kubernetes、ETCD都通过HTTP提供了大量的度量指标。

此外,应用程序应该提供更加丰富且必要的状态报告。在云原生架构下,一切皆是代码,一切皆可由软件控制。为了可以控制,各应用程序必须提供度量接口让管理软件获知应用程序运行状态以做出必要的反应,例如应用程序崩溃时,管理程序可做出停掉当前应用程序实例然后启动新实例的操作。应用程序的健康状况只是能够自动执行应用程序声明周期的一部分,管理程序还需要知道应用程序是否正在工作。

1.4.3 自动测量数据

自动测量数据是做出决定所必需的信息,这些数据与健康状况报告的数据是有重叠的,但是它们的用途不一样。健康报告是告知管理程序所辖应用程序的生命周期状态,而自动测量数据是告知应用程序的业务度量指标。

度量的指标一般称为服务级别指标(Service Level Indicator,SLI)或关键绩效指标(Key Performance Indicator,KPI)。这些指标是特定于应用程序的数据,让管理程序监测应用程序的性能在服务级别目标(Service Level Objectives,SLO)内。自动测量数据可以解决以下问题:

● 应用程序每分钟收到的请求数。

● 是否有任何错误。

● 应用程序延迟多久。

● 业务处理需要多长时间。

监测数据经常被抓取或推送到时间序列数据库(如Prometheus或InfluxDB),然后再由度量指标模型进行处理分析,以便后续提醒或者大屏展示。

有一点需要注意的是,自动测量数据应该用于提醒场景而不是健康监测。在一个动态可自我修复的环境中,管理程序几乎不关心单个应用程序的生命周期,而更多关心应用程序的SLO,因为若是一个程序崩溃,管理程序可以动态重启应用程序实例以恢复正常运行状态。

举一个例子,在Kubernetes中运行以下命令可以看到coredns重启过两次和3次,但是管理程序不关心这个行为,只关心它是否在正常运转,因为管理程序的SLO就是要正常运转。

1.4.4 弹性处理故障

云原生应用程序应当正视故障而不是竭力避免故障。唯一不应该有故障的系统是那些维持生命的系统。任何系统都应该有一个合理的SLO,如果无视SLO而去避免故障发生,则花费的成本将非常巨大。为此,必须假设应用程序可能发生故障,并采取必要的措施应对故障,这是云原生应用的一种模式。

无论发生什么样的故障,云原生应用都必须适应,并采取合理的调整措施应对。

此外,云原生应用还需要设计一种方法应对过载,这也是互联网应用面临的常见问题,例如12306售票、淘宝双11活动时的并发访问过载。处理过载的一种常见方法是适度降级。在《Site Reliability Engineering》一书中对于应用程序的优雅降级做了详细描述。书中指出在负载过重的情况下,可以采取降低准确性、反馈少量数据等方式给用户适度响应,而不是拒绝服务。

尽管减少应用程序负载可以通过负载均衡设备或者动态扩展等基础架构进行处理,但是应用程序仍有可能接收到超负载的请求。所以云原生应用要求具备服务优雅降级的能力。最现实的处理方式是服务降级,返回部分回应或使用本地缓存中的旧信息进行回应。

这部分内容在Service Mesh中得到了很好的解决。

1.4.5 声明式通信

由于云原生应用程序在云环境中运行,因此它们与基础架构和支持应用程序的交互方式与传统应用程序不同。在云本机应用程序中,与任何内容进行通信的方式都是通过网络。很多时候,网络通信是通过RESTful HTTP调用完成的,但也可以通过其他接口实现,例如远程过程调用(RPC)。

传统应用程序可以通过消息队列、共享存储上的文件或触发shell命令的本地脚本来自动执行任务。事件发生后,通信方法根据本地服务器上的信息做出反应。例如,如果用户点击提交,则运行本地服务器上的提交脚本。

在传统应用程序中,通信的介质可能是文件或者消息队列,但是这些方式都是尝试构建避免失败的方式,它们在云原生架构下存在一些问题。例如应用程序把结果写入到文件,写完后应用程序崩溃了。此时会出现了一种情况:应用程序崩溃之前,计算结果已经写入文件中。按照云原生理念,此时应用程序将重启,再次执行计算过程,计算结果再次写到文件中。因此,开发人员应该停止使用反应式通信,开始使用声明式通信,从而提高应用程序的健壮性,并且减少应用程序的依赖。