持续演进的Cloud Native:云原生架构下微服务最佳实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.4 如何衡量Cloud Native的能力

实际上,以成熟度模型来描述Cloud Native要优于使用精确的定义。表1-1通过Cloud Native 采用的技术点来描述架构的等级。当然有可能你目前处于第一级,但是已经使用了一些第三级的技术点,这没有关系,这个表只是起到一个参考作用,并不要求严格按照其中的等级执行。也就是说,哪怕现在处于第一级,也完全可以直接跳过第二级到第三级。但是一个系统的架构,通常有一个发展过程,并非一步到位的。如果采用比较成熟的公有云,实际上已经具备了第三级的能力,需要做的仅仅是在应用层面做到服务化。

表1-1 Cloud Native成熟度模型

① 作为服务提供使用,开发、测试、部署、维护都有专门的团队负责,只需要通过接口调用,轻松实现多租户需求。

1.5 Cloud Native的原则

在软件设计的过程中,团队会用一些原则来约束开发者,将其作为开发者共同遵守的标准或规则。本节许多地方都会涉及原则,对原则的描述是为了阐释Cloud Native的宏观思想。

为失败设计原则

试想,如果让你设计一条非常可靠的海底铁路隧道,那么你应该怎么设计呢?因为这涉及很多人的生命安全,你必须消除所有失败的可能性。事实上,通常会建立三条相互连接的隧道,其中两条可以容纳铁路运输,另一条既可以用来维护,也可以在紧急情况下用于逃生。30年前的英法海底隧道正是采用了这个设计思路。从架构的角度讲,为失败设计同样重要,因为失败是不可避免的,我们希望失败的结果是我们预料到的,是经过设计的。

在微服务架构场景中,当服务数量越来越多,依赖越来越复杂时,出现问题的概率会越来越大,问题定位也会越来越困难,这时候再用传统的解决方法将是一个灾难,传统方法通常将可靠性等同于防止故障,需要在思想上进行转变。微服务架构由于存在更多的远程调用,任何的外部依赖都有可能会失效或延迟,这是潜在的故障和瓶颈。例如,试图一味地提升硬盘的质量不如存多份数据更可靠,因为失败是不可避免的,所以设计目标是预测并解决这些故障。因此,设计服务时应充分考虑异常情况,从使用者的角度出发,能够容忍故障的发生,最小化故障的影响范围。例如,在电商系统中,价格很重要,如果价格服务整体不可用,那么这时候前端详情页显示一个没有价格的页面比一个什么也不显示或者显示后端服务失效的页面强得多。

不变性原则

我们已经听到过很多由于运维人员删错了文件,或者配置错了参数而导致的故障。对于资源调度来说,我们更希望所有的服务无差异化配置,所有的服务都是标准的,而不希望在部署任何服务的过程中还需要手动操作,手动操作是不容易回溯的。

实现不变性原则的前提是,基础设施中的每个服务、组件都可以自动安装、部署,不需要人工干预。每个服务或组件在安装、部署完成后将不会发生更改,如果要更改,则丢弃老的服务或组件并部署一个新的服务或组件。另外,为了提升可用性,我们应该尽量减少故障修复时间,要知道替换的速度远远快于修复的速度,这种思想与不可变对象[3]的概念完全相同。

去中心化原则

中心化往往代表的是瓶颈点,在微服务场景下,每个服务可以独立采用自己的技术方案或技术栈,因为每个服务具有自己独立的业务场景,可以根据实际情况进行选择。服务之间通过进程隔离,每个服务都有独立的数据库,一个服务实例失效不会导致大规模故障。相对于单体架构,这是一种去中心化的设计,系统没有一个物理或者逻辑的中心控制节点,不会因为一个节点的故障导致整个系统不可用。

另外,从研发流程的角度来说,去中心化意味着关注点分离。在微服务场景下,每个服务由独立的全功能团队负责,相对来说,更容易实现关注点分离。团队具有决策权,每个服务可以独立的开发、测试、部署、升级,只要接口不发生变化,对其不必过度关心。

Cloud Native对开发团队的一个非常重要的要求是独立自主。可以尝试反问团队:“服务是否经常必须经过团队外人员审批才能上线?”当然,责任和权力是相辅相成的,例如,开发团队需要回答这样的问题:系统由 Java 换成了 Go,是否具备充足的理由?一旦出现问题是否能解决?

标准化原则

很多人都听说过宠物和牲畜的故事。宠物是有名字的,宠物和人有感情,不能被随意替代,而牲畜只是产奶、产肉,很容易被替代,标准化能让软件更像牲畜,而不是宠物。当所有程序都非常标准的时候,采用自动化的手段管理更容易。例如,如果我们都采用相同的微服务框架,那么服务之间的调用将变得非常容易。而且,团队间发生人员流动,也不再会因为换了一种框架而需要漫长的熟悉时间。当所有的日志打印都遵循某种标准的时候,对于排除故障,日志分析将非常重要。

这些标准最好不是通过规范性的文档来实现。例如,公司的编程规范,大多数人只有入职时可能会看一遍。最好的方式是通过框架来固化这些标准,通过工具来检测这些标准。例如可以利用SonarQube检测代码重复度,用CheckStyle检测代码风格。

标准化包含的范畴非常广,从最简单的操作系统、HostName到软件安装的目录、参数、版本、配置文件等,业界很少有统一的标准,因为很多系统存在历史遗留问题,标准化的成本比较高。但是,一旦系统规模变大,对可用性要求变高,做到标准化是无法逃避的。

