如何借助Quarkus和MicroProfile实现微服务

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"为何需要微服务特性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在微服务架构中,应用程序是由多个相互连接的服务组成的,这些服务协同工作以实现所需的业务功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,一个典型的企业级微服务架构如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-1-typical-enterprise-microservices-architecture-1620661728483.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最初,我们可能认为使用微服务架构实现一个应用程序是很容易的事情。但是,要恰当地完成这一点并不容易,因为我们会面临一些新的挑战,而这些挑战是单体架构所未曾遇到的。举例来讲,这样的挑战包括容错、服务发现、扩展性、日志和跟踪等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了应对这些挑战,每个微服务都需要实现在Red Hat被称为“微服务特性(microservicility)”的内容。这个术语指的是除了业务逻辑之外,服务必须要实现的一个横切性关注点的列表,总结起来如下图所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-2-microservicilities-diagram-1620661728483.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"业务逻辑可以使用任何语言(Java、Go或JavaScript)或任何框架(Spring Boot、Quarkus)来实现,但是围绕着业务逻辑,我们应该实现如下的关注点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"API"},{"type":"text","text":":服务可以通过一组预先定义的API操作进行访问。例如,在采用RESTful Web API的情况下,会使用HTTP作为协议。此外,API还可以使用像"},{"type":"link","attrs":{"href":"https:\/\/swagger.io\/","title":"","type":null},"content":[{"type":"text","text":"Swagger"}]},{"type":"text","text":"这样的工具实现文档化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"发现(Discovery)"},{"type":"text","text":":服务需要能够发现其他的服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"调用(Invocation)"},{"type":"text","text":":在服务发现之后,需要使用一组参数来调用它,并且可能会返回一个响应。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"弹性(Elasticity)"},{"type":"text","text":":微服务架构很重要的特性之一就是每个服务都是有弹性的,这意味着它可以根据一些参数(比如系统的重要程度或当前的工作负载)独立地进行扩展和伸缩。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"回弹性(Resiliency)"},{"type":"text","text":":在微服务架构中,我们在开发时应该要考虑到故障,特别是与其他服务进行通信的时候。在单体架构中,应用会作为一个整体进行启动和关闭。但是,当我们把应用拆分成微服务架构之后,应用就变成由多个服务组成的,所有的服务会通过网络互相连接,这意味着应用的某些部分可能在正常运行,而其他部分可能已经出现了故障。在这种情况下,很重要的一点就是遏制故障,避免错误通过其他的服务进行传播。回弹性(或称为应用回弹性)是指一个应用\/服务能够对面临的问题作出反应的能力,在出现问题的时候,依然能够提供尽可能最好的结果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"管道(Pipeline)"},{"type":"text","text":":服务应该能够独立部署,不需要任何形式的部署编排。基于这一点,每个服务应该有自己的部署管道。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"认证(Authentication)"},{"type":"text","text":":在微服务架构中,涉及到安全性时,很重要的一个方面就是如何认证\/授权内部服务之间的调用。Web token(以及通用的token)是在内部服务之间声明安全性的首选方式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"日志(Logging)"},{"type":"text","text":":在单体应用中,日志是很简单的事情,因为应用的所有组件都在同一个节点中运行。现在,组件以服务的形式分布在多个节点上,因此,为了全面了解日志跟踪的情况,我们需要一个统一的日志系统\/数据收集器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"监控(Monitoring)"},{"type":"text","text":":要保证基于微服务的应用正确运行,很重要的一个方面就是衡量系统的运行情况、理解应用的整体健康状况并在出现问题的时候发出告警。监控是控制应用程序的重要方面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"跟踪(Tracing)"},{"type":"text","text":":跟踪用来可视化一个程序的流程和数据进展。当我们需要检查用户在整个应用中的操作时,它对开发人员或运维人员尤其有用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Kubernetes正在成为部署微服务的事实标准工具。它是一个开源的系统,用来自动化、编排、扩展和管理容器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是在我们提到的十个微服务特性中,通过使用Kubernetes只能覆盖其中的三个。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-3-ten-microservicilities-covered-when-using-Kubernetes-1620661728483.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"发现(Discovery)"},{"type":"text","text":" 是通过_Kubernetes Service_理念实现的。它提供了一种将_Kubernetes Pod_(作为一个整体)进行分组的方式,使其具有稳定的虚拟IP和DNS名。要发现一个服务只需要在发送请求的时候使用Kubernetes的服务名作为主机名即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Kubernetes "},{"type":"text","marks":[{"type":"strong"}],"text":"调用(Invocation)"},{"type":"text","text":" 服务是非常容易的,因为平台本身提供了所需的网络来调用任意的服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"弹性(Elasticity)"},{"type":"text","text":" (或者说扩展性)是Kubernetes从一开始就考虑到的问题,例如,如果运行"},{"type":"codeinline","content":[{"type":"text","text":"kubectl scale deployment myservice --replicas=5"}]},{"type":"text","text":"命令的话,"},{"type":"text","marks":[{"type":"italic"}],"text":"myservice"},{"type":"text","text":" deployment就会扩展至五个副本或实例。Kubernetes平台会负责寻找合适的节点、部署服务并维持所需数量的副本一直处于运行状态。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,剩余的微服务特性该怎么处理呢?Kubernetes只涵盖了其中的三个,那么我们该如何实现剩余的哪些呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据所使用的语言或框架,我们有很多可遵循的策略,但是在本文中,我们会看到如何使用"},{"type":"link","attrs":{"href":"https:\/\/quarkus.io\/","title":"","type":null},"content":[{"type":"text","text":"Quarkus"}]},{"type":"text","text":"来实现其中某些微服务特性。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"什么是Quarkus?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/quarkus.io\/","title":"","type":null},"content":[{"type":"text","text":"Quarkus"}]},{"type":"text","text":"是一个全栈、Kubernetes原生的Java框架,适用于Java虚拟机(JVM)和原生编译环境,针对容器环境对Java的进行了专门的优化,使其成为一个可用于无服务器、云和Kubernetes环境的高效平台。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Quarkus没有重复发明轮子,而是使用了由标准\/规范支撑的知名企业级框架,并使它们可以借助"},{"type":"link","attrs":{"href":"https:\/\/www.graalvm.org\/","title":"","type":null},"content":[{"type":"text","text":"GraalVM"}]},{"type":"text","text":"编译成二进制文件。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"什么是MicroProfile?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Quarkus集成了"},{"type":"link","attrs":{"href":"https:\/\/microprofile.io\/","title":"","type":null},"content":[{"type":"text","text":"MicroProfile"}]},{"type":"text","text":"规范,将企业级Java生态系统转移到了微服务架构中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在下图中,我们可以看到构成MicroProfile规范的所有API。其中有些API是基于"},{"type":"link","attrs":{"href":"https:\/\/jakarta.ee\/","title":"","type":null},"content":[{"type":"text","text":"Jakarta EE"}]},{"type":"text","text":"(也就是以前的Java EE)规范的,比如CDI、JSON-P和JAX-RS,其他的则是由Java社区开发的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-4-diagram-APIs-that-comprise-the-MicroProfile-specification-1620661728483.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来,我们就使用Quarkus来实现API、调用、回弹性、认证、日志、监控和跟踪等微服务特性。"}]},{"type":"heading","attrs":{"align":null,"level":2}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何使用Quarkus实现微服务特性"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"起步"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开始使用Quarkus的最快捷方式就是通过"},{"type":"link","attrs":{"href":"https:\/\/code.quarkus.io\/","title":"","type":null},"content":[{"type":"text","text":"起始页面"}]},{"type":"text","text":",在这里我们可以添加所需的依赖。就本例来讲,我们要注册如下的依赖以满足微服务特性的需求:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"API:RESTEasy JAX-RS、RESTEasy JSON-B和OpenAPI"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"调用:REST Client JSON-B"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回弹性:Fault Tolerance"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"认证:JWT"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"日志:GELF"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"监控:Micrometer metrics"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"跟踪:OpenTracing"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-5-Quarkus-start-page-1620661728483.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以手动选择这些依赖,也可以导航至如下的链接"},{"type":"link","attrs":{"href":"https:\/\/code.quarkus.io\/?a=microservicilities-quarkus&e=resteasy&e=resteasy-jsonb&e=rest-client-jsonb&e=smallrye-jwt&e=smallrye-openapi&e=logging-gelf&e=smallrye-fault-tolerance&e=micrometer&e=smallrye-opentracing","title":"","type":null},"content":[{"type":"text","text":"Microservicilities Quarkus Generator"}]},{"type":"text","text":",在这里所有的依赖都已经选择好了。然后点击“"},{"type":"text","marks":[{"type":"italic"}],"text":"Generate your application"},{"type":"text","text":"”按钮以下载包含脚手架应用的压缩文件。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"服务"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本例中,我们会创建一个非常简单的应用,它只包含两个服务。其中一个服务名为_Rating service_,它会返回给定一本书的评分,另外一个服务名为_Book service_,它会返回某本书的信息及其评分。服务之间的所有调用必须要进行认证。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在下图中,我们可以看到完整系统的概览:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-6-overview-complete-system-1620661728483.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"_Rating service_已经开发完成并且能够以Linux容器的形式供我们使用。我们可以使用如下的命令在9090端口启动该服务:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"docker run --rm -ti -p 9090:8080 \nquay.io\/lordofthejars\/rating-service:1.0.0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了校验该服务,我们可以发送请求到"},{"type":"link","attrs":{"href":"http:\/\/localhost:9090\/rate\/1","title":"","type":null},"content":[{"type":"text","text":"http:\/\/localhost:9090\/rate\/1"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"curl localhost:8080\/rate\/1 -vv\n\n> GET \/rate\/1 HTTP\/1.1\n> Host: localhost:8080\n> User-Agent: curl\/7.64.1\n> Accept: *\/*\n>\n< HTTP\/1.1 401 Unauthorized\n< www-authenticate: Bearer {token}\n< Content-Length: 0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里的状态码是"},{"type":"codeinline","content":[{"type":"text","text":"401 Unauthorized"}]},{"type":"text","text":",这是因为我们没有在请求中以bearer token(JWT)的形式提供认证信息。带有_group_ "},{"type":"codeinline","content":[{"type":"text","text":"Echoer"}]},{"type":"text","text":"的合法token才能访问_rating service_。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"API"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Quarkus使用大家熟知的JAX-RS规范来定义RESTful web API。在底层,Quarkus使用了RESTEasy实现,直接与Vert.X框架协作,而不是使用Servlet相关的技术。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在我们为book service定义API,实现最常见的操作:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"import javax.ws.rs.Consumes;\nimport javax.ws.rs.DELETE;\nimport javax.ws.rs.GET;\nimport javax.ws.rs.POST;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.PathParam;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.QueryParam;\nimport javax.ws.rs.core.MediaType;\nimport javax.ws.rs.core.Response;\nimport javax.ws.rs.core.UriBuilder;\n\n@Path(\"\/book\")\npublic class BookResource {\n\n @GET\n @Path(\"\/{bookId}\")\n @Produces(MediaType.APPLICATION_JSON)\n public Book book(@PathParam(\"bookId\") Long bookId) {\n \/\/ logic\n }\n\n @POST\n @Consumes(MediaType.APPLICATION_JSON)\n public Response getBook(Book book) {\n \/\/ logic\n\n return Response.created(\n UriBuilder.fromResource(BookResource.class)\n .path(Long.toString(book.bookId))\n .build())\n .build();\n }\n\n @DELETE\n @Path(\"\/{bookId}\")\n public Response delete(@PathParam(\"bookId\") Long bookId) {\n \/\/ logic\n\n return Response.noContent().build();\n }\n\n @GET\n @Produces(MediaType.APPLICATION_JSON)\n @Path(\"search\")\n public Response searchBook(@QueryParam(\"description\") String description) { \n \/\/ logic\n\n return Response.ok(books).build();\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们要注意的第一件事情就是这里定义了四个不同的端点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"GET \/book\/{bookId}"}]},{"type":"text","text":"使用GET HTTP方法来返回图书的信息,其中包括它的评分。返回元素会自动反编组(unmarshal)为JSON。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"POST \/book"}]},{"type":"text","text":"使用POST HTTP方法插入来自请求体内容的一本图书。请求体的内容会自动从JSON编组(marshal)为Java对象。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"DELETE \/book\/{bookId}"}]},{"type":"text","text":"使用DELETE HTTP方法以根据ID删除某本图书。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"GET \/book\/search?description={description}"}]},{"type":"text","text":"根据描述搜索图书。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要注意的第二件事就是返回的类型,有时候我们返回的是一个Java对象,有时候返回的是"},{"type":"codeinline","content":[{"type":"text","text":"javax.ws.rs.core.Response"}]},{"type":"text","text":"的实例。当使用Java对象的时候,我们会将Java编组为"},{"type":"codeinline","content":[{"type":"text","text":"@Produces"}]},{"type":"text","text":"注解所设置的媒体类型。具体到本服务中,输出是JSON文档。如果使用"},{"type":"codeinline","content":[{"type":"text","text":"Response"}]},{"type":"text","text":"对象的话,对于返回什么内容给调用者,我们会有更细粒度的控制,例如,我们可以设置返回给调用者的HTTP状态码、头信息或内容。至于该优选选择哪种方式,这取决于具体的使用场景。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"调用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定义完访问_book service_的API之后,我们就该开发调用_rating service_服务以获取图书评分信息的代码了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Quarkus使用"},{"type":"link","attrs":{"href":"https:\/\/github.com\/eclipse\/microprofile-rest-client","title":"","type":null},"content":[{"type":"text","text":"MicroProfile Rest Client"}]},{"type":"text","text":"规范来访问外部的(HTTP)服务。它提供了一种类型安全的方式借助HTTP协议访问RESTful服务,在这个过程中,它会使用JAX-RS 2.0的一些API以实现一致性和更简单的重用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们要创建的第一个元素是代表远程服务的接口,它会用到JAX-RS的注解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"import javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.PathParam;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.core.MediaType;\n\nimport org.eclipse.microprofile.rest.client.inject.RegisterRestClient;\n\n@Path(\"\/rate\")\n@RegisterRestClient\npublic interface RatingService {\n\n @GET\n @Path(\"\/{bookId}\")\n @Produces(MediaType.APPLICATION_JSON)\n Rate getRate(@PathParam(\"bookId\") Long bookId);\n\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当"},{"type":"codeinline","content":[{"type":"text","text":"getRate()"}]},{"type":"text","text":"方法被调用的时候,会触发一个对"},{"type":"codeinline","content":[{"type":"text","text":"\/rate\/{bookId}"}]},{"type":"text","text":"的远程HTTP调用,在这个过程中"},{"type":"codeinline","content":[{"type":"text","text":"bookId"}]},{"type":"text","text":"会被替换为方法参数中的值。很重要的一点就是,要为接口添加"},{"type":"codeinline","content":[{"type":"text","text":"@RegisterRestClient"}]},{"type":"text","text":"注解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后,"},{"type":"codeinline","content":[{"type":"text","text":"RatingService"}]},{"type":"text","text":"接口需要注入到"},{"type":"codeinline","content":[{"type":"text","text":"BookResource"}]},{"type":"text","text":"中以执行远程调用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"import org.eclipse.microprofile.rest.client.inject.RestClient;\n\n@RestClient\nRatingService ratingService;\n\n@GET\n@Path(\"\/{bookId}\")\n@Produces(MediaType.APPLICATION_JSON)\npublic Book book(@PathParam(\"bookId\") Long bookId) {\n final Rate rate = ratingService.getRate(bookId);\n\n Book book = findBook(bookId);\n return book;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"@RestClient"}]},{"type":"text","text":"注解会注入对应接口的一个代理实例,从而提供了客户端的实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后需要配置的就是服务的位置(_hostname_部分)。在Quarkus中,配置属性是在"},{"type":"codeinline","content":[{"type":"text","text":"src\/main\/resources\/application.properties"}]},{"type":"text","text":"文件中设置的。要配置服务的位置,我们需要使用Rest Client接口的全限定名并结合URL作为键,然后使用实际的位置作为值:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"org.acme.RatingService\/mp-rest\/url=http:\/\/localhost:9090\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要想正确访问_rating_服务并摆脱"},{"type":"codeinline","content":[{"type":"text","text":"401 Unauthorized"}]},{"type":"text","text":"的问题,我们还需要解决相互认证的问题。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"认证"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基于token的认证机制允许系统基于一个安全token进行认证、授权和身份验证。Quarkus集成了"},{"type":"link","attrs":{"href":"https:\/\/github.com\/eclipse\/microprofile-jwt-auth","title":"","type":null},"content":[{"type":"text","text":"MicroProfile JWT RBAC Security"}]},{"type":"text","text":"规范,以使用JWT Bearer Token来保护服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要使用MicroProfile JWT RBAC Security来保护一个端点,我们只需要为方法添加"},{"type":"codeinline","content":[{"type":"text","text":"@RolesAllowed"}]},{"type":"text","text":"注解即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@GET\n@Path(\"\/{bookId}\")\n@RolesAllowed(\"Echoer\")\n@Produces(MediaType.APPLICATION_JSON)\npublic Book book(@PathParam(\"bookId\") Long bookId)\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"随后,我们还需要在"},{"type":"codeinline","content":[{"type":"text","text":"application.properties"}]},{"type":"text","text":"文件中配置token的issuer以及公钥文件的位置,以便于校验token的签名:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"mp.jwt.verify.publickey.location=https:\/\/raw.githubusercontent.com\/redhat-developer-demos\/quarkus-tutorial\/master\/jwt-token\/quarkus.jwt.pub\nmp.jwt.verify.issuer=https:\/\/quarkus.io\/using-jwt-rbac\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该扩展会执行如下的校验:token是合法的;issuer是正确的;token没有被修改过;签名是合法的;它还没有过期。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在,_book service_和_rating service_都使用相同的JWT issuer和秘钥进行保护,所以服务之间的通信需要用户进行认证,这是通过在"},{"type":"codeinline","content":[{"type":"text","text":"Authentication"}]},{"type":"text","text":"头信息中提供一个合法的bearer token实现的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"rating service_运行起来之后,我们就可以使用如下的命令启动_book service"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":".\/mvnw compile quarkus:dev\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,我们可以发送请求来获取图书信息并提供一个合法的JSON Web Token作为bearer token。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至于token如何生成超出了本文的范围,我们假设token已经能够生成:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-7-generation-of-the-token-1620662784968.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"curl -H \"Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTcwMDk0MTcxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MjIwMDgxNDE3MSwiaWF0IjoxNTcwMDk0MTcxLCJqdGkiOiJhLTEyMyJ9.Hzr41h3_uewy-g2B-sonOiBObtcpkgzqmF4bT3cO58v45AIOiegl7HIx7QgEZHRO4PdUtR34x9W23VJY7NJ545ucpCuKnEV1uRlspJyQevfI-mSRg1bHlMmdDt661-V3KmQES8WX2B2uqirykO5fCeCp3womboilzCq4VtxbmM2qgf6ag8rUNnTCLuCgEoulGwTn0F5lCrom-7dJOTryW1KI0qUWHMMwl4TX5cLmqJLgBzJapzc5_yEfgQZ9qXzvsT8zeOWSKKPLm7LFVt2YihkXa80lWcjewwt61rfQkpmqSzAHL0QIs7CsM9GfnoYc0j9po83-P3GJiBMMFmn-vg\" localhost:8080\/book\/1 -v\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"响应再次提示禁止访问的错误:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"< HTTP\/1.1 401 Unauthorized\n< Content-Length: 0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能会想在提供了合法的token之后,为何还会遇到这个错误。如果我们探查一下book service的控制台,就会看到如下的异常:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 401\n at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)\n at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出现这个异常的原因在于我们已经认证并授权访问_book service_,但是这个bearer token并没有传递到_rating service_中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-8-bearer-token-1620662784968.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了让"},{"type":"codeinline","content":[{"type":"text","text":"Authorization"}]},{"type":"text","text":"头信息能够从传入的请求自动传播至rest-client请求,我们需要进行两项修改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一项修改是更新Rest Client接口并为其添加"},{"type":"codeinline","content":[{"type":"text","text":"org.eclipse.microprofile.rest.client.inject.RegisterClientHeaders"}]},{"type":"text","text":"注解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@Path(\"\/rate\")\n@RegisterRestClient\n@RegisterClientHeaders\npublic interface RatingService {}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二项修改是配置哪些头信息要在请求之间进行传递,这是在"},{"type":"codeinline","content":[{"type":"text","text":"application.properties"}]},{"type":"text","text":"文件中进行配置:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"org.eclipse.microprofile.rest.client.propagateHeaders=Authorization\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们再次使用相同的curl,就会得到正确的输出了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"< HTTP\/1.1 200 OK\n< Content-Length: 39\n< Content-Type: application\/json\n\n io.quarkus;\n quarkus-micrometer-registry-prometheus\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Micrometer扩展默认会注册一些与系统、JVM或HTTP相关的指标。所收集到的指标的一个子集可以通过"},{"type":"codeinline","content":[{"type":"text","text":"\/q\/metrics"}]},{"type":"text","text":"端点进行访问,如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"curl localhost:8080\/q\/metrics\n\njvm_threads_states_threads{state=\"runnable\",} 22.0\njvm_threads_states_threads{state=\"blocked\",} 0.0\njvm_threads_states_threads{state=\"waiting\",} 10.0\nhttp_server_bytes_read_count 1.0\nhttp_server_bytes_read_sum 0.0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,我们还可以使用Micrometer API实现应用特定的指标。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这里,我们实现一个自定义的指标来衡量评分最高的图书。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要注册一个指标,也就是本例中的一个gauge,是通过使用"},{"type":"codeinline","content":[{"type":"text","text":"io.micrometer.core.instrument.MeterRegistry"}]},{"type":"text","text":"类来完成的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"private final MeterRegistry registry;\nprivate final LongAccumulator highestRating = new LongAccumulator(Long::max, 0);\n\npublic BookResource(MeterRegistry registry) {\n this.registry = registry;\n registry.gauge(\"book.rating.max\", this,\n BookResource::highestRatingBook);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们发送一些请求并校验gauge是否被正确地更新了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"curl -H \"Authorization: Bearer ...\" localhost:8080\/book\/1\n\n{\"bookId\":1,\"name\":\"Book 1\",\"rating\":3}\n\ncurl localhost:8080\/q\/metrics\n\n# HELP book_rating_max\n# TYPE book_rating_max gauge\nbook_rating_max 3.0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们还可以设置一个计时器,记录从rating service获取评分信息所耗费的时间。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Supplier rateSupplier = () -> {\n return ratingService.getRate(bookId);\n};\n\nfinal Rate rate = registry.timer(\"book.rating.test\").wrap(rateSupplier).get();\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们发送一些请求并校验是否收集了评分服务的耗时。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"# HELP book_rating_test_seconds\n# TYPE book_rating_test_seconds summary\nbook_rating_test_seconds_count 4.0\nbook_rating_test_seconds_sum 1.05489108\n# HELP book_rating_test_seconds_max\n# TYPE book_rating_test_seconds_max gauge\nbook_rating_test_seconds_max 1.018622001\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Micrometer使用"},{"type":"codeinline","content":[{"type":"text","text":"MeterFilter"}]},{"type":"text","text":"实例来自定义由"},{"type":"codeinline","content":[{"type":"text","text":"MeterRegistry"}]},{"type":"text","text":"实例所发出的指标。Micrometer扩展将会探测到"},{"type":"codeinline","content":[{"type":"text","text":"MeterFilter"}]},{"type":"text","text":" CDI bean并使用它们来初始化"},{"type":"codeinline","content":[{"type":"text","text":"MeterRegistry"}]},{"type":"text","text":"实例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,我们可以定义一个通用的标签来设置应用运行的环境(prod、testing、staging等)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@Singleton\npublic class MicrometerCustomConfiguration {\n\n @Produces\n @Singleton\n public MeterFilter configureAllRegistries() {\n return MeterFilter.commonTags(Arrays.asList(\n Tag.of(\"env\", \"prod\")));\n }\n\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"发送一个新的请求并校验指标是否添加了标签。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"http_client_requests_seconds_max{clientName=\"localhost\",env=\"prod\",method=\"GET\",outcome=\"SUCCESS\",status=\"200\",uri=\"\/rate\/2\",} 0.0\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"请注意,标签"},{"type":"codeinline","content":[{"type":"text","text":"env"}]},{"type":"text","text":"包含的值为"},{"type":"codeinline","content":[{"type":"text","text":"prod"}]},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"跟踪"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Quarkus应用使用"},{"type":"link","attrs":{"href":"https:\/\/opentracing.io\/","title":"","type":null},"content":[{"type":"text","text":"OpenTracing"}]},{"type":"text","text":"规范来为互相交互的Web应用提供分布式跟踪能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来,我们配置OpenTracing连接一个Jaeger服务器,并将服务的名字设置为book-service以标识跟踪信息:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"quarkus.jaeger.enabled=true\nquarkus.jaeger.endpoint=http:\/\/localhost:14268\/api\/traces\nquarkus.jaeger.service-name=book-service\nquarkus.jaeger.sampler-type=const\nquarkus.jaeger.sampler-param=1\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在,我们发送一个请求:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"curl -H \"Authorization: Bearer ...\" localhost:8080\/book\/1\n\n{\"bookId\":1,\"name\":\"Book 1\",\"rating\":3}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"访问Jaeger UI来校验调用过程被进行了跟踪:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/imgopt.infoq.com\/fit-in\/1200x2400\/filters:quality(80)\/filters:no_upscale()\/articles\/microservicilities-quarkus\/en\/resources\/1figure-10-Jaeger-UI-to-validate-that-the-call-is-traced-1620663754274.jpg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"结论"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开发和实现微服务架构要比开发单体应用更具挑战性。我们相信,微服务特性能够促使你在应用基础设施方面正确地开发服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们在这里所阐述的微服务特性(除API和管道之外)都是新的理念,或者说在单体应用中会以不同的方式来实现。其中的原因在于,现在应用被拆分成了多个组成部分,所有的这些组成部分需要在网络中进行相互连接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你打算开发微服务并将它们部署到Kubernetes的话,那么Quarkus是一个很好的解决方案,因为它可以很平滑地与Kubernetes进行集成,实现大多数的微服务特性都非常简单,只需要几行代码就能实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用来阐述本文的源码都可以在"},{"type":"link","attrs":{"href":"https:\/\/github.com\/lordofthejars\/microservicilities-quarkus","title":"","type":null},"content":[{"type":"text","text":"github"}]},{"type":"text","text":"上找到。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者简介:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Alex Soto"},{"type":"text","text":"是红帽公司的开发者体验总监。他对Java领域、软件自动化充满热情,他相信开源软件模式。Soto是"},{"type":"link","attrs":{"href":"https:\/\/www.manning.com\/books\/testing-java-microservices","title":"","type":null},"content":[{"type":"text","text":"Manning的《Testing Java Microservices》"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/www.oreilly.com\/library\/view\/quarkus-cookbook\/9781492062646\/","title":"","type":null},"content":[{"type":"text","text":"O’Reilly的《Quarkus Cookbook》"}]},{"type":"text","text":"两本书的共同作者,他还是多个开源项目的贡献者。自2017年以来,他一直是Java Champion,是国际演讲者和Salle URL大学的教师。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"英文原文:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/articles\/microservicilities-quarkus\/","title":null,"type":null},"content":[{"type":"text","text":"Implementing Microservicilities with Quarkus and MicroProfile"}],"marks":[{"type":"underline"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章