微服务架构和微服务的设计模式
微服务在企业中可以带来积极的影响。 因此,如何处理微服务体系架构(MSA)和一些微服务设计模式以及微服务体系架构的一般目标或原则是很有必要的。 以下是微服务架构实现中要考虑的四个目标
- 降低成本 — MSA将降低设计,实施和维护IT服务的整体成本
- 提高发布速度 — MSA将提高项目从构建到部署的速度。
- 提升弹性 — MSA将提升我们服务网络的弹性
- 有可见性 — MSA为您的服务和网络上提供更好的可见性。
MSA是建立在哪些原则基础之上是需要你去了解的
- 可扩展性
- 可用性
- 弹性扩展
- 灵活性
- 独立性,自主性
- 去中心化治理
- 故障隔离
- 自动配置
- 通过DevOps持续交付
在坚持这些原则基础上推广自己的解决方案或者系统会带来一些挑战和问题,这些问题在许多解决方案中都很常见,而且可以通过使用正确的设计模式来解决, 这些就是微服务的设计模式,这些模式可以分为五个大类,而每一类又包含了许多设计模式。具体如下图所示:
解耦模式(Decomposition Patterns)
按业务能力解耦
通过运用单一职责原则,微服务总是会把服务之间的耦合变为松耦合,微服务通过业务能力解耦,而且服务的定义是对应于业务能力。业务能力这个概念来自于业务架构模型,某种程度上来说,业务确实是可以产生价值,业务能力经常是对应于一个业务实体对象。例如:
- 订单管理对应于订单
- 客户管理对应于客户
按照子域解耦
按照业务能力解耦一个应用可能是一个好的开始,但是你可能会遇到所谓的“神类”(God Classes),就是哪些不容易解耦的类,而且这些类在多个服务之间很常见。领域驱动设计(DDD) 参考应用问题空间--业务--做为一个域(domain)。一个域由多个子域组成,而每一个子域对应于业务的不同的部分。
子域可以分类如下:
- 核心(Core) — 区分业务的关键 和 应用中最有价值的部分
- 支撑(Supporting ) — 与业务相关,但是不是关键部分,可以内部实现,也可以外部实现
- 泛化(Generic ) — 不针对特定的业务,理想情况下使用现成的软件实施
订单管理的子域包括:
- 产品目录服务
- 库存管理服务
- 订单管理服务
- 交付管理服务
按事务解耦/两阶段提交(2PC)模式
可以通过事务分解服务,然后系统中将会有多个事务。 分布式事务处理的重要参与者之一是事务处理协调器[3]。分布式事务包括两个步骤:
- 准备阶段 — 在此阶段中,事务的所有参与者都准备提交并通知协调器他们已准备好完成事务。
- 提交或者回滚阶段 — 在此阶段中,事务协调器向所有参与者发出提交或回滚命令
2PC的问题是和单个微服务执行时间来对比耗时长。即使微服务在相同的网段中,协调微服务之间的事务依旧会拖慢整个系统。因此这个解决方案一般不使用在高负载的场景中
扼杀模式
以上三种设计模式用于对未开发的应用(greenfield apps)的解耦, 但是我们80%的工作都是和庞大而僵化的应用(遗留代码库)打交道。扼杀模式(Strangler Pattern)就是为了解决这个问题而来的。在相同的URI空间中创建两个独立共存的应用,随着时间的推移,重构过的新应用将“扼杀”或者替代原来的应用,直到最终把庞大而僵化的应用关闭掉。扼杀应用(Strangler Application)的步骤分为转换,共存和消灭三步[4]:
- 转换(Transform ) — 用现代方式创建一个新的平行的站点
- 共存(Coexist ) — 将已有的站点重定向到新的站点,新站点逐步实现老站点的功能
- 消灭(Eliminate ) — 移除已有的站点的旧的功能
隔板模式
将应用程序的元素隔离到池中,以便如果其中一个失败,其他应用程序将继续运行提供服务,这个设计模式称为隔板模式(Bulkhead), 因为他类似于船体中一个个被隔离的分区。根据使用者负载和可用性要求,这些分区服务实例被分割到不同的组里面。这种设计模式有助于隔离故障(isolate failures), 并允许即使在故障期间仍可为某些使用者维持服务功能.
数据库模式(Database Patterns)
为微服务定义数据库架构时,我们需要考虑以下几点:
- 服务之间必须是松散耦合的, 它们可以独立开发,部署和扩展。
- 业务事务在跨越多个微服务的时候保证不变
- 一些业务事务跨越多个微服务来查询数据
- 有时数据库必须可以复制,并且可以弹性共享
- 不同的服务有不同的数据存储要求
每一个服务对应一个数据库(Database per Service)
为了解决上述问题,必须为每个微服务设计一个数据库. 该数据库只能是该服务私有的,并且只能通过微服务的API访问,不能被其他的微服务直接访问。例如,对关系型数据库,我们可以使用 每个服务有私有化的表(private-tables-per-service), 每个服务有自己的schema (schema-per-service), 或者每个服务有私有的数据库服务器 (database-server-per-service)
每一个服务共享数据库 (Shared Database per Service)
我们已经讨论了每个服务一个数据库是微服务的理想选择,但它是微服务的反模式(anti-pattern)。如果一个单一而又庞大的应用,并试图把它拆分为微服务,那么数据库的反范式化(denormalization )就不那么容易。将每个微服务共享数据库不是理想的情况,但是是可行的解决方案。大多数人认为这是微服务的反模式,但对于brownfield 应用,这是将应用程序分解成较小逻辑部分的一个很好的开始。但是对于greenfield 应用不太适用。
命令查询的责任分离 (Command Query Responsibility Segregation,CQRS)
一旦我们实现了每个服务对应一个数据库,就需要将从多个微服务查询返回的数据连接起来。显然这是不可能的。CQRS建议将应用分为两个部分 — 命令端 (command side)和查询端 (query side):
- 命令端处理创建,更新和删除请求
- 查询端通过使用物化视图来处理查询部分
通常 事件溯源模式(event sourcing pattern)和它一起用来为任何数据更改创建事件。通过订阅事件流,可以使物化视图保持不断的更新
事件溯源模式(event sourcing pattern)
大多数应用程序都使用数据,一个典型的途径就是应用保持当前的状态。例如,传统的创建,读取,更新和删除(CRUD)中,典型的数据处理是从存储中读取数据,它包含经常使用事务锁定数据的限制。
事件溯源模式定义了一系列事件驱动的数据的处理操作,每一个事件处理操作都会记录在仅追加存储中(append-only store)。应用程序代码发送一系列 命令式的描述了数据上发生的动作的事件到事件持久化存储的地方。每个事件代表一组数据更改(例如,AddedItemToOrder)
这些事件持久化存储在充当系统记录系统的事件存储中。
观察者模式(Observability Patterns)
日志聚合
考虑这样一种情况:一个应用包含多个微服务实例,每个请求经常在横跨多个微服务实例,那么每一个微服务实例都产生一个表转化格式的日志文件。 因此我们需要一个中心化的日志服务来将每个服务实例的日志收集起来。用户可以搜索分析并分析日志,并且配置一些当日志中出现特定信息的报警规则。例如:PCF确实有一个日志聚合器(Log aggregator), 用来收集PCF平台上各个应用的各个组件(router, controller, Diego, 等等…)的日志。AWS Cloud Watch也这样做。
性能指标
因为微服务架构导致服务的数量增加时,密切注意事务变得十分关键,以便监控微服务模式并且在问题发生的时候发出警告。
一个指标服务用来收集每个单独操作的统计信息。它应该聚合一个应用服务的所有指标,以便提供报告和警报。 聚合指标应该包含两个模块:
- 推送 — 服务推送指标给指标服务 例如:NewRelic, AppDynamics
- 拉取 — 指标服务可以从每个服务中拉取指标 例如:Prometheus
分布式跟踪
在微服务架构中,请求通常跨越多个微服务。 每个服务通过跨多个服务执行一个或多个操作来处理一个请求。 在进行故障排除时,有一个跟踪ID是非常值得的,这样我们可以端对端的跟踪请求。
解决方案是引入一个事务ID,可以使用以下方法:
- 为每个外部请求分配唯一的外部请求ID
- 将外部请求ID传递给所有服务
- 在所有日志消息中包括外部请求ID
健康检查
实施微服务架构后,有一种可能是:服务可能会启动但无法处理事务。每个服务都需要具有一个端点用来检查应用的健康程度,例如health。这个API应该检查主机的状态, 与其他服务/基础结构的连接以及其他任意特定的逻辑。
交叉关注模式(Cross-Cutting Concern Patterns)
外部配置(External Configuration)
一个典型的服务通常还会调用其他服务和数据库,对于每一个环境,例如dev, QA, UAT, prod,这些环境的端点URL或某些配置属性可能不同,这些属性中的任何一项更改都可能需要重新构建或重新部署服务。
为了避免代码修改,我们可以使用配置,将所有的配置信息都外部化,包括端点URL和认证信息。应用程序应该在启动时或运行时加载这些配置。这些配置可以在应用启动的时候访问到,或者这些配置在不需要重启服务的情况下可以更新。
服务发现模式
当遇见如图所示的微服务架构时,在微服务调用方面我们需要关注一些问题。
使用容器技术,IP地址可以动态分配给每个微服务实例, 每次地址更改时,消费者服务的调用都会中断,需要手动更改才能恢复。
消费者必须记住每个服务URL,并使其紧密耦合。
因此需要创建服务注册,该服务注册将保存每个生产型服务的元数据和每个服务的说明规范。服务实例在启动时应注册到注册中心,而在实例关闭时应注销。服务发现有两种类型:
- 客户端,例如:Netflix Eureka
- 服务端:例如: AWS ALB
断路器模式
一个服务通常会调用其他服务来查询数据,有一种可能性是下游的服务会关闭,这将会带来两个问题:第一个是:上游服务继续请求关闭的网络服务,直到耗尽网络资源,并且降低系统性能。第二个是:用户体验将是糟糕的且不可预测的。
消费者应通过代理来调用远程服务,该代理的行为类似于电路中的断路器。当连续的请求失败的次数超过阈值时,断路器将跳闸一段时间,并且在跳闸的这段时间内,所有的调用远程服务的尝试都将立即失败。当超过了断路器跳闸时间之后,断路器将允许有限数量的测试请求通过。如果这些请求成功,则断路器将恢复正常操作。否则,如果有一个请求失败,则断路器再次跳闸。对于 一个应用试图尝试调用一个远程服务或者获取共享资源,并且该操作很容易的失败的情况来说, 这个模式非常适用。
Circuit Breaker Pattern.png
蓝绿发布模式
在微服务架构中,一个应用程序可以具有许多微服务。 如果我们在停止所有服务之后然后部署增强版本,则停机时间将是巨大的,并且可能影响业务。同样,回滚将是一场噩梦。蓝绿发布模式可以避免这种情况。
实施蓝绿发布模式可以减少或消除停机时间,它通过运行两个相同的生产环境:Blue和Green,来实现这一目标。这里我们假设绿色是已存在的工作实例,蓝色是该应用程序的新版本。在任何时候,只有一个环境处于活动状态,该活动环境为所有生产流量提供服务。 所有云平台均提供用于实施蓝绿色部署的选项。
Blue-Green Deployment Pattern.png
网友评论