1.1.2 微服务架构常见设计模式
微服务架构为软件研发过程带来了巨大的优势,包括设计阶段的有界上下文、云原生、去中心化治理、容错及灵活部署、API网关、断路器、独立数据库、消息代理、服务发现,开发、测试和集成阶段的松耦合、可重用架构、服务规模、技术栈自由度、CI/CD、数据持久化、数据隔离、测试自动化,运行维护阶段的容器化、服务独立性、可靠性、故障隔离、可扩展性、快速演化等特性。
微服务架构在企业级应用中已经获得了巨大的成功,Amazon、Netflix、Spotify和Twit-ter都已经将其核心业务转向了微服务架构。但它同时也带来了一些具体的问题,例如在设计阶段如何把握微服务分解的粒度以及信息安全策略的设计,在开发阶段如何管理分布式存储和测试,在运行阶段如何应对微服务架构带来的额外网络开销和计算资源消耗。因此微服务架构设计需要遵循以下基本原则:1)独立自治的服务;2)可扩展;3)去中心化;4)弹性服务;5)实时负载均衡;6)可用性;7)通过DevOps持续集成和交付;8)无缝API集成和持续监控;9)故障隔离;10)自动扩容。为实现上述目标,可以根据业务模型,参考图1-1中的模式来设计微服务架构。
图1-1 微服务架构设计模式
1.聚合器模式
聚合器是指收集数据并进行展示的网页或代码。在微服务架构中,聚合器模式通常是指通过调用不同的微服务来获取所需的信息并实现相应的功能,这种模式适用于需要对不同微服务的输出进行整合的场景。聚合器模式的设计基于DRY(Don′t Repeat Yourself)原则。因此可以将业务逻辑抽象到一个微服务,并将该服务和其他服务进行组合。例如,考虑图1-2中的两个服务:服务A和服务B。
图1-2 聚合器模式
服务A和服务B都有自己的数据库,可以通过向聚合器提供数据来同时扩展这些服务,也可以通过具有唯一事务ID的聚合器从两个服务中收集数据并应用业务逻辑,最后将其发布为REST端口,这些数据可以最终被对应的服务所使用。
2.API网关模式
微服务架构下的应用设计要求每个服务都要实现某个小而具体的功能。但是,当应用程序从整体分解为小且自治的微服务时,开发人员可能会面临以下一些需求:
从多个微服务请求数据。
满足同一个数据库服务上多样化的用户服务数据请求。
根据数据消费者的需求对复用微服务中的数据进行转换。
处理来自不同协议的请求。
这些场景所需要的设计模式则是API网关模式。API网关模式可以看作一种代理服务,作为聚合器服务的变体,它可以将请求发送到多个服务,并将结果聚合回服务组合或消费者端。如图1-3所示,API网关模式还可以充当所有微服务的入口,并为不同类型的客户端创建细粒度的API。
因此,一旦客户端发送请求,这些请求就会传递到API网关,由该网关充当入口点,将客户端的请求转发给适当的微服务,然后,在负载均衡器的处理下将请求发送到对应的服务。微服务架构采用服务发现的方式给出服务之间通信的路径,然后通过无状态服务器(HTTP请求/消息总线)互相通信。API网关还可以对请求协议进行转换,以及承担微服务之间安全认证/授权的工作。
图1-3 API网关模式
3.链式微服务模式
链式微服务模式的适用场景是服务产生的单个输出是多个服务链式输出的组合。如图1-4所示,如果将三个服务排成一条链,那么,来自客户端的请求首先被服务A接收,然后服务A与服务B进行通信并收集数据,接下来是服务B与服务C通信并收集数据,然后生成最终的输出。所有这些服务都使用同步HTTP请求或响应进行消息传递,在请求通过所有服务并生成相应的响应之前,客户端不会获得任何输出。
图1-4 链式微服务模式
另外在链式微服务模式中,服务A到服务B的请求可能看起来与服务B到服务C的不同。同样,服务C到服务B的响应与服务B到服务A的响应也可能完全不同。从设计上来说,不建议做长链,因为客户端要等到整个链完成后才能得到输出。
4.异步消息传递模式
在链式微服务模式中,客户端在同步消息传递过程中会阻塞或者必须等待很长时间。但是,如果不希望客户端等待很长时间,那么可以选择异步消息传递模式。在这种设计模式中,所有服务之间都可以相互通信,但它们不必按顺序相互通信。
如图1-5所示,考虑3个服务:服务A、服务B和服务C。来自客户端的请求可以同步发送到服务C和服务B,这些请求将会放在消息队列中。除此之外,还可以将请求发送到服务A,其返回对象不必与该请求对象完全一致。
图1-5 异步消息传递模式
5.服务共享数据库模式
当前,每个应用程序的正常运行都需要并产生大量数据,因此,当应用程序从单体架构切换到微服务架构时,每个微服务都需要有足够的数据量来完成对它的请求。从设计上来看,可以为每个服务设置一个数据库,也可以是多个服务共享一个数据库。但在实际场景中可能会出现以下问题:
数据的重复和不一致。
不同的服务有不同类型的存储需求。
业务逻辑需要查询多个服务所拥有的数据。
数据的非规范化。
这里前三个问题可以通过为每个服务设置单独的数据库来解决,因为微服务只会访问自己的数据,每个微服务都将拥有自己的数据库,从而防止系统中的其他服务使用该数据库。
除此之外,为了解决规范化问题,可以采用如图1-6所示的服务共享数据库模式,通过对齐多个数据库来解决数据的规范化问题。这种模式有助于单体应用向微服务架构分解过程中的数据获取。但是,考虑到微服务的扩展,这种共享数据库数量不可设计太多,一般限制在2~3个微服务之内。
图1-6 服务共享数据库模式
6.分支模式
分支(微服务设计)模式可以同时处理来自两个或多个独立微服务的请求和响应。与链式微服务模式不同,在分支模式下,服务请求不是按顺序传递的,而是将请求传递给两个或多个互斥的服务链。这种设计模式扩展了聚合器模式,同时也提供了多链/单链响应的灵活性。
考虑电子商务应用,可能需要从多个服务进行数据检索,而这些数据可能是来自各种服务的协作输出。在这种模式下,如图1-7所示,服务请求可以直接发送给服务A,而服务A则可以将请求传递给独立服务B,以及链式服务C、D。因此,在需要从多个源检索数据的场景下,可以使用分支模式。
7.命令查询职责分离器模式
从微服务数据库设计模式上来看,微服务根据应用场景的不同,有单独的数据库或服务间共享的数据库。但是,在单独数据库模式下,我们无法直接对该微服务所拥有的数据库进行查询,因为数据访问仅限于服务自身的API。因此,如果应用存在这类需求,则可以使用命令查询职责分离器(Command Query Responsibility Segregation,CQRS)模式对微服务进行设计。
图1-7 分支模式
如图1-8所示,在CQRS模式下,应用程序将分为两个独立的部分:命令(Command)和查询(Query)。命令部分将处理与CREATE、UPDATE、DELETE相关的所有请求,而查询部分将处理物化视图,并通过一系列事件更新。这里的独立可以是服务级别的,也可以是API级别的,同时在设计时需要考虑两个数据库之间的数据一致性问题。
图1-8 命令查询职责分离器模式
8.断路器模式
顾名思义,断路器模式用于在被请求或者响应的服务不工作时,停止该服务的请求和响应过程。例如,客户端正在发送请求从多个服务中检索数据,但是由于某些问题,其中一项服务已关闭。这时候将面临两个问题:首先,由于客户端无法知道特定服务已关闭,因此将不断发送请求到该服务。其次,由于这类无效请求的不断发送,导致网络资源枯竭,性能低下,用户体验差。
在这类场景下,为了避免此类问题,可以使用断路器模式。如图1-9所示,在断路器模式下,客户端将调用一个远程服务作为代理,该代理将作为断路器。当故障数量超过阈值时,断路器会在设定的断路周期内跳闸。然后,所有尝试调用远程服务的请求都将在这段时间内失败。一旦该周期结束,断路器将允许有限数量的测试请求通过,如果这些请求成功,断路器将恢复正常操作,如果仍然出现故障,则断路周期再次开始。
图1-9 断路器模式
9.分解设计模式
在微服务开发过程中,开发人员的想法是创建小型服务,每个服务都有自己的功能。但是,将应用程序分解为小而自治的单元也必须符合逻辑。要将小型或大型应用程序分解为小型服务,可以使用分解设计模式。在此模式下,可以基于业务能力或子领域对应用进行分解。例如考虑一个电子商务应用,如果按业务能力进行分解,可以分为订单、付款、客户、产品等独立服务。但是,在同样场景中,如果通过分解子领域来设计应用程序,例如在这个例子中,把客户看作一个子领域,那么这个子领域将用于客户管理、客户支持等。所以,在分解过程中,可以使用领域驱动设计,将整个系统模型分解为子领域,每个子领域都有自己的特定业务模型和范围(有界上下文)。这样一来,开发人员设计微服务时,将围绕范围或上下文来设计这些服务。尽管分解设计模式理论上可行,但由于识别子领域和业务能力对于大型应用程序来说并非易事,因此对于大型单体应用程序并不完全可行。