写点什么

老司机避坑指南:如何快速搞定微服务架构?

  • 2020 年 5 月 29 日
  • 本文字数:5890 字

    阅读完需:约 19 分钟

老司机避坑指南:如何快速搞定微服务架构?

如今,微服务架构已经成为了现代应用开发的首选。虽然它能够解决大部分的程序问题,但是它并非一颗百试不爽的“银弹”。


在采用这种架构之前,我们应当事先了解可能出现的各种问题及其共性,预先为这些问题准备好可重用的解决方案。


那么,在开始深入讨论微服务的不同设计模式之前,让我们先了解一下微服务架构的一些构建原则:


  • 可扩展性

  • 可用性

  • 弹性

  • 独立、自主性

  • 去中心化治理

  • 故障隔离

  • 自动调配

  • 通过 DevOps 实现持续交付


在遵循上述各条原则的同时,我们难免会碰到一些挑战。下面我们来具体讨论可能出现的各种问题、及其解决方案。


分解模式

按照业务功能分解

问题: 微服务是有关松散耦合的服务,它采用的是单一职责原则。虽然我们在逻辑原理上都知道要将单个应用分成多个小块,但是在实际操作中,我们又该如何将某个应用程序成功分解成若干个小的服务呢?


解决方案: 有一种策略是按照业务功能进行分解。此处的业务功能是指能够产生价值的某种业务的最小单位。那么一组给定业务的功能划分则取决于企业本身的类型。


例如,一家保险公司的功能通常会包括:销售、营销、承保、理赔处理、结算、合规等方面。每一个业务功能都可以被看作是一种面向业务、而非技术的服务。


按照子域分解

问题: 按照业务功能对应用程序进行分解只是一个良好的开端,之后您可能会碰那些不易分解的所谓“神类”(God Classes)。这些类往往会涉及到多种服务。


例如,订单类就会被订单管理、订单接受、订单交付等服务所使用到,那么我们又该如何分解呢?


解决方案: 对于“神类”的问题,DDD(Domain Driven Design,领域驱动设计)能够派上用场。


它使用子域(Subdomain)和边界上下文(Bounded Context)的概念来着手解决。


DDD 会将企业的整个域模型进行分解,并创建出多个子域。每个子域将拥有一个模型,而该模型的范围则被称为边界上下文。那么每个微服务就会围绕着边界上下文被开发出来。


注意: 识别子域并不是一件容易的事,我们需要通过分析业务与组织架构,识别不同的专业领域,来对企业加强了解。


刀砍模式(Strangler Pattern)

问题: 前面我们讨论的设计模式一般适用于针对那些“白手起家”的 Greenfield 应用进行分解。


但是我们真实接触到的、约占 80% 的是 Brownfield 应用,即:一些大型的、单体应用(Monolithic Application)。


由于它们已经被投入使用、且正在运行,如果我们简单按照上述方式,同时对它们进行小块服务的分解,将会是一项艰巨的任务。


解决方案: 此时,刀砍模式(Strangler Pattern)就能派上用场了。我们可以把扼杀模式想象为用刀砍去缠在树上的藤蔓。


该方案适用于那些反复进行调用的 Web 应用程序。对于每一个 URI(统一资源标识符)的调用来说,单个服务可以被分解为不同的域和单独的子服务。其设计思想是一次仅处理一个域。


这样,我们就可以在同一个 URI 空间内并行地创建两套独立的应用程序。最终,在新的应用重构完成后,我们就能“刀砍”或替换掉原来的应用程序,直到最后我们可以完全关闭掉原来的单体应用。



集成模式

API 网关模式

问题: 当一个应用程序被分解成多个小的微服务时,我们需要关注如下方面。


具体如下:


  • 如何通过调用多个微服务,来抽象出 Producer(生产者)的信息。

  • 在不同的渠道上(如电脑桌面、移动设备和平板电脑),应用程序需要不同的数据来响应相同的后端服务,比如:UI(用户界面)就可能会有所不同。

  • 不同的 Consumer(消费者)可能需要来自可重用式微服务的不同响应格式。谁将去做数据转换或现场操作?

  • 如何处理不同类型的协议?特别是一些可能不被 Producer 微服务所支持的协议。


