架构设计思维-集成
《架构设计思维-分解》我们讲解了分解,按照不同纬度将系统分解,接下来我们讲如何将分解完成的各个组件或子系统,通过合适的方式,最终还能够集成为一个完整的整体,分解仅仅是加速开发和降低问题复杂度,如果分解后的内容无法集成在一起,那么分解就没有任何意义。分解+集成可以理解为架构最核心的思考方式和方法。
架构思维中的分解和集成是随着系统的演化进行演化,从单体架构到ESB为代表的SOA架构再到现在流行的微服务,集成方式也从直接依赖到ESB为枢纽再到多种形式存在的微服务集成,接下来我们就来解决微服务中集成的方式有哪些。
1
应用架构的演变
大家认真想一下,架构为什么进行演化呢?这些年,微服务非常火,那你有没想过微服务的动机是什么?其实,最重要的动机就是业务变化太快了。特别是移动互联网出现以后,各种各样的业务:共享单车、支付宝、微信支付等等,业务经历着飞速的变革与创新,所以就要求底层的应用技术能够支撑得上业务的快速变化。那我们看一下应用架构的变迁,其实也是从另一个角度来印证上面说的“快”。
1-1
单体架构
Web应用程序发展的早期,在开发服务端企业应用时,应用需要支持各种不同类型的客户端,比如桌面浏览器、移动浏览器以及原生移动应用。应用还需要向第三方提供可访问的API,并通过Web Service或者消息代理与其它应用实现集成。大部分web工程是将所有的功能模块(service side)打包到一起并放在一个web容器中运行,很多企业的Java应用程序打包为war包。应用通过执行业务逻辑、访问数据库、与其它系统交换信息、并返回一条HTML/JSON/XML响应,来处理请求(HTTP请求与消息)。
应用采用多层架构或者六角架构,主要由以下几类不同组件构成:
展现组件——负责处理HTTP请求并响应HTML或者JSON/XML(对于web Services APIs)
业务逻辑——应用的业务逻辑
数据库访问逻辑——用于访问数据库的数据访问对象
不同逻辑组件分别响应应用中的不同功能模块。
单体架构的缺点
单体应用巨大的代码库可能会让人望而生畏,特别是对那些团队新成员来说。应用难以被理解和进行修改,进而导致开发速度减慢。由于没有清晰的模块边界,模块化会逐渐消失。另外,由于难以正确把握代码变化,导致代码质量逐步下滑,陷入恶性循环。
过载的IDE——代码库越大,IDE速度越慢,开发者的生产效率越低。
过载的Web容器——应用越大,Web容器启动时间越长。容器启动耗费时间,极大影响到开发者的生产效率。对部署工作也有负面影响。
持续部署困难——巨大的单体应用本身就是频繁部署的一大障碍。为了更新一个组件,你必须重新部署整个应用。这会中断那些可能与更改无关的后台任务(例如Java应用中的Quartz任务),同时可能引发问题。另外,未被更新的组件有可能无法正常启动。重新部署会增加风险,进而阻碍频繁更新。因为用户界面开发者经常需要进行快速迭代与频繁重新部署,所以这对用户界面开发者而言更加是个难题。
应用扩展困难——单体架构只能进行一维伸缩。一方面,它可以通过运行多个应用副本来增加业务容量,实现扩展。一些云服务甚至可以根据负载量动态调整实例数量。但在另一方面,数据量增大会使得该架构无法伸缩。每个应用实例需要访问所有数据,导致缓存低效,加大内存占用和I/O流量。另外,不同的应用组件有不同的资源需求——有的是CPU密集型的,另外一些是内存密集型的。单体架构无法单独伸缩每个组件。
难于进行规模化开发——单体应用是规模化开发的障碍。应用一旦达到特定规模,需要将现有组织拆分成多个团队,每个团队负责不同的功能模块。举例来说,我们可能需要设立UI团队、会计团队、库存团队等等。单体应用的问题在于它使团队无法独立展开工作。团队需要在工作进度和重新部署上进行协调。对于各团队而言,这使得变更和更新产品变得异常困难。
1-2
SOA架构
SOA架构,是一种粗粒度、开放式、松耦合的服务结构,要求软件产品在开发过程中,按照相关的标准或协议,进行分层开发。通过这种分层设计或架构体系可以使软件产品变得更加弹性和灵活,且尽可能的与第三方软件产品互补兼容,以达到快速扩展,满足或响应市场或客户需求的多样化、多变性。
SOA架构的缺点
可靠性(Reliability):SOA还没有完全为事务的最高可靠性——不可否认性(nonrepudiation)、消息一定会被传送且仅传送一次(once-and-only-once delivery)以及事务撤回(rollback)——做好准备,不过等标准和实施技术成熟到可以满足这一需求的程度并不遥远。
安全性(Security):在过去,访问控制只需要登录和验证;而在SOA环境中,由于一个应用软件的组件很容易去与属于不同域的其他组件进行对话,所以确保迥然不同又相互连接的系统之间的安全性就复杂得多了。
编排 (Orchestration):统一协调分布式软件组件以便构建有意义的业务流程是最复杂的,但它同时也最适合面向服务类型的集成,原因很显然,建立在SOA上面的应用软件被设计成可以按需要拆散、重新组装的服务。作为目前业务流程管理(BPM)解决方案的核心,编排功能使IT管理人员能够通过已经部署的套装或自己开发的应用软件的功能,把新的元应用软件 (meta-application)连接起来。 事实上,最大的难题不是建立模块化的应用软件,而是改变这些系统表示所处理数据的方法。
遗留系统处理(Legacy support):SOA中提供集成遗留系统的适配器, 遗留应用适配器屏蔽了许多专用性API的复杂性和晦涩性。一个设计良好的适配器的作用好比是一个设计良好的SOA服务:它提供了一个抽象层,把应用基础设施的其余部分与各种棘手问题隔离开来。一些厂商就专门把遗留应用软件“语义集成”到基于XML的集成构架中。 但是集成遗留系统的工作始终是一种挑战。
语义 Semantics:定义事务和数据的业务含义,一直是开发面临的最棘手的问题。语义关系是设计良好SOA架构的核心要素。 就目前而言,没有哪一项技术或软件产品能够真正解决语义问题。为针对特定行业和功能的流程定义并实施功能和数据模型是一项繁重的任务,它最终必须由业务和开发者共同承担。不过,预制组件和经过实践证明的咨询技能可以简化许多难题。
1-3
微服务架构
微服务是一种用于构建应用的架构方案。微服务架构有别于更为传统的单体式方案,可将应用拆分成多个核心功能。每个功能都被称为一项服务,可以单独构建和部署,这意味着各项服务在工作(和出现故障)时不会相互影响。是应用的各项核心功能,而且这些服务均可独立运行。但是,微服务架构不只是应用核心功能间的这种松散耦合,它还涉及重组开发团队、涉及如何进行服务间通信以应对不可避免的故障、满足未来的可扩展性并实现新的功能集成。
微服务的挑战
构建:您必须花时间明确各个服务间的依赖关系。要知道,由于存在这些依赖关系,当您完成一个构建时,可能会触发多个其他构建。您还需要考虑微服务对于数据的影响。
测试:集成测试和端到端测试可能会前所未有的难以实施,但却更加重要。根据您在架构相互支撑的服务时所采用的不同方式,架构中的一个部分出现故障,很可能会导致其他部分也随之出现故障。
版本管理:在更新到新版本时,请记住:向后兼容性可能会因更新操作而失效。要解决这一问题,您可以利用条件逻辑来进行构建,但是构建会变得繁复、难以控制且快速。或者,您也可以为不同的客户端维护多个活跃版本,但是相关的维护和管理工作会变得更加庞杂。
部署:没错,这也是一大挑战,至少是首次设置时所要面临的一大挑战。为了简化部署,您必须先大量投资自动化,因为人工部署无法应对微服务的复杂性。请好好思考您要以何种方式以及怎样的顺序来部署各项服务。
日志记录:使用分布式系统时,您需要利用集中式日志将所有相关信息集中到一处。否则,积累的日志数量将让您难以招架。
监控:您必须通过一个集中式视图来了解整个系统的情况,以便找出问题的根源。
调试:无法进行远程调试,因为这种方式无法涵盖数十个或数百个服务。不幸的是,关于应该如何进行调试,目前还没有标准答案。
连接:请考虑使用服务探索功能,无论是集中式的还是集成式。
2
集成方式
SOA体系下,服务之间通过企业服务总线(Enterprise Service Bus)通信,许多业务逻辑在中间层(消息的路由、转换和组织)。
微服务架构倾向于降低中心消息总线(类似于ESB)的依赖,将业务逻辑分布在每个具体的服务终端。大部分微服务基于HTTP、JSON这样的标准协议,集成不同标准和格式变的不再重要。另外一个选择是采用轻量级的消息总线或者网关,有路由功能,没有复杂的业务逻辑。下面就介绍几种常见的架构方式。
点对点方式
点对点方式中,服务之间直接用。每个微服务都开放REST API,并且调用其它微服务的接口。
明显,在比较简单的微服务应用场景下,这种方式还可行,随着应用复杂度的提升,会变得越来越不可维护。这点有些类似SOA的ESB,尽量不采用点对点的集成方式。
网关方式
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能个。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。
用曾经参与项目的例子,所有的业务接口通过API网关暴露,是所有客户端接口的唯一入口。微服务之间的通信也通过API网关。
采用网关方式有如下优势:
有能力为微服务接口提供网关层次的抽象。比如:微服务的接口可以各种各样,在网关层,可以对外暴露统一的规范接口。
轻量的消息路由、格式转换。
统一控制安全、监控、限流等非业务功能。
每个微服务会变得更加轻量,非业务功能个都在网关层统一处理,微服务只需要关注业务逻辑
目前,API网关方式应该是微服务架构中应用最广泛的设计模式。
消息代理方式
微服务也可以集成在异步的场景下,通过队列和订阅主题,实现消息的发布和订阅。一个微服务可以是消息的发布者,把消息通过异步的方式发送到队列或者订阅主题下。作为消费者的微服务可以从队列或者主题共获取消息。通过消息中间件把服务之间的直接调用解耦。
通常异步的生产者/消费者模式,通过AMQP、MQTT等异步消息规范。
03
集成难点
微服务系统架构变得越来越流行了,是模块化的一种方法,把一整块应用拆分成一个个服务,让团队在开发大型复杂的应用时更快地交付出高质量的系统,团队成员们可以轻松地接受到新技术,因为他们可以使用最新且推荐的技术栈来实现各自的服务,微服务架构也通过让每个服务都被部署在最佳状态的硬件上而改善了应用的扩展性。但是在微服务集成过程中,数据服务、事务、查询这些我们需要特别的处理,不像过去的单体架构、基于ESB的SOA系统架构一样,微服务遇到了之前不存在的问题,现在许多应用大到一个人根本无法完成,而且复杂到光靠一个人去理解是不可能的。这种情况下,应用就必须被拆分成一个个模块。在单体应用中,模块被定义为比方一个java package。然而,这种做法在实践中并不是很理想,时间长了,单体应用就变得越来越庞大,微服务架构把服务作为一个模块单元,每个服务都有一个不可渗透且很难违反的边界。也就是每个微服务要提供一种单独而独立的能力。这样的话,应用程序的模块化就更容易随时间保存。接下来我们就看看怎么解决这个三个问题。
3-1
数据服务设计
从来没有一个 one-size-fits-all 的架构,所以在微服务架构下面,我们需要了解的,一样是几个关键的架构考量点。然后针对自己的实际应用,选择哪些考量点是更加重要。现在我主要就是跟大家来讨论从哪几个角度着手来设计一个符合微服务架构原则的数据架构。比如说,我们可以从一系列的问题来开始这个讨论:
这么多微服务之间,是否可以用一个数据库,还是多个数据库来支持多个微服务?
如果是多个数据库,是否为每一个微服务挑选一个最合适的数据库,还是选择同一种类型的数据库?
如何在微服务架构下扩展我的数据库?
当一个依赖的服务需要修改数据库 Schema 的时候,是否会影响到现有系统?
当微服务应用不断衍变的时候,数据库是否可以快速的响应应用需求变化?
这些就是我们在微服务数据架构时候要关注的地方。
一库一服or一库多服
无论是单体应用,还是微服务应用,有一点是肯定的:应用的各个模块之间都需要进行较为频繁的通信,通过一起协同合作,来实现应用的整体价值。在单体应用中,这种通信是通过方法调用来完成的。在微服务中,则通过 API 调用来完成。这些模块或者服务间调用,大部分时候是为了共享数据。 共享数据最贱的方式当然就是采用一种共享数据库的模式,也就是单体应用常用的方式 - 应用可以有多个系统模块,但一般都是只有一个数据库。
这种架构模式通常会被认为是微服务架构下的反范式,它的问题在于:
单点故障一个数据库倒下,整批服务全部停止。
数据在同一个地方,会给贪图方便的开发或者 DBA 工程师编写很多数据间高度依赖的程序或者工具;
无法针对某一个服务进行精准优化或扩展
所以一般推荐的做法,是为每一个微服务准备一个单独的数据库,也即一库一服 (database per service) 模式。这种模式更加适合微服务架构 - 它满足每一个服务是独立开发、独立部署、独立扩展的特性。当需要对一个服务进行升级或者数据架构改动的时候,无须影响到其他的服务。需要对某个服务进行扩展的时候,也可以手术式的对某一个服务进行局部扩容。另外,如果某些服务对数据库有特殊的需求,这种模式也为下文所讲的混合持久化 (Polyglot Persistence) 提供了可能性。
混合持久化
混合持久化在大型互联网公司是一个比较风行的模式,它秉承的原则就是为特别的任务提供最好的工具。比如说某个系统提供一个高并发低延迟的共享用户会话方案 (shared session storage), Redis 可能是一个非常理想的选择。如果是实现一个产品目录,涉及到大量不定结构的商品数据及属性的建模管理,系统可能会采用模式灵活,动态 schema 的 MongoDB 来作为我的数据库解决方案。如果系统希望支持非常强大的全文搜索,ElasticSearch 则是行业中的佼佼者。
混合持久化的优势很明显,可以让每个单独的服务使用到最佳的工具和技术。但是它的弊端也是不容忽视。部署、监控、备份、升级等数据库管理工作从来都是一件困难但是重要的任务。引入多个不同的数据库,也意味着对系统管理维护的复杂度和成本提高了很多。这种情况下可能需要比较有资源的公司或者团队才可以使用。这也解释了这个模式在大型互联网公司得到较多的采用与推广。针对于其他小型规模的用户,或者是缺乏足够掌握各种新型技术人才的公司来说,另一种更为可行的模式可能是多模数据库 (Multi-model)。
多模数据库的特征是:
依然是一库一服务(为一个服务部署一个单独的数据库);
但是使用的是同一种类型,支持多种场景的数据库;
虽然是多实例,但是只需维护一种类型的数据库,管理上和人员配备上都较为简单。
如果你在开发的应用是一款企业级产品,会交付到客户环境部署安装,则运维管理的简单性将在技术选型中占据非常重要的一个比重,无疑这种情况下多模数据库更加适用。
3-2
数据一致性
一个传统的单体应用可以通过ACID事务来强制业务规则从而实现一致性。想象一下,比如,金融系统的用户都有信用额度,就是在创建订单之前必须先看信用如何。应用程序必须确保潜在的多个并发尝试去创建订单不超过客户的信用限额。如果Orders和Customers都在同一个库中,那么就可以使用ACID事务来搞定:不幸的是,在微服务架构中我们无法通过这种方式管理数据的一致性。Orders和Customers表被不同的服务所拥有,只能通过各自的服务API访问。他们甚至可能在不同的数据库。
一种比较常见的做法就是使用分布式事务来搞定,比如2PC等。但是这种做法对于现代应用来说也许不是一种可行的方案。CAP定理要求你必须在可用性和一致性之间选择,可用性通常是较好的选择。而且,许多现代技术,例如大多数NoSQL数据库,甚至不支持ACID事务,更不用说2PC。所以管理数据的一致性需要使用其他的方式。我们使用事件驱动架构中的一种技术叫事件源(event sourcing)来解决分布式事务。
Event sourcing (事件源)通过使用根本不同的事件中心方式来获得不需2PC的原子性,保证业务实体的一致性。 这种应用保存业务实体一系列状态改变事件,而不是存储实体现在的状态。应用可以通过重放事件来重建实体现在状态。只要业务实体发生变化,新事件就会添加到时间表中。因为保存事件是单一操作,因此肯定是原子性的。
为了理解事件源工作方式,考虑事件实体作为一个例子。传统方式中,每个订单映射为ORDER表中一行,例如在ORDER_LINE_ITEM表中。但是对于事件源方式,订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够数据来重建订单状态。
事件是长期保存在事件数据库中,提供API添加和获取实体事件。事件存储跟之前描述的消息代理类似,提供API来订阅事件。事件存储将事件递送到所有感兴趣的订阅者,事件存储是事件驱动微服务架构的基干。
事件源方法有很多优点:解决了事件驱动架构关键问题,使得只要有状态变化就可以可靠地发布事件,也就解决了微服务架构中数据一致性问题。另外,因为是持久化事件而不是对象,也就避免了object relational impedance mismatch problem。
数据源方法提供了100%可靠的业务实体变化监控日志,使得获取任何时点实体状态成为可能。另外,事件源方法可以使得业务逻辑可以由事件交换的松耦合业务实体构成。这些优势使得单体应用移植到微服务架构变的相对容易。
事件源方法也有不少缺点,因为采用不同或者不太熟悉的变成模式,使得重新学习不太容易;事件存储只支持主键查询业务实体,必须使用 Command Query Responsibility Segregation (CQRS) 来完成查询业务,因此,应用必须处理最终一致数据。
3-3
分布式查询
在传统的单体应用中,我们通常使用join来实现跨表查询。比如,我们可以通过sql轻松的查询出最近客户所订的大额订单:但我们无法在微服务架构中实现这样的查询。就像前面提到的那样,Orders和Customers表分属不同的服务,只能通过服务API来访问。而且他们可能使用了不同的数据库。而且,即使你使用事件源(Event Sourcing )处理查询问题可能更麻烦。有一种解决方案就是通过一种叫CQRS(Command Query Responsibility Segregation)做法来解决分布式查询问题。
实现查询的好方法是使用称为命令查询责任分离(CQRS)的体系结构模式: Command Query Responsibility Segregation。如名称所示,CQRS将应用程序分为两部分。第一部分是命令侧(command-side),其处理命令(例如,HTTP POST,PUT和DELETE)以创建,更新和删除聚合。前提是这些聚合是使用事件源实现的。应用程序的第二部分是查询侧(query-side),其通过查询聚合的一个或多个物化视图(materialized views)来处理查询(例如HTTP GET)。查询侧通过订阅由命令侧发布的事件来保持视图(view)与聚合(aggregate)同步。查询侧(query-side)视图可以使用任何类型的能满足需求的数据库来实现。根据需求,应用程序的查询端可能使用一个或多个以下数据库;在很多场合,CQRS是一个以事件为基础(event-based)的综合体,比如使用RDBMS作为记录系统再使用比如Elasticsearch来处理文本查询。CQRS的查询侧可以使用其它类型的数据库,支持多种类型的数据库,不仅仅是文本搜索引擎。而且,它通过订阅事件准实时地去更新查询侧的视图。
CQRS的优点
优点一:在微服务架构中实现查询,特别是使用事件源的架构。它使应用程序有效地支持一组不同的查询。
优点二:是把命令侧和查询侧分离,达到了解耦的作用。
CQRS的缺点。
缺点一:就是需要额外的工作来开发和维护这套系统。你需要开发和部署更新和查询视图的查询端服务。还有就是你需要部署视图数据库(view store)。
缺点二:是处理命令侧和查询侧视图之间的“滞后”。查询层相比命令侧存在一定的时延。更新聚合,然后立即查询视图的客户端应用程序可能会看到聚合的以前版本。所以必须通过一些手法来避免暴露这些潜在的不一致性给用户。
04
总结
有句话说的是架构师的工作就是每天做不断的取舍 (trade off),因为选择往往是让人很纠结。微服务越来越流行,但是微服务系统架构种,集成不是架构师关注的唯一的技术点还有:服务发现、服务配置、服务框架和治理、监控体系、微服务的限流熔断、容器部署技术&持续交付流水线,微服务能更快速的配合业务的快速发展,但是同时也出现了以前从未遇到的问题,希望后续能为大家提供更多的设计思路。
技术方舟带你远航,带领你略不一样的风景