在Web开发中调试微服务时的痛苦和痛苦

在IT中,您很少会见到从未听说过微服务的人。在Internet上以及与此主题相关的专门站点上,有很多文章通常可以很好地解释整体服务与实际上微服务之间的差异。一位经验不足的Java开发人员阅读了“什么是Web应用程序的微服务以及它们所吃的东西”类别的文章,他充满了喜悦和信心,因为现在一切都会变得美好。毕竟,主要目标是“锯穿”巨大的整体(通常是战争/耳朵文件的最终人工制品),该整体在一系列单独的生活服务上执行大量工作,每个服务都将执行仅与之相关的严格定义的功能,并会做好。除此之外,还有水平可伸缩性-只需扩展即可相应的节点,一切都会很棒。已有更多用户到达或需要更多容量-仅添加了5-10个新服务实例。大致来说,这就是它的工作原理,但是,正如您所知,魔鬼在细节上,而经过仔细研究,最初看起来很简单的问题可能会变成最初没有人考虑的问题。

在这篇文章中,Rexoft的Java实践的同事分享了他们如何调试Web微服务的经验。



如何实现交易数据的完整性


在尝试将架构从整体服务转移到微服务时,以前没有这种经验的团队通常会开始将服务拆分为域模型的顶级对象,例如:用户/客户/员工等。在将来,通过更详细的研究,人们会发现,这样更方便地分解成更大的块,这些块将域内的多个对象聚合在一起。因此,您可以避免不必要地调用第三方服务。

第二个重点是对事务数据完整性的支持。在整体中,此问题是通过应用程序服务器解决的,在该服务器中,争吵不休,容器实际上在其中概述了事务的边界。在微服务的情况下,事务的边界是模糊的,除了编写业务逻辑代码外,还需要能够管理数据完整性,维持系统不同部分之间的一致性。这是一项非常重要的任务。解决此类体系结构问题的建议可以在Internet和相关技术社区中找到。

在本文中,我们将尝试描述团队尝试使用微服务时出现的特定技术难题,以及解决这些难题的方法。我立即注意到,提出的选择并不是唯一的选择。也许会有更多优雅的服务,但是我将给出的建议在实践中经过了检验,可以准确地解决现有的困难,以及是否使用它们是每个人的个人问题。

与微服务的主要问题是,他们是非常容易在本地运行(例如,使用spring.ioIntelliJ IDEA的,这可以在短短的5分钟内完成,甚至更少)。但是,当尝试在Kubernetes中执行相同操作时集群(如果您以前没有使用过该集群的经验),则在访问特定端点时简单启动控制器并打印“ Hello World”可能需要半天。在整体式的情况下,情况更简单。每个开发人员都有一个本地应用程序服务器。部署过程也非常简单-您需要手动或使用IDE将最终的War / ear工件复制到Application Server中的正确位置通常这不是问题。

调试细节


第二个重点是调试在具有整体的情况下,假定开发人员在其机器上有一个Application Server,并在其中部署了他的War / Ear。您始终可以进行调试,因为所需的一切都在手边。使用微服务,一切都会变得有些复杂,服务通常本身就是一件事。通常,他拥有自己的数据库方案(数据位于其中),执行特定于他的特定功能,与其他服务的所有通信都通过同步HTTP调用(例如通过RestTemplate或Feign),异步(例如Kafka或RabbitMQ)进行组织。因此,通常在微服务方法中,保存或验证以前在单个war / ear文件中某个位置实现的某个对象的基本简单任务通常以微服务的方式表示:转到一个或N个相邻服务,无论是数据获取操作例如某些参考值或保存相邻实体的操作,在我们的服务中执行业务逻辑所需的数据。在这种情况下,编写业务逻辑变得更加困难。

因此,解决方案选项如下

  1. 编写您的业务逻辑代码。与此同时,所有的外部调用嘲笑 -外部合同仿真,测试,写为假设的一部分,外部合同是正义的,那么有一个部署进行验证电路。有时很幸运,并且集成马上就可以进行,有时很不幸运-您必须重做业务逻辑代码第n次,因为在我们实现功能期间,相邻服务中的代码已更新,API签名已更改,我们需要重做任务的一部分在一边。
  2. . , , Kubernetes, . . , — , remote debug . , runtime , , . -, , 2–5 , . . , Kubernetes , . -, (Per thread), , .

Kubernetes


实际上,解决此问题的方法就是网可能还有其他类似的程序,但是只有他个人经历,他才能建立自己的积极基础。通常,操作原理如下:

