当前位置: 首页 > 科技观察

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

时间:2023-03-19 01:20:27 科技观察

【.com原稿】如今,微服务架构已经成为现代应用开发的首选。虽然它可以解决大部分的编程问题,但它并不是久经考验的“灵丹妙药”。在采用这种架构之前,我们应该提前了解可能出现的各种问题及其共性,并提前为这些问题准备可复用的解决方案。因此,在我们开始深入讨论微服务的不同设计模式之前,让我们先了解一下微服务架构的一些构建原则:难免会遇到一些挑战。让我们详细讨论可能出现的各种问题及其解决方案。分解模式按业务功能分解问题:微服务是松散耦合的服务,采用单一职责原则。虽然我们都知道在逻辑上应该将单个应用程序拆分成多个小块,但是在实践中,如何才能成功地将一个应用程序分解成若干个小服务呢?解决方案:有一个按业务功能分解的策略。这里的业务功能是指某个业务中能够产生价值的最小单元。一组给定业务的功能划分则取决于业务本身的类型。例如,保险公司的职能通常包括:销售、营销、承保、索赔处理、计费、合规等。每个业务职能都可以看作是面向业务的服务,而不是技术服务。按子域分解问题:按业务功能分解应用只是一个好的开始,接下来你可能会接触到所谓的“神类”,分解不好。这些类通常涉及多种服务。比如订单类,订单管理、订单接受、订单发货等服务都会用到,那么我们应该如何分解呢?解决方案:对于“神级”的问题,DDD(DomainDrivenDesign,领域驱动设计)可以派上用场。它使用子域和限界上下文的概念来解决它。DDD将企业的整个领域模型打散,创建多个子领域。每个子域都有一个模型,该模型的范围称为边界上下文。然后每个微服务都会围绕有界上下文进行开发。注意:识别子域不是一件容易的事。我们需要通过分析业务和组织结构并识别不同的专业领域来加强对企业的理解。Strangler模式问题:我们之前讨论的设计模式通常适用于那些“从头开始”的Greenfield应用程序的分解。但我们真正接触到的,占80%左右的是Brownfieldapplications,即:一些大型的单体应用程序(MonolithicApplications)。既然已经投入使用并运行起来,如果简单的按照上面的方法同时分解成一个个小块的服务,那将是一件很困难的事情。解决方案:这就是Strangler模式派上用场的地方。我们可以把扼杀模式想象成用刀从树上砍下藤蔓。此场景适用于重复调用的Web应用程序。单个服务可以分解为不同的域和单独的子服务,供每个URI(统一资源标识符)调用。它的设计理念是一次只处理一个域。这样,我们就可以在同一个URI空间内并行创建两个独立的应用程序。最终,在新的应用被重构之后,我们可以“砍掉”或者替换掉原来的应用,直到最后我们可以完全关闭原来的单体应用。集成模式API网关模式问题:当一个应用被分解成多个小的微服务时,我们需要注意以下几个方面。具体内容如下:如何通过调用多个微服务来抽象Producer(生产者)信息。在不同的渠道上(比如电脑桌面、移动设备和平板电脑),应用程序响应同一个后端服务需要不同的数据,比如:UI(用户界面)可能不同。不同的消费者可能需要来自可重用微服务的不同响应格式。谁来做数据转换或现场操作?如何处理不同类型的协议?特别是一些Producer微服务可能不支持的协议。解决方案:API网关将有助于解决上述微服务实施中涉及的问题。具体如下:API网关是任何微服务调用的统一入口。像代理服务一样,它可以将微服务请求路由到其相关的微服务,并抽象出生产者的细节。它不仅可以将一个请求(fanout,output)扇出到多个服务,还可以聚合多个结果返回给Consumer。由于通用API无法处理所有消费者请求,因此该解决方案为每种特定类型的客户端创建了一个细粒度的API。它还可以将某种协议请求(如:AMQP)转换为另一种协议(如:HTTP),反之亦然,方便了Producer和Consumer的处理。它还从微服务中卸载身份验证和授权存储库。聚合器模式问题:虽然我们已经讨论了如何解决API网关模式中聚合数据的问题,但我们将进一步讨论它。当我们将业务功能分解成多个更小的逻辑代码块时,需要思考各个服务的返回数据如何配合。显然,这个责任不会交给Consumer,那么我们就需要了解Producer应用程序的内部实现。解决方案:聚合器模式将有助于解决这个问题。它涉及如何聚合来自不同服务的数据,然后将最终响应发送给Consumer。具体来说,我们有以下两种实现方式:复合微服务(CompositeMicroservice)会调用所有需要的微服务,整合各种数据,对数据进行转换后再传回。API网关(APIGateway)还可以将来自多个微服务的请求进行Partition(分区),聚合数据后再发送给Consumer。我们建议:如果使用任何业务逻辑,请选择复合微服务;否则,请使用API网关解决方案。客户端UI组合模式问题:各种服务按照业务功能和子域进行分解开发时,需要根据用户体验的预期效果,从一些不同的微服务中提取数据。在以往的单体应用中,我们只需要一次调用就可以将UI中的所有数据获取到后端服务,刷新提交到UI页面即可。今天,情况不同了。解决方案:对于微服务,UI一定要设计成单屏单页多版块多区结构。每个段将调用单独的后端微服务来提取数据。AngularJS和ReactJS等框架能够为特定服务组合UI组件。被称为单页应用程序(SPA),它们使应用程序能够仅刷新屏幕的特定区域,而不是整个页面。DatabaseSchemaDistributingDatabaseQuestionsbyService:您可能有一个关于如何定义数据库模式的微服务问题。以下是具体问题:服务必须松散耦合,以便它们可以独立重新开发、部署和扩展。单个业务交易需要在多个服务中保持相同。某些业务交易需要从多个服务中查询数据。数据库有时需要根据规模需求进行复制和分片。不同的服务有不同的数据存储需求。解决方案:为了解决上述需求,我们需要在设计上为每个微服务配备专属的数据库schema。即:数据库只能通过其对应微服务的API独立访问,不能被其他服务直接访问。例如,对于关系数据库,我们可以使用:按服务分配私有表集(private-tables-per-service),按服务分配表结构(schema-per-service),或按服务分配数据库服务器(database-服务器-每服务)。每个微服务都应该有一个单独的数据库ID,以便它们具有独占访问权限,同时不允许访问其他服务表集。按服务共享数据库问题:上面讨论的按服务分布式数据库是一种理想的微服务模型,前面提到的Greenfield应用和DDD风格的开发普遍采用这种模型。但是,如果我们处理的是需要微服务的单体应用程序,那就没那么容易了。解决方案:虽然服务共享数据库的模式有些违背微服务的理念,但更适合将前面提到的Brownfield应用(不是新应用)分解成更小的逻辑块。这种模式下,一个数据库可以匹配多个微服务,当然最多2到3个,否则会影响扩展性、自治性和独立性。CommandQueryResponsibilitySegregation(CQRS)问题:对于按服务分配数据库的模式,如何在微服务架构中实现多个服务联合查询数据的需求?解决方案:CQRS建议拆解应用分为两部分:命令和查询。命令部分主要处理创建、更新、删除等请求;查询部分使用物化视图(MaterializedViews)来处理各种查询。它通常与事件溯源模式结合使用,为任何数据创建更改事件。实体化视图通过订阅事件流来保持更新。Saga模式问题:当每个服务都有自己的数据库,业务交易跨越多个服务时,我们如何保证整体业务数据的一致性?例如:对于有客户信用额度标志的电子商务公司,就应用而言,需要保证新的订单不超过客户的信用额度。但是,由于订单和客户属于不同的数据库,应用程序不能简单地实现本地事务的ACID(原子性、一致性、隔离性、持久性)特性。解决方案:Saga代表一个高层的业务流程,由一个服务中的多个子请求组成,伴随着一个一个更新的数据。当请求失败时,将执行其补偿请求。有两种方法可以实现这一点:编排:没有中央协调器,每个服务生成并侦听来自其他服务的事件,以决定是否应采取行动。Orchestrator:一个中央协调器(对象),负责集中事件(Saga)的决策和业务逻辑的排序。观察模式日志聚合问题:让我们考虑这样一个用例:一个应用程序包括运行在多台机器上的多个服务实例,各种请求跨越这多个服务实例。同时,每个服务实例都会生成一个标准格式的日志文件。那么我们如何通过针对特定请求的各种日志来了解应用程序的行为呢?解决方案:很明显,我们需要一个集中式的日志服务,将各个服务实例的日志聚合起来,方便用户根据日志进行搜索和分析。他们可以为可能出现在日志中的某些消息配置警告。例如:PCF(PivotalCloudFoundry)平台有一个日志聚合器,它从每个元素(例如:路由器、控制器等)收集与应用程序相关的日志。AWSCloudWatch也有类似的功能。性能指标问题:当各种服务组合随着微服务架构变得越来越复杂时,监控事务的完整性并在出现问题时能够及时发出警告就显得尤为重要。那么我们如何收集与应用程序相关的性能指标呢?解决方案:为了对不同的操作进行统计,并提供相应的报告和警告。我们一般采用两种模式聚合指标:推送模式:将指标推送到专门的指标服务,比如NewRelic和AppDynamics。Pull:从指标服务中拉取各种指标,如:Prometheus。分布式追踪问题:在微服务架构中,请求跨越多个服务是很常见的。一个服务需要跨多个服务执行一个或多个操作,才能处理一些特定的请求。那么,我们如何跟踪端到端请求以了解问题所在?解决方案:我们需要一个有特点的服务。具体的特征服务如下:为每个外部请求分配一个唯一的ID。将此外部请求ID传递给所有服务。在所有日志消息中包含外部请求ID。在集中服务中,记录处理外部请求的相关信息,包括:开始时间、结束时间、执行时间。SpringCloudSlueth+ZipkinServer是一种常见的实现方式。健康检查问题:在实现微服务架构的过程中,我们可能会遇到服务已经启动却无法处理事务的情况。那么,我们如何使用负载均衡模式来保证请求不会“掉”到故障实例中呢?解决方案:每个服务都需要有一个端点,通过/health等参数,对应用进行健康检查。API需要能够检查主机的状态、其他服务与基础设施的连接以及任何特定的逻辑关系。SpringBootActuator不仅可以实现端点的健康检查,还可以自定义。横切关注点模式(Cross-CuttingConcernPatterns)外部配置问题:通常,一个服务需要调用其他服务和数据库。在开发、QA(QualityAssurance,质量保证)、UAT(UserAcceptanceTest,用户验收测试)和生产环境中,端点的URL,或者一些配置属性会有所不同。因此,有时我们需要重构和重新部署这些服务的各种属性。那么我们如何避免在更改配置时修改代码呢?解决方案:外部化所有配置,包括每个端点的URL和信任凭证,以确保应用程序可以在启动或运行时加载它们。SpringCloud配置服务器提供了将属性外部化到GitHub并将它们作为环境属性加载的选项。此方法确保可以在启动时访问应用程序,或者在不重新启动服务器的情况下刷新应用程序。服务发现模式问题:当微服务初具规模时,我们需要考虑以下两个关于调用服务的问题。具体问题如下:由于采用容器技术,IP地址往往会动态分配给不同的服务实例。因此,每次IP地址变化,都可能影响到Consumer服务,需要我们手动更改。消费者需要记住每个服务的URL,这就回归到紧耦合状态。那么,消费者或路由器如何知道所有可用的服务实例和位置?解决方案:我们需要创建一个服务注册中心来保存每个Producer服务的元数据(MetaData)。服务实例在启动时应注册到表中,并在关闭时从表中注销。消费者或路由器可以通过查询注册表找出服务的位置。Producer服务还需要对注册表执行健康检查,以确保它可以使用可用的和正在运行的服务实例。我们通常有两种类型的服务发现:客户端和服务器端。使用客户端发现的一个例子是NetflixEureka;使用服务器端发现的一个例子是AWSALB。断路器模式问题:有时,当一个服务调用其他服务获取数据时,下游服务(DownstreamService)会被“掉线”。它通常有两种结果:请求继续发送到被丢弃的服务,直到网络资源耗尽和性能下降。用户不可预测,用户体验差。那么我们如何避免级联服务故障并妥善处理呢?解决方案:消费者应该通过代理来调用远程服务,就像电路中的断路器一样。当持续失败的次数超过设定的阈值时,断路器会“跳闸”一段时间,导致所有调用远程服务的尝试立即被切断。在设定的时间过去后,断路器仅允许有限数量的测试请求通过。而如果这些请求成功,那么断路器将恢复正常运行;否则,判断故障依旧,重新开始一个新的计时周期。NetflixHystrix很好地使用了这种断路器模式。它可以帮助您定义回退机制,以防断路器“跳闸”以提供更好的用户体验。蓝绿部署模式问题:在微服务架构中,一个应用程序可以有多个微服务。如果我们为了部署增强版而停止所有的服务,停机时间过长会影响业务。此外,回滚将是一场噩梦。那么我们如何避免或减少部署期间的服务停机时间呢?解决方案:我们可以采用蓝绿部署策略来减少或消除停机时间。在蓝色和绿色两个相同的生产环境中,我们假设绿色环境有当前的真实实例,蓝色环境有最新版本的应用程序。在任何时候,只有一个环境可以处理所有真实流量并提供外部服务。今天,所有的云服务平台都提供基于蓝绿部署的选项。当然,我们也可以采用很多其他的微服务架构模式,比如:Sidecar模式、ChainedMicroservice、BranchMicroservice、EventSourcingPattern、持续交付方式等等。【原创稿件,合作网站转载请注明原作者和出处为.com】