CNCF(Cloud Native Computing Foundation)给出了云原生应用的三大特征:
所以,到底什么是云原生?云原生从字面意思上来看可以分成云和原生两个部分。
云是和本地相对的,传统的应用必须跑在本地服务器上,现在流行的应用都跑在云端,云包含了IaaS、PaaS和SaaS。
原生就是土生土长的意思,我们在开始设计应用的时候就考虑到应用将来是运行云环境里面的,要充分利用云资源的优点,比如️云服务的弹性和分布式优势。
云原生包含了一组应用的模式,用于帮助企业快速,持续,可靠,规模化地交付业务软件。云原生由微服务架构,DevOps 和以容器为代表的敏捷基础架构组成。
微服务解决的是我们软件开发中一直追求的低耦合+高内聚,其本质是讲一款应用软件切分成若干个小服务,这些小服务独立也能运行,且互不影响。
DevOps(Development和Operations的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。
它是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。
持续交付(Continuous delivery,缩写为 CD),是一种软件工程手法,让软件产品的产出过程在一个短周期内完成,以保证软件可以稳定、持续的保持在随时可以发布的状况。它的目标在于让软件的构建、测试与发布变得更快以及更频繁。这种方式可以减少软件开发的成本与时间,减少风险。
简单来讲,虚拟化就是一种将一台计算机虚拟成多台逻辑计算机,在同一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可以安装不同的操作系统,并且这些操作系统上的应用程序可以在相互独立的空间内运行且互不影响,从而显著提高计算机资源利用效率和计算机工作效率。
容器化是应用程序级别的虚拟化,允许单个内核上有多个独立的用户空间实例,这些实例称为容器。
容器提供了将应用程序的代码、运行时、系统工具、系统库和配置打包到一个实例中的标准方法。容器共享一个内核(操作系统),它安装在硬件上。
CNCF Landscape最重要的产出包括一个路线图和一个全景图。路线图(Trail Map)是CNCF对云原生用户使用开源项目以及云原生技术的推荐过程。在路线图的每个步骤中,用户都可以选择供应商支持的产品或自己动手使用开源项目。
整个路线图分成了十个步骤,每个步骤都是用户或平台开发者将云原生技术在实际环境中落地时,需要循序渐进思考和处理的问题:
1.容器化。目前最流行的容器化技术是Docker,你可以将任意大小的应用程序和依赖项,甚至在模拟器上运行的一些程序,都进行容器化。随着时间的推移,你还可以对应用程序进行分割,并将未来的功能编写为微服务。
2.CI/CD(持续集成和持续发布)。创建CI/CD环境,从而使源代码上的任意修改,都能够自动通过容器进行编译、测试,并被部署到预生产甚至生产环境中。
3.应用编排。Kubernetes是目前市场上应用编排领域被最广泛应用的工具,Helm Charts可以用来帮助应用开发和发布者用于升级Kubernetes上运行的应用。
4.监控和分析。在这一步中,用户需要为平台选择监控、日志以及跟踪的相关工具,例如将Prometheus用于监控、Fluentd用于日志、Jaeger用于整个应用调用链的跟踪。
5.服务代理、发现和治理。CoreDNS、Envoy和LInkerd可以分别用于服务发现和服务治理,提供服务的健康检查、请求路由、和负载均衡等功能。
6.网络。Calico、Flannel以及Weave Net等软件用于提供更灵活的网络功能。
7.分布式数据库和存储。分布式数据库可以提供更好的弹性和伸缩性能,但同时需要专业的容器存储予以支持。
8.流和消息处理。当应用需要比JSON-REST这个模式更高的性能时,可以考虑使用gRPC或者NATS。gRPC是一个通用的RPC(远程调用)框架(类似各种框架中的RPC调用),NATS是一个发布/订阅和负载均衡的消息队列系统。
9.容器镜像库和运行环境。Harbor是目前最受欢迎的容器镜像库,同时,你也可以选择使用不同的容器运行环境用于运行容器程序。
10.软件发布。最后可以借助Notary等软件用于软件的安全发布。
网上关于微服务的定义有许多,这里找出作者认为好理解的定义:
微服务架构通过对特定业务领域的分析与建模,将复杂的应用分解成小而专一、耦合度低并且高度自治的一组服务,每个服务都是很小的应用。
微服务的「微」并不是一个真正可衡量、看得见、摸得着的微。这个「微」所表达的,是一种设计思想和指导方针,是需要团队或者组织共同努力找到一个平衡点。所以,微服务到底有多微,是个仁者见仁,智者见智的问题,最重要的是团队觉得合适。但注意,如果达成「团队觉得合适」的结论,至少还应遵循以下两个基本前提:
所谓高内聚,是一个模块内各个元素彼此结合的紧密程度高。低耦合,是指对于一个完整的系统,模块与模块之间,尽可能独立存在。
在面向对象的设计中,更是有放之四海而皆准的「SOLID原则」。SOLID 原则中的 S 表示的是 SRP(Single Responsibility Principle,单一职责原则):即一个对象应该只有一个发生变化的原因,如果一个对象可被多个原因改变,那么就说明这个对象承担了多个职责。
对于每个服务而言,我们希望它处理的业务逻辑能够单一,在服务架构层面遵循单一职责原则。也就是说,微服务架构中的每个服务,都是具有业务逻辑的,符合高内聚、低耦合原则以及单一职责原则的单元,不同的服务通过「管道」的方式灵活组合,从而构建出庞大的系统。
服务之间应通过轻量级的通信机制,实现彼此之间的互通互联,互相协作。所谓轻量级通信机制,通常指语言无关、平台无关的交互方式。
对于轻量级通信格式而言,我们熟悉的 XML 或者 JSON ,它们的解析和使用基本与语言无关、平台无关。对于轻量级通信协议而言,通常基于HTTP,能让服务间的通信变得标准化并且无状态化。 REST(Representational State Transfer,表现层状态转化),是实现服务之间互相协作的轻量级通信机制之一。
对于微服务而言通过使用语言无关、平台无关的轻量级通信机制,使服务与服务之间的协作变得更加标准化,也就意味着在保持服务外部通信机制轻量级的情况下,团队可以选择更适合的语言、工具或者平台来开发服务本身。
独立性是指在应用的交付过程中,开发、测试以及部署的独立。
在传统的单体架构应用中,所有功能都存在于同一个代码库中。当修改了代码库中的某个功能,很容易出现功能之间相互影响的情况。尤其是随着代码量、功能的不断增加,风险也会逐渐增加。
除此之外,当多个特性被不同小组实现完毕,需要经过集成、回归测试,团队才有足够的信心,保证功能相互配合、正常工作并且互不影响。因此,测试过程不是一个独立的过程。
当所有测试验证完毕,单体架构应用将被构建成一个部署包,并标记相应的版本。在部署过程中,单体架构部署包将被部署到生产环境,如果其中某个特性存在缺陷,则有可能导致整个部署过程的失败或回滚。、
在微服务架构中,每个服务都是一个独立的业务单元,当对某个服务进行改变时,对其他的服务不会产生影响。换句话说,服务和服务之间是独立的。对于每个服务,都有独立的代码库。当对当前服务的代码进行修改后,并不会影响其他服务。从代码库的层面而言,服务与服务是隔离的。
对于每个服务,都有独立的测试机制,并不担心破坏其他功能而需要建立大范围的回归测试。由于构建包是独立的,部署流程也就能够独立,因此服务能够运行在不同的进程中。
所有功能运行在同一个进程中,也就意味着,当对应用进行部署时,必须停掉当前正在运行的应用,部署完成后,再重新启动进程,无法做到独立部署。如果当前某应用中包含定时任务的功能,则要考虑在什么时间窗口适合部署,是否先停掉消息队列或者切断与数据源的联系,以防止数据被读入应用程序内存,但还未处理完,应用就被停止而导致的数据不一致性。
为了提高代码的重用以及可维护性,在应用开发中,我们有时也会将重复的代码提取出来,封装成组件(比如 PHP 中的 Composer 扩展包)。在传统的单体架构应用中,当应用程序在运行期时,所有的组件最终也会被加载到同一个进程中运行。
在微服务架构中,应用程序由多个服务组成,每个服务都是一个具有高度自治的独立业务实体。通常情况下,每个服务都能运行在一个独立的操作系统进程中,这就意味着不同的服务能非常容易的被部署到不同的主机上。作为运行微服务的环境,我们希望它能够保持高度自治性和隔离型。如果多个服务运行在同一个服务器节点上,虽然省去了节点的开销,但是增加了部署和扩展的复杂度。
总而言之,微服务架构其实是将单一的应用程序划分成一组小的服务,每个服务都是具有业务属性的独立单元,同时能够被独立开发、独立运行、独立测试以及独立部署。
首先服务提供者(用户、商品等微服务子模块)按照指定格式的服务接口描述,向注册中心注册服务,声明自己能够提供哪些服务以及服务的地址是什么,完成服务发布。
接下来服务消费者(API 网关层或者相邻的其它微服务模块)请求注册中心,查询所需要调用服务的地址,然后以约定的通信协议向服务提供者发起请求,得到请求结果后再按照约定的协议解析结果。
在服务调用过程中,服务的请求耗时、调用量以及成功率等指标都会被记录下来用作监控,调用经过的链路信息会被记录下来,用于故障定位和问题追踪。在这期间,如果调用失败,可以通过重试等服务治理手段来保证成功率。
所以总结起来,微服务架构下,服务调用主要依赖下面几个基本组件:
此外,从微服务部署角度来说,我们通常会基于容器编排技术实现微服务的运维。
对比点 | 微服务架构 | 单体架构 | 结论 |
---|---|---|---|
上手难度 | API接口调用 | 数据库共享或者本地程序调用 | 单体架构胜 |
开发效率 | 早期设计和沟通的工作量加大,随着项目规模和时间的推移,效率变化不大 | 早期工作量小,随着项目规模和时间的推移,效率大幅度下降 | 对于简单项目单体架构胜,对于复杂项目微服务架构胜 |
系统设计(高内聚低耦合) | 每个业务单独包装成一个微服务,数据和代码都从物理上隔离开来,实现高内聚低耦合相对容易 | 以包的形式对代码进行模块划分,控制得当即可实现高内聚。但最终都是在数据层面将整个系统耦合在一起 | 微服务架构胜 |
系统设计(扩展性) | 独立开发新模块,通过 API 与现有模块交互 | 在现有系统上修改,与现存业务逻辑高度耦合 | 微服务架构胜 |
需求变更响应速度 | 各个微服务组件独立变更,容易实施敏捷开发方法 | 需要了解整个系统才可以正确修改,容易导致不相关模块的意外失败 | 微服务架构胜 |
系统升级效率 | 各个微服务组件独立升级,上手和开发效率高,影响面小 | 需要了解整个系统才可以正确修改,容易导致不相关模块的意外失败 | 微服务架构胜 |
运维效率 | 大系统被拆分为多个小系统,部署和运维难度加大,但可以利用 DevOps 等方式将运维工作自动化 | 简单直接 | 单体架构胜 |
代码复用性 | 微服务组件可以在新项目中直接复用,包括前端页面 | 一般以共享库的形式复用后台代码 | 微服务架构胜 |
硬件需求 | 按需为不同业务模块伸缩资源节点,一个系统需部署多个微服务,需要启动多个运行容器 | 整个系统只需要一个运行容器,为整个系统分配资源 | 对于简单项目单体架构胜,对于复杂项目微服务架构胜 |
项目成本 | 项目早期和后期,成本变化曲线平缓 | 项目早期成本低,后期成本大 | 对于简单系统单体架构胜,对于复杂系统微服务架构胜 |
非功能需求 | 为单独的微服务按需调优,甚至更换实现方式和程序语言 | 为整个系统调优,牵一发而动全身 | 微服务架构胜 |
职责、成就感 | 拥有明确的职责划分,主人翁意识和成就感加强,容易形成自组织型团队 | 职责不明确,容易产生扯皮行为 | 微服务架构胜 |
风险 | 大系统被拆分为小系统,风险可被控制在小系统内,但也引入了各小系统之间的交互风险 | 系统是一个整体,一荣俱荣,一损俱损 | 微服务架构胜 |
对于简单项目来说,单体架构 5 胜 8 败,优势主要体现在开发效率、上手难度、运维效率、硬件需求、项目成本;对于复杂项目来说,微服务架构 11 胜 2败,优势主要体现在硬件需求、项目成本、开发效率、系统设计时的高内聚低耦合和可扩展性、需求变更响应速度、系统升级效率、代码复用性、非功能需求、职责/成就感、风险的可控性。
所以对于小型简单系统来说,使用单体架构更合适,对于大型复杂系统来说,使用微服务架构更合适,但是尽管如此,微服务也不是银弹,它也为系统引入了新的问题比如提高了系统的复杂度,这也导致了开发人员上手难度增加,需要在理解分布式系统设计的基础上才能更好的开发和维护微服务,再就是分布式服务的调用问题,服务的注册和发现、服务之间的分布式事务问题,数据库拆分之后数据报表的处理,数据库查询的复杂度增加,服务之间分布式一致性的问题,此外也为系统运维和管理增加了复杂度,这都是我们在进行微服务架构时要做好的心里准备和技术储备。