独立自主和标准化是一对互斥的原则,独立代表的是灵活、创新,而标准则代表效率、稳定,两者需要权衡。所谓独立自主是在一定的标准下实现的。例如JMS规范,如果都遵循规范,就不会有目前炙手可热的Kafka,又如SQL92与MVCC(Multi-Version Concurrency Control,多版本并发控制)的关系,标准化不能成为一种束缚。

当公司内某个工具或者服务不成熟的时候,就会出现标准化的问题。随着工具或者服务的成熟,标准化成为一种常态。例如公司内的微服务框架,最初通常是野蛮生长的,会出现很多版本,因为初期研发人员对微服务框架的理念理解不一。随着时间的推移,理念逐步统一,认识到一套框架就可以解决问题,既可以节省研发投入,又可以形成标准化。

速度优先原则

美国利宝相互保险公司执行副总裁兼首席信息官James McGlennon说过:“如果你不能提高上市速度,毫无疑问,市场将发生变化,无论你对产品的设计、构建、部署或对员工的培训有多好,都不会完全适合市场需求,只因为晚了一点点。”

如果要赢得客户,赢得市场,速度无疑是更重要的,因为你要比竞争对手更快。速度和效率并不总是矛盾的,效率在大多数场景下是为速度提供条件的,效率有时意味着更多的流程,更多的依赖,效率更像一种“节流”方法,而速度是接近于“开源”的一种手段。当速度和效率发生冲突时,速度优先。

这就是你经常见到的,在微服务架构的场景下,有时会存在重复代码、重复开发,这样做的原因是为了减少依赖,使系统可以更独立。

简化设计原则

NBA的数据统计非常全面,评价一个控卫的非常重要的指标之一就是助攻失误比。相应的,一个球队的助攻失误比也经常被作为球队的衡量指标。当追求更多助攻的时候,就会产生更多失误,因此当进行到关键阶段,如5分钟内差距小于5分的时候,球队一般都会选择单打,减少传球,目的是为了减少失误。架构也是一样,越是基础的服务,越需要稳定,越需要简化设计、简化运维。简化设计也是Amazon和Netflix的软件设计原则。

如果你发现设计满足了所有的要求,说明你一定过度设计了。你可能花了更多的时间,设计了一个非常复杂的系统,这对无论是开发复杂度还是工作量,都是一个巨大的挑战。所有的架构都应该本着简单的原则,在架构设计之后,应该多次简化架构设计方案。当然这并不表示所有的地方都追求简单,而是要有所侧重。根据帕累托法则(又称二八定律),80%的产出源自20%的投入。我们首先要简化范围,找到那20%的需要努力的点。通过这种方式找到核心业务流程,投入80%的时间去设计它。

一个非常重要的检测方法是,产品经理的所有想法都实现了吗?按照以上逻辑,大多数产品经理提出的不合理的业务实现应该在发布到线上之前被否定,因为一旦上线,再下线就非常麻烦,会损害一部分用户的体验。

自动化驱动原则

波音公司分析了从1995年到2005年的数据,发现55%的生产事故是人为原因导致的,而根据《SRE Google运维解密》一书中的描述,70%生产事故来源于部署变更。传统行业做到高可靠更多的是通过严格的流程把控,严控每个环节的质量,减少变更次数。为了减少故障,投入更多的测试人员,甚至通过绩效“威胁”研发人员提升质量,这势必带来速度上的劣势,这就是为什么传统行业的公司见到互联网公司感觉用不上力的原因,这是长期积累形成的一种文化,公司的“DNA”很难改变。

部署与运维的成本会随着服务的增多呈指数级增长,每个服务都需要部署、监控、日志分析等运维工作,成本会显著提升。在服务划分之前,应该首先构建自动化的工具及环境,开发人员应该以自动化为驱动力,简化服务在创建、开发、测试、部署、运维上的重复性工作。任何重复性的工作都应该自动化。当代码提交后,自动化的工具链自动编译、构建、测试代码,验证代码是否合法,是否满足统一标准,是否存在安全漏洞。开发人员持续优化代码,当满足上线要求的时候,自动化部署到生产环境,这种自动化的方式,能够实现更可靠的操作,既避免了人为失误,又避免了微服务数量增多带来的开发、管理复杂化。

只有真正拥抱自动化的时候,才能做到持续发布,才能做到更好的用户体验。

演进式设计原则

我曾经经历过这样的一个失败案例:当时设计一个网站,负责人要求我们前期做大量的架构设计,考虑很多两年后的功能,并要进行大量的论证。但是当网站上线的时候,才发现很多客户的需求并不是我们最初设计的,虽然我们最初做了很多可用性相关的设计,仍然出现了很多故障,导致可用性非常低。

架构是持续演进的,并非一蹴而就的。单凭设计阶段很难达到理想的目标,需要不断锤炼。理想主义者会认为架构设计一旦完成,只要按照方案执行,就会设计出满意的软件。但是在软件开发过程中会遇到各种各样的问题,有可能会否定之前的某个决策,导致一系列变化。初级阶段应该采用尽可能简单的架构,因为初级阶段对需求、规模等都不是十分确定,可以采用快速迭代的方式进行架构演进。很多互联网公司都强调架构演进,如腾讯的一条重要发展原则就是“小步快跑”。


[1].英文为Chaos Monkey,国内大多翻译为猴子工程师,意指通过有规则的破坏来发现系统脆弱性的工程师。

[2].第3章详细介绍了敏捷基础设施及公共基础服务。

[3].在面向对象和函数式编程中,不可变对象(Immutable Object)是指一个对象在创建后,其状态无法修改。不可变对象更容易构造、测试及使用。