SOA和微服务架构

时间:2022-04-22 10:03:46

微服务架构强调的第一个重点就是业务系统需要彻底的组件化和服务化,原有的单个业务系统会拆分为多个可以独立开发,设计,运行和运维的小应用。这些小应用之间通过服务完成交互和集成。每个小应用从前端web ui,到控制层,逻辑层,数据库访问,数据库都完全是独立的一套。在这里我们不用组件而用小应用这个词更加合适,每个小应用除了完成自身本身的业务功能外,重点就是还需要消费外部其它应用暴露的服务,同时自身也将自身的能力朝外部发布为服务。

如果一句话来谈SOA和微服务的区别,即微服务不再强调传统SOA架构里面比较重的ESB企业服务总线,同时SOA的思想进入到单个业务系统内部实现真正的组件化。

把这个核心搞清楚后,再来看下网上找到的对微服务架构的一些定义和阐述:

微服务可以在“自己的程序”中运行,并通过“轻量级设备与HTTP型API进行沟通”。关键在于该服务可以在自己的程序中运行。通过这一点我们就可以将服务公开与微服务架构(在现有系统中分布一个API)区分开来。在服务公开中,许多服务都可以被内部独立进程所限制。如果其中任何一个服务需要增加某种功能,那么就必须缩小进程范围。在微服务架构中,只需要在特定的某种服务中增加所需功能,而不影响整体进程。

微服务不需要像普通服务那样成为一种独立的功能或者独立的资源。定义中称,微服务是需要与业务能力相匹配,这种说法完全正确。不幸的是,仍然意味着,如果能力模型粒度的设计是错误的,那么,我们就必须付出很多代价。如果你阅读了Fowler的整篇文章,你会发现,其中的指导建议是非常实用的。在决定将所有组件组合到一起时,开发人员需要非常确信这些组件都会有所改变,并且规模也会发生变化。服务粒度越粗,就越难以符合规定原则。服务粒度越细,就越能够灵活地降低变化和负载所带来的影响。然而,利弊之间的权衡过程是非常复杂的,我们要在配置和资金模型的基础上考虑到基础设施的成本问题。

再强调下即:

首先对于应用本身暴露出来的服务,是和应用一起部署的,即服务本身并不单独部署,服务本身就是业务组件已有的接口能力发布和暴露出来的。了解到这点我们就看到一个关键,即我们在进行单个应用组件设计的时候,本身在组件内部就会有很大接口的设计和定义,那么这些接口我们可以根据和外部其它组件协同的需要将其发布为微服务,而如果不需要对外协同我们完全可以走内部API接口访问模式提高效率。

其次,微服务架构本身来源于互联网的思路,因此组件对外发布的服务强调了采用HTTP Rest API的方式来进行。这个也可以看到在互联网开放能力服务平台基本都采用了Http API的方式进行服务的发布和管理。从这个角度来说,组件超外部暴露的能力才需要发布为微服务,其本身也是一种封装后的粗粒度服务。而不是将组件内部的所有业务规则和逻辑,组件本身的底层数据库CRUD操作全部朝外部发布。否则将极大的增加服务的梳理而难以进行整体服务管控和治理。

微服务的基本思想在于考虑围绕着业务领域组件来创建应用,这些就应用可独立地进行开发、管理和加速。在分散的组件中使用微服务云架构和平台使部署、管理和服务功能交付变得更加简单。

对于互联网谈到微服务架构一定会谈到Devops即开发测试和部署运维的一体化。当我们的单体应用以及拆分为多个小应用后,虽然整体架构可以松耦合和可扩展,但是如果拆分的组件越多,这些组件之间本身的集成和部署运维就越复杂。即任何一个组件,当他依赖的外部其它应用组件越多的时候,整个集成,部署和联调测试的过程就越复杂。这些如果完全靠我们手工去完成一是增加工作量,一是增加出错概率。