在本地计算机上,开发人员安装远程呈现,配置kubectl以访问相应的Kubernetes群集(将循环配置添加到〜/ .kube / config)。之后,网开始,它实际上是本地开发人员计算机和Kubernetes之间的代理有不同的启动选项,最好在官方指南中更详细地查看,但在最基本的情况下,它可以分为两个步骤:

  1. Sudo telepresence (, Linux- , sudo . , root/). Kubernetes deployment telepresence . deployment Kubernetes.
  2. 通常,在开发人员的本地计算机上启动服务实例。但是,在这种情况下,他将可以访问Kubernetes集群的整个基础结构,包括服务发现(Eureka,领事),Api网关(Zuul),Kafka及其队列(如果有)等等。也就是说,实际上,我们需要的所有集群环境都可以使用,但可以在本地使用。好处是可以进行本地调试,但是在集群环境中,它已经快得多了,因为实际上,我们位于Kubernetes内部(通过隧道),并且不通过端口从外部访问它进行远程调试。

此解决方案有几个缺点:

  1. Telepresence Linux Mac, Windows VFS, , issue GitHub. . , - Linux/Mac, .
  2. , Service Discovery (Eureka, Consul)Round Robin , endpoint , , , :

  • kubernetes -> . telepresence deployment , «» Eureka ip-address:port/service-name dns-name:port/service-name , . . Kubernetes , timeout;
  • deployment - Kubernetes , ( ) (Round Robin), ;
  • endpoint, feature, HTTP 404 endpoint Gateway, Service Discovery , Round Robin . Service Discovery endpoint , HTTP 404.
  • , , .


动态查询路由意味着网关API(Zuul)能够在我们需要的同一服务的多个实例中进行选择。在一般情况下,可以通过添加谓词来解决此问题,该谓词允许您在请求处理阶段从具有相同名称的公共服务池中选择所需的服务。自然地,我们希望能够动态路由的服务中的每个服务都必须具有某种包含数据的元信息,该数据将用于确定是否需要此服务。例如,Spring Cloud(在Eureka的情况下)允许您通过在application.yml中的特殊元数据块中指定来执行此操作

eureka:
  instance:
    preferIpAddress: true
    metadata-map:
      service.label: develop

在服务发现中的com.netflix.appinfo.InstanceInfo#getMetadata中注册了这样的服务后,将有一个带有键service.label的标签和值develop,可以在运行时获取它。服务开始时的重要一点是检查服务实例是否存在具有此类元信息的服务发现中,以避免潜在的冲突。

路由选项


之后,可以将问题的解决方案简化为两个选项:

  1. API Gateway . , , , , Headers: DestionationService: feature/PRJ-001. , , Header . , — - API Gateway.
  2. API Gateway, , . ., , , Zuul 1 endpoint- /api/users/… user, feature/PRJ-001, Zuul 2 endpoint- /api/users/… user, feature/PRJ-002. , N API Gateway N , . . , . . feature — , , , , , , . API Gateway, , . ., , , — , .






作为Gateway API的一部分,还值得提供一种机制,使您能够在运行时更改路由规则。最好将这些设置放在config-map中。在这种情况下,只需重写新路由并在Kubernetes中重新启动Gateway API来更新路由,或者使用Spring Boot Actuator(假设Gateway API中存在相应的依赖关系)就足够了-调用端点/刷新,这实际上是重新读取的配置地图中的数据,并将更新路由。

重要的一点是,相对而言,应该有一个服务的参考实例(例如,标记为develop,它将从服务开发的主分支中收集)和单独的主Gateway API(将始终在访问该服务的设置中指定)。本质上,我们为自己提供了一个独立的暂存环境,该暂存环境将始终在动态路由的上下文中运行。包含路由设置的Gateway API

配置映射的示例(此处仅是其外观的示例,为了正确操作,它需要在API Gateway服务的后端侧以代码形式进行相应的绑定

{
  "kind": "ConfigMap",
  "apiVersion": "v1",
  "metadata": {
    ...
  },  
"data": {
    ...        
    "rules.meta.user": "develop",
    "rules.meta.client": "develop",
    "rules.meta.notification": "feature/PRJ-010",
    ...    
  }
}

rules.meta是包含服务路由规则的映射。
用户/客户端/通知 -在Eureka中注册服务的名称。

development / feature / PRJ-010-相应服务的application.yml中的服务标签,如果服务的实例不止一个,则将在Service Discovery中具有相同名称的所有可用服务中选择所需的服务。

结论


像这个世界上的一切一样,IT中的工具和解决方案也不是完美的。不要以为如果更改体系结构,所有问题都会立即消失。只有详细地沉浸于所使用的技术和您自己的经验中,您才能真正了解正在发生的事情。

我希望这些材料可以帮助您解决问题。有趣的任务,无错误出售!

All Articles