解决方案: API 网关将有助于解决在微服务实施过程中所涉及到的上述关注点。


具体如下:


  • API 网关是任何微服务调用的统一入口。

  • 它像代理服务一样,能够将一个微服务请求路由到其相关的微服务处,并抽象出 Producer 的细节。

  • 它既能将一个请求扇出(fan out,输出)到多个服务上,也能汇总多个结果,并发回给 Consumer。

  • 鉴于通用 API 无法解决 Consumer 的所有请求,该方案能够为每一种特定类型的客户端创建细粒度的 API。

  • 它也可以将某种协议请求(如:AMQP)转换为另一种协议(如:HTTP),反之亦然,从而方便了 Producer 和 Consumer 的处理。

  • 它也可以将认证与授权存储库从微服务中卸载出去。


聚合器模式

问题: 虽然我们已经在 API 网关模式中讨论了如何解决聚合数据的问题,不过我们仍将做进一步的讨论。


当我们将业务功能分解成多个较小的逻辑代码块时,有必要思考每个服务的返回数据是如何进行协作的。


显然,该责任不会留给 Consumer,那么我们就需要理解 Producer 应用的内部实现。


解决方案: 聚合器模式将有助于解决该问题。它涉及到如何聚合来自不同服务的数据,然后向 Consumer 发送最终响应。


具体说来,我们有如下两种实现方法:


  • 复合微服务(Composite Microservice) 将会去调用全部所需的微服务,整合各种数据,并在回传之前转换数据。

  • API 网关(API Gateway) 也能对多个微服务的请求进行 Partition(分区),并在发送给 Consumer 之前聚合数据。


我们建议: 如果您用到了任何业务逻辑的话,请选用复合微服务;否则请采用 API 网关方案。


客户端 UI 合成模式

问题: 当各种服务按照业务功能和子域被分解开发时,它们需要根据用户体验的预期效果,从一些不同的微服务中提取数据。


在过去的单体应用中,我们只要从 UI 到后端服务的唯一调用中获取所有的数据,并刷新和提交到 UI 页面上便可。如今,情况则不同了。


解决方案: 对于微服务来说,UI 必须被设计成单屏、单页面的多段、多区域的结构。


每一段都会去调用单独的后端微服务,以提取数据。像 Angular JS 和 React JS 之类的框架都能够实现为特定的服务合成 UI 组件。


通过被称为单页应用(Single Page Applications,SPA)的方式,它们能够使得应用程序仅刷新屏幕的特定区域,而不是整个页面。


数据库模式

按服务分配数据库

问题: 您可能会碰到如何定义数据库架构的微服务问题。


下面是具体的关注点:


  • 服务必须是松散耦合的,以便能够被二次开发、部署和独立扩容。

  • 各个业务交易需要在横跨多个服务时,仍保持不变。

  • 某些业务交易需要从多个服务中查询到数据。

  • 数据库有时需要根据规模需求被复制与分片。

  • 不同的服务具有不同的数据存储需求。


解决方案: 为了解决上述需求,我们需要通过设计为每个微服务配备一个独享的数据库模式。


即:该数据库仅能被其对应微服务的 API 单独访问,而不能被其他服务直接访问到。


例如,对于关系型数据库,我们可以使用:按服务分配私有表集(private-tables-per-service)、按服务分配表结构(schema-per-service)、或按服务分配数据库服务器(database-server-per-service)。


每个微服务应该拥有一个单独的数据库 ID,以便它们在独享访问的同时,禁止再访问其他的服务表集。


按服务共享数据库

问题: 上面讨论的按服务分配数据库是一种理想的微服务模式,它一般被前面提到的 Greenfield 应用和 DDD 式的开发。但是,如果我们面对的是需要采用微服务的单体应用就没那么容易了。