原来谈组件化开发谈的最多的是单个组件的持续集成,包括配置环境集成,自动打包部署,自动化的冒烟测试等。对于微服务架构下首先仍然是要做好单个组件本身的持续集成,其次在这个基础上增加了多个组件的打包部署和组件间的集成。里面的核心思想就是Devops的思路,希望能够实现开发设计到部署运维的一体化。

由于微服务架构里面强调了单个组件本身是可以在独立的进程里面运行,各个组件之间在部署的时候就能够做到进程级别的隔离。那么一台服务器我们可能需要初始化几十个甚至更多的进程来进行应用组件的部署。为了保持进程的隔离性,我们可以用虚拟机,但是当几十个进程都完全用独立的虚拟机就不现实的,而这个问题的解决刚好就是利用PaaS平台里面的轻量Docker容器来做这个事情,每个Docker是独立的容器刚好又完全做到进程级别的隔离,资源占用率又最小,这些特点刚好满足微服务架构的开发测试和自动化部署。

前面这些问题思考清楚后就是考虑所有暴露的微服务是否需要一个统一的服务管控和治理平台,按照当前微服务架构的整体思路,虽然单个服务的实现和发布仍然是在组件内部完成的,但是这些组件暴露的服务本身的调用情况,服务本身的安全,日志和流量控制等仍然需要一个统一的SOA服务管理平台来完成。

由于微服务尽量都是通过HTTP API的方式暴露出去的,因此这种服务管理平台不需要像传统企业内部的ESB服务总线这么重。但是最基本的服务注册,服务代理,服务发布,服务简单的路由,安全访问和授权,服务调用消息和日志记录这些功能还是需要具备。类似淘宝的Dubbo架构,即可以做为微服务架构下的服务管控平台。

对于这种服务管控平台,核心需要讨论的就是服务每次调用本身的消息传递,输入和输出日志是否需要记录,当前就有两种做法,一种是不记录,管理平台只负责服务注册和目录发布,安全授权,实际的服务访问仍然是两个组件之间的点对点连接完成,这种方式下整个架构下获取更高的性能,同时服务管理平台也不容易成为大并发服务访问下的单点瓶颈;另外一种方式就是完全记录,在这种方式下就需要考虑服务管理平台本身的集群化不是,高并发下的性能问题。而个人建议最好的方式还是SOA服务管理平台应该提供两种管理能力,同时仅仅对核心的需要Log日志的服务进行日志记录,而其它服务只提供服务目录和访问控制即可。

===========2016.6.8日更新,增加Chris Richardson微服务系列读书笔记

本文为阅读《Chris Richardson 微服务系列》的阅读笔记,具体原文参考:「Chris Richardson 微服务系列」服务发现的可行方案以及实践案例
, 里面有另外四篇的链接,当前daocloud已经更新到第5篇事件驱动架构。

第一篇 微服务架构的优势和不足

文中强调的单体应用的场景,我在前面很多谈组件化和服务化的文章里面已经都谈到过了,即一个应用系统里面的模块没有办法做到彻底解耦,如果要实现按组件单独部署是不可能的,相互之间仍然有大量内部不可见依赖而导致了模块间无法拆分。

那么单体应用本身带来的问题主要有哪些?

1.系统复杂:内部多个模块紧耦合,关联依赖复杂,牵一发而动全身。
2.运维困难:变更或升级的影响分析困难,任何一个小修改都可能导致单体应用整体运行出现故障。
3.无法扩展:无法拆分部署,出现性能瓶颈后往往只能够增加服务器或增加集群节点,但是DB问题难解决

正是由于这些原因需要考虑引入微服务架构(实质仍然是单个应用本身的组件化和服务化),对于微服务文章里面有一个详细说明如下:一个微服务一般完成某个特定的功能,比如订单管理、客户管理等。每个微服务都是一个微型应用,有着自己六边形架构,包括商业逻辑和各种接口。有的微服务通过暴露
API 被别的微服务或者应用客户端所用;有的微服务则通过网页 UI 实现。在运行时,每个实例通常是一个云虚拟机或者 Docker
容器。


