今天来聊一聊微服务,初衷是:1、微服务现在确实很火 2、虽然大家张口闭口微服务,但是大家对微服务的理解确实千差万别,甚至有误解。(19年1月份未完成的文章)
“微服务”一词源于Martin Fowler的名为 Microservices 的博文,可以在他的官方博客上找到: http://martinfowler.com/articles/microservices.html。
简单地说,微服务是系统架构上的一种设计风格, 它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful API进行通信协作。 被拆分成的每一个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。
值得注意的是,这里所说的“小”是以业务边界来区分的,而不是根据代码的多少来区分的。每个服务都运行在一个单独的进程中,服务之间通过轻量级的机制进行通信,例如使用HTTP资源接口;每个服务都可以通过全自动化的部署机制来独立部署;微服务中的各个服务可以以多种语言来编写,但是在实际开发中,由于各个公司的技术栈有限,通常会指定一门擅长的技术语言,例如Java;每个服务都可以使用不同数据存储技术,例如MySQL、Cassandra及MongoDB等,但是为了统一,通常各个服务还是会选用同一种存储技术。
为什么需要微服务?通常会将微服务架构和单体架构进行比较。二者的架构图对比如下:
图 单体架构与微服务架构图比较
在以往传统的企业系统架构中, 我们针对一个复杂的业务需求通常使用对象或业务类 型来构建一个单体项目。 在项目中我们通常将需求分为三个主要部分:数据库、服务端处理、前端展现。在业务发展初期,由于所有的业务逻辑在一个应用中,开发、测试、部署 都还比较容易且方便。但是,随着企业的发展,系统为了应对不同的业务需求会不断为该单体项目增加不同的业务模块;同时随着移动端设备的进步,前端展现模块已经不仅仅局限于Web的形式,这对于系统后端向前端的支持需要更多的接口模块。单体应用由于面对的业务需求更为宽泛,不断扩大的需求会使得单体应用变得越来越腕肿。单体应用的问题就逐渐凸显出来,由于单体系统部署在一个进程内,往往我们修改了一个很小的功能,为了部署上线会影响其他功能的运行。 并且,单体应用中的这些功能模块的使用场景、并发量、消耗的资源类型都各有不同,对于资源的利用又互相影响,这样使得我们对各个业务模块的系统容量很难给出较为准确的评估。所以,单体系统在初期虽然可以非常方便地进行开发和使用,但是随着系统的发展,维护成本会变得越来越大,且难以控制。
为了解决单体系统变得庞大脯肿之后产生的难以维护的问题, 微服务架构诞生了并被大家所关注。我们将系统中的不同功能模块拆分成多个不同的服务,这些服务都能够独立部署和扩展。由于每个服务都运行在自己的进程内,在部署上有稳固的边界,这样每个服务的更新都不会影响其他服务的运行。同时,由于是独立部署的,我们可以更准确地为每个服务评估性能容量,通过配合服务间的协作流程也可以更容易地发现系统的瓶颈位置,以及给出较为准确的系统级性能容量评估。单体应用和微服务应用对比图如下:
图 单体应用和微服务应用对比图
在实施微服务之前,我们必须要知道,微服务虽然有非常多吸引人的优点,但是也因为服务的拆分引发了诸多原本在单体应用中没有的问题。
• 运维的新挑战:在微服务架构中,运维人员需要维护的进程数量会大大增加。有条不紊地将这些进程编排和组织起来不是一件容易的事,传统的运维人员往往很难适应这样的改变。我们需要运维人员有更多的技能来应对这样的挑战,运维过程需要更多的自动化,这就要求运维人员具备一定的开发能力来编排运维过程并让它们能自动运行起来。
• 接口的一致性:虽然我们拆分了服务,但是业务逻辑上的依赖并不会消除,只是从单体应用中的代码依赖变为了服务间的通信依赖。而当我们对原有接口进行了一些修改,那么交互方也需要协调这样的改变来进行发布,以保证接口的正确调用。我们需要更完善的接口和版本管理,或是严格地遵循开闭原则。
• 分布式的复杂性:由于拆分后的各个微服务都是独立部署并运行在各自的进程内,它们只能通过通信来进行协作,所以分布式环境的问题都将是微服务架构系统设计时需要考虑的重要因素,比如网络延迟、分布式事务、异步消息等。
尽管微服务架构有很多缺点和问题, 但是其实现的敏捷开发和自动化部署等优点依然被广大优秀架构师和开发者所青眯,所以解决这些问题就是这几年诸多架构大师努力的目标。
在架构师对于一个大型系统架构的设计与实施的过程中,面对环境、资源、团队等各种因素的影响,几乎不会出现完全相同的架构设计。对于微服务架构而言更是如此,由于并没有一个标准或正式的定义,每位架构师都根据自身理解与实际情况来进行设计,并在发展的过程中不断演化与完善。经过多年的发展,Martin Fowler 在 Microservices一文中,提炼出了微服务架构的九大特性,用于指导大家设计架构。
服务组件化
组件,是一个可以独立更换和升级的单元。就像 PC 中的 CPU、 内存、显卡、硬盘一样,独立且可以更换升级而不影响其他单元。
在微服务架构中,需要我们对服务进行组件化分解。服务,是一种进程外的组件,它通过 HTTP 等通信协议进行协作,而不是像传统组件那样以嵌入的方式协同工作。每一个服务都独立开发、部署,可以有效避免一个服务的修改引起整个系统的重新部署。
打一个不恰当的比喻,如果我们的 PC 组件以服务的方式构建,那么只维护主板和一些必要外设之后,计算能力通过一组外部服务实现,我们只需要告诉 PC 从哪个地址来获得计算能力,通过服务定义的计算接口来实现我们使用过程中的计算需求,从而实现 CPU 组件的服务化。这样原本复杂的 PC 服务得到了轻量化的实现,我们甚至只需要更换服务地址就能升级 PC 的计算能力。
按业务组织团队
当决定如何划分微服务时,通常也意味着我们要开始对团队进行重新规划与组织。按以往的方式,我们往往会从技术的层面将团队划分为多个,比如DBA团队、运维团队、后端团队、前端团队、设计师团队等。若我们继续按这种方式组织团队来实施微服务架构开发,当有一个服务出现问题需要更改时,可能是一个非常简单的变动,比如对人物描述增加一个字段, 这需要从数据存储开始考虑一直到设计和前端, 虽然大家的修改都非常小,但这会引起跨团队的时间耗费和预算审批。这就是康威定律在起作用:任何组织在设计一套系统(广义层面的系统)时,其设计成果都会直接体现该组织所使用的沟通结构。其作用示意图如下:
图 康威定律的实际体现
在实施微服务架构时,需要采用不同的团队分割方法。由于每一个微服务都是针对特定业务的宽栈或是全栈实现,既要负责数据的持久化存储,又要负责用户的接口定义等各种跨专业领域的职能。因此, 面对大型项目的时候,对于微服务团队的拆分更加建议按业务线的方式进行拆分,一方面可以有效减少服务内部修改所产生的内耗;另一方面,团队边界可以变得更为清晰。服务边界由团队边界决定,如下图所示。
图 由团队边界决定的服务边界
做“产品 ”的态度
在实施微服务架构的团队中,每个小团队都应该以做产品的方式, 对其产品的整个生命周期负责。而不是以项目的模式,以完成开发与交付并将成果交接给维护者为最终目标。
开发团队通过了解服务在具体生产环境中的情况,以增加他们对具体业务的理解,比如,很多时候,一些业务中发生的特殊或异常情况,很可能产品经理都并不知晓,但细心的开发者很容易通过生产环境发现这些特殊的潜在问题或需求。
所以,我们需要用做“产品”的态度来对待每一个微服务,持续关注服务的运作情况,并不断分析以帮助用户来改善业务功能。
智能端点与哑管道
在单体应用中,组件间直接通过函数调用的方式进行交互协作。 而在微服务架构中,由于服务不在一个进程中,组件间的通信模式发生了改变,若仅仅将原本在进程内的方法调用改成 RPC方式的调用, 会导致微服务之间产生烦琐的通信,使得系统表现更为糟糕,所以,我们需要更粗粒度的通信协议。
在微服务架构中,通常会使用以下两种服务调用方式:
• 第一种,使用HTTP的RESTful API或轻量级的消息发送协议,实现信息传递与服务调用的触发。
• 第二种,通过在轻量级消息总线上传递消息,类似 RabbitMQ 等一些提供可靠异步交换的中间件。
Martin Fowler曾说:在极度强调性能的情况下, 有些团队会使用二进制的消息发送协议,例如protobuf。即使是这样,这些系统仍然会呈现出“智能瑞点和哑管道”的特点,这是为了在易读性与高效性之间取得平衡。当然大多数Web应用或企业系统并不需要在这两者间做出选择,能够荻得易读性已经是一个极大的胜利了。
去中心化治理
当我们采用集中化的架构治理方案时,通常在技术平台上都会制定统一的标准,但是每一种技术平台都有其短板,这会导致在碰到短板时,不得不花费大力气去解决,并且可能因为其底层原因解决得不是很好,最终成为系统的瓶颈。
在实施微服务架构时,通过采用轻量级的契约定义接口,使得我们对于服务本身的具体技术平台不再那么敏感,这样整个微服务架构系统中的各个组件就能针对其不同的业务特点选择不同的技术平台, 终于不会出现杀鸡用牛刀或是杀牛用指甲钳的尴尬处境了。
不是每一个问题都是钉子,不是每一个解决方案都是锤子。
去中心化管理数据
我们在实施微服务架构时,都希望让每一个服务来管理其自有的数据库,这就是数据管理的去中心化。
在去中心化过程中,我们除了将原数据库中的存储内容拆分到新的同平台的其他数据库实例中之外(如把原本存储在MySQL中的表拆分后,存储到多个不同的MySQL实例中), 也可以将一些具有特殊结构或业务特性的数据存储到一些其他技术的数据库实例中(如把
日志信息存储到MongoDB中或把用户登录信息存储到Redis中)。去中心化数据管理如下图所示。
图 去中心化数据管理
虽然数据管理的去中心化可以让数据管理更加细致化,通过采用更合适的技术可让数据存储和性能达到最优。但是,由于数据存储于不同的数据库实例中后,数据一致性也成为微服务架构中亟待解决的问题之一。分布式事务本身的实现难度就非常大,所以在微服务架构中,我们更强调在各服务之间进行“无事务”的调用, 而对于数据一致性,只要求数据在最后的处理状态是一致的即可;若在过程中发现错误,通过补偿机制来进行处理,使得错误数据能够达到最终的一致性。
基础设施自动化
近年来云计算服务与容器化技术的不断成熟,运维基础设施的工作变得越来越容易。但是,当我们实施微服务架构时,数据库、应用程序的个头虽然都变小了,但是因为拆分的原因,数量成倍增长。这使得运维人员需要关注的内容也成倍增长,并且操作性任务也会成倍增长,这些问题若没有得到妥善解决,必将成为运维人员的噩梦。 单体应用和微服务应用部署的不同如下图所示:
图 模块化部署的区别
所以,在微服务架构中,务必从一开始就构建起“持续交付”平台来支撑整个实施过程,该平台需要两大内容缺一不可。
• 自动化测试:每次部署前的强心剂,尽可能地获得对正在运行的软件的信心。
• 自动化部署:解放烦琐枯燥的重复操作以及对多环境的配置管理。
“持续交付”流程如下图所示:
图 持续交付流程
容错设计
在单体应用中,一般不存在单个组件故障而其他部件还在运行的情况,通常是一挂全挂。而在微服务架构中,由于服务都运行在独立的进程中,所以存在部分服务出现故障,而其他服务正常运行的情况。 比如,当正常运作的服务B调用到故障服务A时,因故障服务A没有返回,线程挂起开始等待,直到超时才能释放,而此时若触发服务B 调用服务A 的请求来自服务C,而服务C频繁调用服 B时,由于其依赖服务A,大量线程被挂起等待,最后导致服务A也不能正常服务, 这时就会出现故障的蔓延。
所以,在微服务架构中,快速检测出故障源并尽可能地自动恢复服务是必须被设计和考虑的。通常,我们都希望在每个服务中实现监控和日志记录的组件,比如服务状态、断路器状态、吞吐量、网络延迟等关键数据的仪表盘等。
演进式设计
通过上面的几点特征,我们已经能够体会到,要实施一个完美的微服务架构,需要考虑的设计与成本并不小,对于没有足够经验的团队来说,甚至要比单体应用付出更多的代价。
所以,在很多情况下,架构师都会以演进的方式进行系统的构建。在初期,以单体系统的方式来设计和实施,一方面系统体量初期并不会很大,构建和维护成本都不高。另一方面,初期的核心业务在后期通常也不会发生巨大的改变。随着系统的发展或者业务的需要,架构师会将一些经常变动或是有一定时间效应的内容进行微服务处理, 并逐渐将原来在单体系统中多变的模块逐步拆分出来,而稳定不太变化的模块就形成一个核心微服务存在于整个架构之中。
系统微服务拆分以稳定为中心、主要遵循以下原则:
1.AKF拆分原则
业界对于可扩展系统架构设计有一个朴素的理念:通过加机器就可以解决容量和可用性问题。这一理念在云计算概念疯狂流行的今天,得到了广泛的认可,对于一个规模迅速增长的系统而言,容量和性能问题当然是首当其冲的。但随着时间的向前,系统规模的增长,除了面对性能与容量的问题外,还要面对功能与模块数量上的增长带来的系统复杂性问题以及业务的变化带来的提供差异化服务的问题。
然而许多系统在架构设计时为充分考虑这些问题,导致系统重构成为常态,而影响业务交付能力,还浪费人力财力。对此《可扩展艺术》一书提出了一个系统可扩展模型--AKF可扩展立方体(Scalability Cube)。
图 AFK可扩展立方体
Y轴(功能)关注应用中功能划分,基于不同的业务拆分,Y轴扩展会将庞大的整体应用拆分为多个服务,每个服务实现一组相关的功能,如订单管理、客户管理等;X轴(水平扩展)关注水平扩展,也就是“加机器解决问题”,X轴扩展与我们前面朴素理念是一致的,通过绝对平等的复制服务与数据,以解决容量与可用性的问题,其实就是将微服务运行多个实例,做集群加负载均衡的模式;Z轴(数据分区)关注服务与数据的优先级划分,如按地域划分,Z轴(数据分区)关注服务与数据的优先级划分,如按地域划分,为了性能数据安全上的考虑,我们将一个完整的数据集按一定维度划分出不同的子集。一个分区(Shard),就是整体数据集的一个子集。比如用尾号来划分用户,那同样尾号的那部分用户就可以认为是同一个分区,数据分区一般包括以下几种数据划分形式:
数据类型:如业务类型
数据范围:如时间段、用户ID
数据热度:如用户活跃度、商品热度
按读写分:如商品描述、商品库存
2.前后端分离原则
何为前后端分离?前后端本来不就是分离的吗?这要从jsp开始讲起。分工精细化从来都是蛋糕做大的原则,多个领域工程师最好在不需要接触其他领域知识的情况下合作,才能使效率越来越高,维护也会变得简单。jsp的模板技术融合了html和java代码,使得传统MVC开发中的前后端如胶似漆,前端做好页面,后端转成模板,发现问题再找前端,前端又看不懂java代码,前后端分离的目的就是打破这尴尬的局面,我们需要的是一个全能的团队,而不是一个个全能的人。
前后端分离原则,简单的讲就是前端和后端的代码分离,我们推荐的模式是最好采用物理分离的方式部署,进一步促使更彻底的分离。如果继续使用服务端模板技术,如jsp,把java、js、css、html都堆到一个页面里,稍微复杂一点的页面就无法维护了。
这种前后端分离有几个好处:
图 前后端分离
3.无状态服务
对于无状态服务,首先说一下什么是状态:如果一个数据需要被多个服务共享,才能完成一笔交易,那么这个数据被称为状态。进而依赖这个状态的服务被称为有状态的服务,反之成为无状态服务。
这个无状态服务原则并不是说在微服务架构里不允许存在状态,表达的真实意思就是要把有状态的业务服务改变为无状态的计算类服务,那么状态数据也就相应的迁移到对应的“有状态数据服务”中。
场景说明:例如我们从前在本地内存中建立的数据缓存、Session缓存,到现在微服务架构中就应该把数据迁移到分布式缓存中存储,让业务服务变成一个无状态的计算节点。迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。
图 无状态服务
4.Restful的通信风格
图 Restful通信风格
微服务拆分不是一蹴而就的,是不断演进且必须要根据业务实际需要,根据研发资源情况进行拆分。勿要为了拆而拆,导致系统复杂,难以维护、难以支撑业务。