解决方案: 按服务共享数据库的模式虽然有些违背微服务的理念,但是它对于将前面提到的 Brownfield 应用(非新建应用)分解成较小的逻辑块是比较适用的。


在该模式下,一个数据库可以匹配不止一个的微服务,当然也至多 2~3 个,否则会影响到扩容、自治性和独立性。


命令查询职责隔离(CQRS)

问题: 对于按服务分配数据库的模式而言,我们如何在微服务的架构中,实现对多个服务进行联合查询数据的需求呢?


解决方案: CQRS 建议将应用程序拆分成两个部分:命令和查询。命令部分主要处理创建、更新和删除之类的请求;查询部分则利用物化视图(Materialized Views)来处理各种查询。


它通常配合事件溯源模式(Event Sourcing Pattern)一起创建针对任何数据的变更事件。而物化视图则通过订阅事件流,来保持更新。


Saga 模式

问题: 当每个服务都有自己的数据库,而且业务交易横跨多个服务时,我们该如何确保整体业务数据的一致性呢?


例如:对于某个带有客户信用额度标识的电商应用而言,它需要确保新的订单不会超出客户的信用额度。


但是,由于订单和客户分属不同的数据库,应用程序无法简单地实现本地交易的 ACID(原子性、一致性、隔离性、持久性)特性。


解决方案: Saga 代表了一个高层次的业务流程,它是由一个服务中的多个子请求,并伴随着逐个更新的数据所组成。在某个请求失败时,它的补偿请求会被执行。


实现方式有如下两种:


  • 编排(Choreography): 没有中央协调器,每个服务都会产生并侦听其他服务的事件,以决定是否应采取行动。

  • 协调(Orchestrator): 由一个中央协调器(对象)负责集中处理某个事件(Saga)的决策,和业务逻辑的排序。



观测模式

日志聚合

问题: 我们来考虑这样一个用例:某个应用程序包括了那些在多台机器上运行的多个服务实例,各种请求横跨在这些多个服务实例之中。同时,每个服务实例都会生成一种标准格式的日志文件。


那么我们如何针对某个特定的请求,通过各种日志来理解该应用程序的行为呢?


解决方案: 显然,我们需要一个集中化的日志服务,将各个服务实例的日志予以聚合,以便用户对日志进行搜索和分析。他们可以针对日志中可能出现的某些消息,配置相应的警告。


例如:PCF(Pivotal Cloud Foundry)平台拥有一个日志聚合器,它从每种元素(如:路由器、控制器等)中收集与应用相关的日志。而 AWS Cloud Watch 也具有相似的功能。


性能指标

问题: 当各种服务组合随着微服务架构变得越来越复杂时,监控交易的完整性,并能够在出现问题时及时发出警告,就显得尤为重要了。那么我们该如何收集与应用相关的性能指标呢?


解决方案: 为了收集不同操作的统计信息,并提供相应的报告和警告。


我们一般会用两种模式来聚集各项指标:


  • 推式: 将各项指标推给专门的指标服务,如:NewRelic 和 AppDynamics。

  • 拉式: 从指标服务处拉取各项指标,如:Prometheus。


分布式跟踪

问题: 在微服务架构中,横跨多个服务的请求是比较常见的。某个服务需要通过横跨多个服务去执行一到多项操作,才能处理一些特定的请求。


那么,我们该如何通过跟踪某个端到端的请求,以获知出现的问题呢?


解决方案: 我们需要一种具有特性的服务。


具体特性服务如下:


  • 为每个外部请求分配一个唯一的 ID。

  • 将该外部请求 ID 传给所有的服务。

  • 在所有的日志消息中都包含该外部请求 ID。

  • 在集中式服务中,记录处理外部请求的相关信息,包括:开始时间、结束时间、和执行时间。


Spring Cloud Slueth + Zipkin Server,是一种常见的实现方式。


健康检查

问题: 我们在实施微服务架构的过程中,可能会碰到某个服务虽已启动,但是无法处理交易的情况。


那么,我们该如何通过负载均衡的模式,来确保请求不会“落入”失败的实例中呢?