从这个定义和说明仍然需要做一些关键理解,即在我前面谈微服务的文章里面谈到过的,即核心的几点包括了,其一足够构成一个独立小应用(从DB到UI),其二微服务应用之间只能通过Service
API进行交互,其三一般运行在云虚拟机或更轻的Docker容器上。


API
Gateway,这实际上微服务架构里面的很重要的内容,其作用类似于传统企业内部的ESB服务总线,只是更加轻量和高性能来解决微服务的管控和治理问题。而对于负载均衡,缓存,路由,访问控制,服务代理,监控,日志等都属于基本的服务管控内容,也是API
Gateway需要考虑的核心能力。

Scale Cube的3D模型,描述的相当好,即通过微服务架构实施后扩展性的变化。

1. Y轴:本质是应用的分解,即将传统的单体应用分解为多个微服务应用。
2. X轴:水平弹性扩展能力,即通过负载均衡来实现水平弹性扩展,但是DB问题无法解决,引入3
3. Z轴:当单个微服务应用引入了DB弹性扩展能力要解决的时候,我们引入了对数据库进行拆分和DaaS

对于微服务架构的好处前面在讲单体应用的问题的时候已经谈到了,微服务架构正好是解决这些问题。而对于微服务架构的不足,简单总结如下:

1. CAP原则:由于服务无状态和引入了分布式,较难解决事务一致性问题。
2. 集成复杂:任何彻底的分解都将带来集成的复杂度,即模块在集成时候需要外部微服务模块更多的配合。
3. 部署问题:稍大项目都涉及到上100个服务节点部署,还涉及到部署后的配置,扩展和监控问题。

第二篇 使用API网关构建微服务

首先说下这篇文章的引入场景,以一个亚马逊购物网站的手机APP订单查看界面来举例,如果是一个单体应用,那么所有的界面需要获取信息都通过单体应用统一一个地址提供的多个Service
API获取。但是转变为微服务架构后可以看到对于会员管理,商品管理,订单管理,财务结算管理等都已经拆分为了不同的微服务模块,需要从不同的服务提供地址调用不同的微服务模块提供的Service
API来返回数据。

在原文里面我们看到对于客户端和微服务模块间点对点直接通讯提了三个问题,如下:

1. 问题一:客户端需求和每个微服务暴露的细粒度 API 不匹配
2. 问题二:部分服务使用的协议对 web 并不友好,如二进制RPC或AMQP消息等。
3. 问题三:会使得微服务难以重构,如服务拆分或服务组合的场景。

那么我们从传统的ESB能力来对上面三个问题进行一个说明,第一个问题即可能涉及到细粒度的API组合,类似组合服务无法做;其二是可能存在协议转换的问
题要解决;其三即服务透明的问题,即需要对客户端提供一个统一的服务目录以使底层服务透明。由于以上问题,引入了API服务网关的概念,再次强调,对于API服务网关即使微服务架构里面的轻量服务总线,解决服务管控和治理相关问题。文中对API
Gateway给出如下说明:

API 网关是一个服务器,也可以说是进入系统的唯一节点。这与面向对象设计模式中的 Facade 模式很像。API
网关封装内部系统的架构,并且提供 API
给各个客户端。它还可能还具备授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等功能。

API 网关负责服务请求路由、组合及协议转换。客户端的所有请求都首先经过 API 网关,然后由它将请求路由到合适的微服务。API
网关经常会通过调用多个微服务并合并结果来处理一个请求。它可以在 web 协议(如 HTTP 与 WebSocket)与内部使用的非
web 友好协议之间转换。

API 网关还能为每个客户端提供一个定制的 API。通常,它会向移动客户端暴露一个粗粒度的 API。以产品详情的场景为例,API
网关可以提供一个端点(/productdetails?productid=xxx),使移动客户端可以通过一个请求获取所有的产品详情。API
网关通过调用各个服务(产品信息、推荐、评论等等)并合并结果来处理请求。

