1.1.3 云原生的设计原则
顾名思义,云原生是面向“云”而设计的应用,但要给云原生下一个明确的定义很难,所有的架构的目标都是解决特定的业务场景。一方面,业务场景千变万化,而每个人的技术背景不同,站的角度不同,所理解和设计的系统架构也就各不相同。另一方面,架构总是不断演进的,新的技术层出不穷,因此云原生的落地形式与能力边界也在不断演进中。
换一个思路,云原生所倡导的思想与设计原则,或许能更好地让大家理解什么是云原生,云原生具体解决哪一类问题。
1.去中心化原则
中心化意味着单点,为了具备良好的线性扩展能力,分布式系统要求去中心化,避免单点故障。对于系统的服务能力,随着资源加入,微服务的性能和容量能够呈线性扩展。在微服务场景下,每个服务可以独立采用自己的技术方案或技术栈,每个微服务应用独立部署,服务之间进程隔离,每个服务都有独立的数据库,一个服务实例的失效不会导致大规模的故障。这也是微服务架构和SOA非常重要的区别之一。SOA一般有一个中心化的企业服务总线(Enterprise Service Bus,ESB)负责所有服务的注册发现以及调用路由;微服务架构虽然也有一个服务注册中心,但服务注册中心只负责应用启动或者状态变更时做服务推送,真正在运行过程中微服务之间的相互调用都是点对点直接调用,即运行时是去中心化的。
另外,从研发流程的角度来说,去中心化意味着关注点分离。云原生对开发团队一个很重要的要求是独立自主,每个服务由独立的团队负责开发运维,所有者的团队对服务具有决策权,可以自主选择技术栈以及研发进度,服务之间只要接口不变,外部就不必对其过度关注,更容易实现关注点分离。
2.松耦合原则
(1)实现的松耦合:这是基本的松耦合,即服务消费端不需要依赖服务契约的某个特定实现,这样服务提供端的内部变更就不会影响消费端,而且消费端未来还可以自由切换到该契约的其他服务提供方。
(2)时间的松耦合:典型的是异步消息队列系统,由于有中介者(broker),因此生产者和消费者不必在同一时间都保持可用性以及相同的吞吐量,而且生产者也不需要马上等到回复。
(3)位置的松耦合:典型的是服务注册中心,消费端完全不需要直接知道提供服务端的具体位置,而都通过注册中心查找服务来访问。
(4)版本的松耦合:消费端不需要依赖服务契约的某个特定版本来工作,这就要求服务的契约在升级时要尽可能地提供向下兼容性。
3.面向失败设计原则
为了保证系统的健壮性,软件设计领域中一个很重要的原则是,所有的外部输入和外部依赖都是不可信的,系统间依赖的调用随时可能会失败,也包括硬件基础设施服务随时可能死机,还有后端有状态服务的系统能力可能有瓶颈。总之在发生异常时能够快速失败,然后快速恢复,以保证业务永远在线,不能让业务半死不活地僵持着。
面向失败设计(design for failure),意味着所有的外部调用都有容错处理,我们希望失败的结果是我们可预期的、经过设计的。在微服务架构场景中,当服务数量越来越多,依赖越来越复杂时,出现问题的概率也就越大,问题定位也会越来越困难,这时再用传统的解决办法将是一个灾难。传统的方法通常是通过重试、补偿等手段尽可能避免失败,微服务架构下由于存在更多的远程调用,任何外部依赖都有可能失效或延迟,这是潜在的故障和瓶颈。故障总是无法避免的,设计的目标是预测和解决这些故障。因此在设计服务时,应充分考虑异常情况,从使用者的角度出发,能够容忍故障的发生,最小化故障的影响范围。系统架构设计时需要考虑到应用系统的每一个层面,包括硬件和软件是可能出现故障的,并据此在应用系统架构设计上消除单一故障点,从而实现高可用性(High Availability,HA)的系统架构。
4.无状态化原则
云原生的应用服务设计尽可能是无状态的,使得业务天生具有扩展性,在业务流量高峰和低峰时期,依赖云的特性自动弹性扩容、缩容,满足业务需求。无状态指的是服务在处理请求时,不依赖除请求本身以外的其他内容,也不会有除响应请求之外的额外操作。这样如果要实现无状态服务的并行横向扩展,只需要对服务节点进行并行扩展,在服务之上添加一个负载均衡。
将“有状态”的业务处理过程改造成“无状态”的过程,思路比较简单,主要有以下两种手段。
(1)状态分离:服务端所有的状态信息统一保存在外部独立的分布式存储中(如缓存、消息队列、数据库)。
(2)请求附带全部状态信息:将状态信息前置,丰富请求的入参,将需要处理的数据尽可能都通过上游的客户端放到入参中传过来。
5.不变性原则
容器技术带来的最大优势,是通过镜像实现了可编程式的运行环境定义,从而实现了应用与运行环境的解耦。作为一种服务(IaaS),云原生基础设施提供可编程式的需求描述,并实现记录版本变更,保证环境的一致性。使用软件工程中的原则、实践和工具来加强基础设施服务的生命周期管理,这意味着开发人员可以更频繁地构建更强可控或更稳定的基础设施。对资源调度而言,我们希望所有的服务(包括环境)无差异化配置,实现标准化可迁移,而不希望在部署任何服务的过程中还需要手动操作,因为手动操作是无法批量化、自动化执行的,也是不容易回溯的。
实现不变性原则的前提是,基础设施中的每个服务、组件都可以自动安装、部署,不需要人工干预。所有的资源都可以随时拉起、随时释放,同时以API的方式提供弹性、按需的计算、存储能力。每个服务或者组件在安装部署完成后将不会发生更改,如果要更改,就丢弃老的服务或组件,并重新部署一个服务或组件。另外,为了提升可用性,我们应该尽量减少故障修复时间,要知道替换的速度远远快于修复的速度。
6.自动化驱动原则
为了满足业务需求的频繁变动,通过快速迭代,产品能做到随时可发布,这是一系列开发实践方法。自动化驱动分为持续集成、持续部署、持续交付等阶段,用来确保需求的提出、设计开发测试,再到代码快速、安全地部署到生产环境中。持续集成是指每当开发人员提交了一次改动,就立刻进行构建、自动化测试,确保业务应用和服务均能符合预期,从而可以确定新代码和原有代码能否正确地集成在一起。持续部署是指使用完全的自动化过程把每个变更自动提交到测试环境中,触发自动化测试用例,测试验证通过后将应用安全地部署到生产环境中,打通开发、测试、生产等各个环节。持续交付是软件发布的能力,在持续集成完成后,能够提供到预发布之类的环境上,满足生产环境的条件。
应用系统的部署与运维的成本会随着服务的增多呈指数级增长,每个服务都需要部署、监控、日志分析等运维工作,成本会显著升高。在服务划分之前,应该首先构建自动化的工具及环境,开发、测试人员应该以自动化为驱动力,简化服务在创建、开发、测试、部署、运维上的重复性工作,尽可能通过自动化工具完成所有重复的工作。当提交代码后,自动化的工具链自动编译、构建、测试、集成。开发人员持续优化代码,当满足上线要求时,自动化部署到生产环境中。这种自动化的方式能够实现更可靠的操作,既避免了人为失误,又避免了微服务数量增多带来的开发、运维、管理的复杂化。