如何藉助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"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章