API网关的优点和缺点

对于API网关的优点,其实是类似传统ESB企业服务总线的优点,即实现服务透明,同时对于服务运行过程中的日志,安全,路由,缓存等问题进行统一配置和处理,而不需要每个微服务API实现时都去考虑。如开源的Dubbo服务总线即可以看作是一个API网关的实现。

API网关和ESB的一些重要区别点在于API网关更加轻量和高性能,它不需要去考虑太多遗留系统和诸多协议的适配,其次也不需要考虑服务集成过程中的大
量数据转换和映射。同时为了提升服务网关的性能,一般API网关在实现过程中不会去记录详细的数据传输日志,或者类似Dubbo架构数据传输根本就不会通
过API网关。


使用 API 网关的最大优点是,它封装了应用程序的内部结构
。客户端只需要同网关交互,而不必调用特定的服务。API
网关也有一些不足。它增加了一个我们必须开发、部署和维护的高可用组件。还有一个风险是,API 网关变成了开发瓶颈。

简单来说,在我们期望的去中心化和全分布式架构中,网关又成了一个中心点或瓶颈点,正是由于这个原因我们在网关设计的时候必须考虑即使API
Gateway宕机也不要影响到服务的调用和运行。


API网关的设计和实现

对于大多数应用程序而言,API 网关的性能和可扩展性都非常重要。因此,将 API 网关构建在一个支持异步、I/O
非阻塞的平台上是合理的。有多种不同的技术可以实现一个可扩展的 API 网关
。在 JVM 上,可以使用一种基于 NIO
的框架,比如 Netty、Vertx、Spring Reactor 或 JBoss Undertow 中的一种。一个非常流行的非
JVM 选项是 Node.js,它是一个基于 Chrome JavaScript 引擎构建的平台。

另一个方法是使用 NGINX Plus。NGINX Plus 提供了一个成熟的、可扩展的、高性能 web
服务器和一个易于部署的、可配置可编程的反向代理。NGINX Plus
可以管理身份验证、访问控制、负载均衡请求、缓存响应,并提供应用程序可感知的健康检查和监控。

对于API网关需要实现底层多个细粒度的API组合的场景,文章推荐采用响应式编程模型进行而不是传统的异步回调方法组合代码。其原因除了采用回调方式导致的代码混乱外,还有就是对于API组合本身可能存在并行或先后调用,对于采用回调方式往往很难控制。

基于微服务的应用程序是一个分布式系统,必须使用一种进程间通信机制。有两种类型的进程间通信机制可供选择。一种是使用异步的、基于消息传递的机制。有些实现使用诸如
JMS 或 AMQP 那样的消息代理,而其它的实现(如 Zeromq)则没有代理,服务间直接通信。另一种进程间通信类型是诸如 HTTP
或 Thrift 那样的同步机制。通常,一个系统会同时使用异步和同步两种类型。它甚至还可能使用同一类型的多种实现。总之,API
网关需要支持多种通信机制。

注:如果服务是同步调用可以看到微服务模块之间本身是没有彻底解耦的,即如果A依赖B提供的API,如果B提供的服务不可用将直接影响到A不可用。除非同步服务调用在API网关层或客户端做了相应的缓存。因此为了彻底解耦,在微服务调用上更建议选择异步方式进行。

对于大多数基于微服务的应用程序而言,实现 API 网关,将其作为系统的唯一入口很有必要。API
网关负责服务请求路由、组合及协议转换。它为每个应用程序客户端提供一个定制的 API。API
网关还可以通过返回缓存数据或默认数据屏蔽后端服务失败。

第三篇 微服务架构中的进程间通信

基于微服务的分布式应用是运行在多台机器上的;一般来说,每个服务实例都是一个进程。因此,如下图所示,服务之间的交互必须通过进程间通信(IPC)来实现。

对于微服务架构的交互模式,文章从两个维度进行了描述,即