解决方案: 每个服务都需要有一个端点,通过诸如 /health 的参数,对应用进行健康检查。


该 API 需要能够检查主机的状态,其他服务与基础设施的连接性,以及任何特定的逻辑关系。


Spring Boot Actuator 不但能够实现端点的健康检查,还能够被定制实施。


横切关注点模式(Cross-Cutting Concern Patterns)

外部配置

问题: 通常情况下,一个服务需要去调用其他的服务和数据库。在诸如开发、QA(Quality Assurance,质量保证)、UAT(User Acceptance Test,用户验收测试)、和生产环境中,端点的 URL、或某些配置的属性会有所不同。


因此,有时候我们需要对这些服务的各种属性进行重构、和重新部署。那么我们如何避免在配置变更中修改代码呢?


解决方案: 外部化(externalize)所有的配置,包括各个端点的 URL 和信任凭据,以保证应用程序在启动时、或运行中能够加载它们。


Spring Cloud 配置服务器提供了向 GitHub 进行属性外部化的选项,并将其作为环境属性予以加载。


此法保证了应用程序能够在启动时就被访问到,或是在不重启服务器的情况下实现刷新。


服务发现模式

问题: 当微服务初具规模时,我们需要考虑如下两个关于调用服务方面的问题。


具体问题如下:


  • 由于采用了容器技术,IP 地址往往被动态地分配给不同的服务实例。因此,每次当 IP 地址发生变化时,Consumer 服务可能会受到影响,需要我们手动更改。

  • Consumer 需要记住每个服务的 URL,这就倒退成了紧耦合的状态。


那么,Consumer 或路由器该如何获知所有可用的服务实例与位置呢?


解决方案: 我们需要创建一个服务注册表,来保存每个 Producer 服务的元数据(Meta Data)。


一个服务实例在启动时,应当被注册到表中;而在关闭时,需从表中被注销。


Consumer 或路由器通过查询该注册表,就能够找到服务的位置。Producer 服务也需要对该注册表进行健康检查,以确保能够消费到那些可用的、且正在运行的服务实例。


我们一般有两种服务发现的类型:客户端和服务器端。使用客户端发现的例子是 Netflix Eureka;而使用服务器端发现的例子是 AWS ALB。


断路器模式

问题: 有时候,某个服务在调用其他服务,以获取数据的时候,会出现下游服务(Downstream Service)“掉线”的情况。


它一般会带来两种结果:


  • 该请求持续发往该掉线服务,直至网络资源耗尽和性能降低。

  • 用户产生不可预料的、较差的使用体验。


那么我们该如何避免服务的连锁故障,并妥善处置呢?


解决方案: Consumer 应该通过一个代理来调用某项远程服务,就像电路中的断路器一样。


当出现持续失败的数量超过设定阈值时,断路器就会“跳闸”一段时间,从而导致所有调用远程服务的尝试被立即切断。


在超过设定时间之后,断路器只允许有限数量的测试请求通过。而如果这些请求成功了,那么断路器将恢复正常运行;否则判定为故障依旧,并重新开始新的定时周期。


Netflix Hystrix 就很好地使用了该断路器模式。它可以在断路器“跳闸”的时候,帮助您定义一种回退机制,以提供更好的用户体验。


蓝绿部署模式

问题: 在微服务架构中,一个应用程序可以有多个微服务。如果我们为了部署一个增强版,而停止所有的服务,那么停机时间一旦过长,就会对业务造成影响。


况且,这对于回退来说也将会是一场噩梦。那么我们该如何避免、或减少部署过程中服务的停机时间呢?


解决方案: 我们可以采用蓝绿部署的策略,以减少或消除停机时间。在蓝、绿两个相同的生产环境中,我们假设绿色环境有着当前真实的实例,而蓝色环境具有应用程序的最新版本。


在任何时候,只有一个环境能够处理所有真实的流量,并对外提供服务。如今,所有的云服务平台都能提供基于蓝绿部署的选项。