一对一:每个客户端请求有一个服务实例来响应。
一对多:每个客户端请求有多个服务实例来响应。

同步模式:客户端请求需要服务端即时响应,甚至可能由于等待而阻塞。
异步模式:客户端请求不会阻塞进程,服务端的响应可以是非即时的。

对于分为这两个维度进行描述意义不太大,对于同步模式往往只能是1对1,而且还需要同步等待容易引起阻塞,而对于异步模块往往采用消息机制来实现,同时配合消息中间件可以进一步实现消息的发布订阅。而对于EDA事件驱动架构要看到其本质也是伊布消息中间件和消息的发布订阅。

异步消息机制可以做到最大化的解耦,对于数据CUD的场景可以看到是比较容易通过异步消息机制实现的,但是会进一步引入事务一致性问题,即在采用异步消息
机制后往往通过BASE事务最终一致性来解决事务层面的问题。而对于查询功能可以看到是比较难通过异步消息API实现的,在引入这个之前可以看到需要考虑
两方面的问题并解决。

其一是服务网关需要有数据缓存能力,以解决无法从源端获取数据的场景。其二是前端开发框架本身需要支持异步调用和数据装载模式,特别是对于数据查询功能对于用户来讲,在前端的感受仍然需要时同步的。即通过异步方式返回了查询数据后可以动态刷新前端展示界面。

服务版本的问题:这是不可避免要遇到的问题,特别是对于RestAPI调用,由于Json格式本身无Schema返回更加容易忽视了对服务
版本的管理和控制。要知道对于Json数据格式变化仍然会导致RestAPI调用后处理失败。因此服务版本仍然采用大小版本管理机制比较好,对于小版本变
更则直接对原有服务进行覆盖同时对所有受影响的服务消费端进行升级;而对于大版本升级则本质是新增加了一个新服务,而对于旧版本服务逐步迁移和替代。

处理局部失败:文中提到了Netfilix的服务解决方案,对于失败问题的解决要注意常用的仍然是服务超时设置,断路器机制,流量控制,缓存数据或默认值返回等。不论采用哪种失败处理策略,都需要考虑应该尽量减少服务调用失败或超时对最终用户造成的影响。

基于请求/响应的同步 IPC

使用同步的、基于请求/响应的 IPC
机制的时候,客户端向服务端发送请求,服务端处理请求并返回响应。一些客户端会由于等待服务端响应而被阻塞,而另外一些客户端可能使用异步的、基于事件驱动的客户端代码,这些代码可能通过
Future 或者 Rx Observable
封装。然而,与使用消息机制不同,客户端需要响应及时返回。这个模式中有很多可选的协议,但最常见的两个协议是 REST 和
Thrift。


Thrift 也能够让你选择传输协议,包括原始 TCP 和 HTTP。原始 TCP 比 HTTP 更高效,然而 HTTP
对于防火墙、浏览器和使用者来说更友好。文中对于两种实现方式已经描述的相当详细,可以看到当前互联网OpenAPI平台和微服务架构实现中仍然是大量以采用Rest
API接口为主。

而对于消息格式的选择,可以看到在使用RestAPI接口的时候,更多的是采用了Json消息格式而非XML,对于SOAP
WebService则更多采用了XML消息格式。如果采用Thrift则还可以采用二进制消息格式以提升性能。

第四篇 服务发现的可行方案以及实践案例


首先还是先说场景,看似简单的服务注册和服务目录库管理为何会变复杂,其主要的原因还是在结合了云端PaaS和Docker容器部署后,对于微服务模块部
署完成后提供出来的IP地址是动态在变化的,包括模块在进行动态集群扩展的时候也需要动态接入新的服务提供IP地址。正是由于这个原因引入了服务发现和管
理的困难度。

在文章中提到了两种服务发现模式,即客户端发现模式和服务端发现模式,分开描述如下:

服务客户端发现模式

使用客户端发现模式时,客户端决定相应服务实例的网络位置,并且对请求实现负载均衡。客户端查询服务注册表,后者是一个可用服务实例的数据库;然后使用负
载均衡算法从中选择一个实例,并发出请求。客户端从服务注册服务中查询,其中是所有可用服务实例的库。客户端使用负载均衡算法从多个服务实例中选择出一
个,然后发出请求。

注:这是类似Dubbo实现机制一样的两阶段模式,即任何一个服务的消费都需要分两个步骤进行,第一步首先是访问服务注册库(更多是API
GateWay提供的一个能力)返回一个已经动态均衡后的服务可用地址,第二步即客户端和该地址直接建立连接进行服务消费和访问。


在这种模式的实现中有两个重点,其一是动态负载均衡算法,其二是服务网关需要能够对原始服务提供点进行实时的心跳检测以确定服务提供的可用性。

Netflix OSS 是客户端发现模式的绝佳范例。Netflix Eureka
是一个服务注册表,为服务实例注册管理和查询可用实例提供了 REST API 接口。Netflix Ribbon 是 IPC 客户端,与
Eureka 一起实现对请求的负载均衡。

缺点:底层的IP虽然动态提供出去了,但是最终仍然暴露给了服务消费方,再需要进一步做安全和防火墙隔离的场景下显然是不能满足要求的。

服务端发现模式

客户端通过负载均衡器向某个服务提出请求,负载均衡器查询服务注册表,并将请求转发到可用的服务实例。如同客户端发现,服务实例在服务注册表中注册或注销。在原文中有图示,基本看图就清楚了,即在服务注册库前新增加了一个Load
Balancer节点。注:这两个节点感觉是可以合并到API GateWay的能力中去的。

服务端发现模式兼具优缺点。它最大的优点是客户端无需关注发现的细节,只需要简单地向负载均衡器发送请求,这减少了编程语言框架需要完成的发现逻辑。并且
如上文所述,某些部署环境免费提供这一功能。这种模式也有缺点。除非负载均衡器由部署环境提供,否则会成为一个需要配置和管理的高可用系统组件。(会增大服务端压力)


服务注册表


服务注册表需要高可用而且随时更新。客户端能够缓存从服务注册表中获取的网络地址,然而,这些信息最终会过时,客户端也就无法发现服务实例。因此,服务注册表会包含若干服务端,使用复制协议保持一致性。

首先可以看到服务注册表本身不能是单点,否则存在单点故障,当服务注册表有多台服务器的时候同时需要考虑服务注册库信息在多台机器上的实时同步和一致。我们操作和配置服务注册信息的时候往往只会在一个统一的服务管控端完成。

其次如果服务注册服务器宕机是否一定影响到服务本身的消费和调用,如果考虑更高的整体架构可用性,还可以设计对于服务注册库信息在客户端本地进行缓存,当服务注册表无法访问的时候可以临时读取本地缓存的服务注册库信息并发起服务访问请求。

对于服务注册表,文章提供了三种选择,感觉最常用的实现仍然是基于ZooKeeper进行的。

Etcd – 高可用、分布式、一致性的键值存储,用于共享配置和服务发现。
Consul – 发现和配置的服务,提供 API 实现客户端注册和发现服务。
Apache ZooKeeper – 被分布式应用广泛使用的高性能协调服务。

如前所述,服务实例必须在注册表中注册和注销。注册和注销有两种不同的方法。方法一是服务实例自己注册,也叫自注册模式(self-registration
pattern);另一种是采用管理服务实例注册的其它系统组件,即第三方注册模式。(原文有详细机制描述,不再累述)

虽然方法一把服务实例和服务注册表耦合,必须在每个编程语言和框架内实现注册代码。但是在自己实现完整微服务架构中,考虑到PaaS平台下微服务模块的动
态部署和扩展,采用方法1相当来说更加容易实现。但是方法1仍然不能代替服务注册库本身应该具备的服务节点的心跳检测能力。