当然,我们还可以采用许多其他的微服务架构模式, 如:Sidecar 模式、链式微服务(Chained Microservice)、分支微服务(Branch Microservice)、事件溯源模式(Event Sourcing Pattern)、和持续交付方式等。


本文作者:Rajesh Bhojwani,陈峻编译


本文首发于联盟技术公众号“51CTO 技术栈”。


2020 年 5 月 29 日 15:30316

评论

发布
暂无评论
发现更多内容

人性的弱点-读书笔记,java面试题大汇总小山博客

Java 程序员 后端

什么是接口的幂等性,如何实现接口幂等性?,java微服务架构视频下载

Java 程序员 后端

从Java小白到拿到30k offer,分享自己的学习路程,java基础案例教程pdf百度云

Java 程序员 后端

为什么阿里巴巴不建议在for循环中使用”+,java编程技术开发题库和答案

Java 程序员 后端

二十不惑的年纪,竟4面拿下字节跳动offer,我简直走了,jdk和jre区别面试题

Java 程序员 后端

Vue进阶(幺伍伍):vue-resource 拦截器 interceptors 使用

No Silver Bullet

Vue 10 月日更 vue-resource

五分钟看穿Java并发相关概念,并发原来如此简单,linux学习步骤

Java 后端

京东T7架构师手写的10万字Spring Boot详细学习笔记+源码免费下载

Java 程序员 后端

京东面试,这个问题让我与50万擦肩而过,帮忙看看,mysql下载教程window10

Java 程序员 后端

人到中年的焦虑,Java面试第一问就是做过什么最有难度的项目

Java 程序员 后端

今日头条一面:十道经典面试题解析,Redis如何实现高可扩展

Java 程序员 后端

抖音同城获客系统引流脚本

快抖询盘hk012233

抖音同城获客 获客系统引流脚本 抖音引流脚本 抖音引流

京东T9裂墙力荐的82万字spring cloud微服务和分布式系统实践文档

Java 程序员 后端

九年程序员生涯,我的一些经验教训,offer来了java面试百度云版

Java 程序员 后端

最新版本抖音私信辅助软件移动端

快抖询盘hk012233

抖音私信辅助软件 抖音自动私信软件 抖音私信

五位阿里大牛联手撰写的《深入浅出Java多线程》,java开发视频直播

Java 程序员 后端

京东前端面经(123面详细),进阶学习工作最全指南

Java 程序员 后端

二叉树的各种算法面试题及答案解析,linux基础教程第二版pdf

Java 程序员 后端

京东十年T8架构师手撕MySQL:手写666页核心知识,超85,java在线编译器实现原理

Java 程序员 后端

什么神仙笔记!阿里P9用39实例+1项目讲明白了Spring Cloud家族

Java 程序员 后端

从 0 到 1,带你解剖 MVP 的神秘之处,并自己动手实现 MVP !

Java 程序员 后端

互联网寒冬下!疫情又遭滑铁卢!从裁员到斩获新offer我经历了什么

Java 程序员 后端

什么?这个岗位薪资秒杀一众程序员?,java技术面试常见问题

Java 程序员 后端

从Mybatis源码到Spring动态数据源底层原理分析系列一、Mybatis初始化源码浅析

Java 程序员 后端

二面余额宝(交叉面),mybatis功能架构

Java 程序员 后端

五分钟带你了解Seata分布式事务,java基础菜鸟教程txt

Java 程序员 后端

人工智能 - 语音识别的技术原理是什么,Java理论知识思维导图

Java 程序员 后端

什么是 MySQL 全局锁、表锁、行锁,Java高级开发岗必问知识点

Java 程序员 后端

互联网架构“高并发”到底怎么玩,用Elasticsearch搞定日均1亿订单查询

Java 程序员 后端

京东三面被惨虐:索引+数据库,kafka入门与实践epub

Java 程序员 后端

今年,我在字节跳动面试了九次【已意向书,mongodb入门pdf

Java 程序员 后端

Flutter 自动化测试

Flutter 自动化测试

老司机避坑指南:如何快速搞定微服务架构?-InfoQ