使用 Quarkus 和 MicroProfile 實(shí)現(xiàn)微服務(wù)特性

Quarkus 的文章之前寫過三篇了,講過了 Quarkus 的小而快。

一直在醞釀寫一篇 Quarkus 生態(tài)相關(guān)的约郁,因?yàn)樽罱恢痹诿?Meetup 的事情而擱淺麸锉。正好看到了這篇文章信柿,就拿來翻譯一下冀偶,補(bǔ)全云原生中的“微服務(wù)”這一塊。

本文譯自《Implementing Microservicilities with Quarkus and MicroProfile》 角塑。


為什么要使用微服務(wù)特性蔫磨?

在微服務(wù)架構(gòu)中淘讥,一個(gè)應(yīng)用程序是由幾個(gè)相互連接的服務(wù)組成的圃伶,這些服務(wù)一起工作來實(shí)現(xiàn)所需的業(yè)務(wù)功能。

因此蒲列,典型的企業(yè)微服務(wù)架構(gòu)如下所示:

剛開始窒朋,使用微服務(wù)架構(gòu)實(shí)現(xiàn)應(yīng)用程序看起來很容易。

但是蝗岖,因?yàn)橛辛藛误w架構(gòu)沒有一些新的挑戰(zhàn)侥猩,因此做起來并不容器

舉幾個(gè)例子,比如容錯(cuò)抵赢、服務(wù)發(fā)現(xiàn)欺劳、擴(kuò)展性、日志記錄和跟蹤铅鲤。

為了解決這些挑戰(zhàn)划提,每個(gè)微服務(wù)都應(yīng)實(shí)現(xiàn)我們?cè)?Red Hat 所說的“微服務(wù)特性”。

  • 該術(shù)語是指除業(yè)務(wù)邏輯以外邢享,服務(wù)還必須實(shí)現(xiàn)來解決的跨領(lǐng)域關(guān)注點(diǎn)清單鹏往,如下圖所示:

可以用任何語言(Java、Go骇塘、JavaScript)或任何框架(Spring Boot伊履、Quarkus)實(shí)現(xiàn)業(yè)務(wù)邏輯韩容,但是圍繞業(yè)務(wù)邏輯,應(yīng)實(shí)現(xiàn)以下關(guān)注點(diǎn):

API:可通過一組定義的 API 操作來訪問該服務(wù)唐瀑。例如群凶,對(duì)于 RESTful Web API,HTTP 用作協(xié)議介褥。此外座掘,可以使用諸如 Swagger 之類的工具來記錄 API 。
服務(wù)發(fā)現(xiàn)(Discovery):服務(wù)需要發(fā)現(xiàn)其他服務(wù)柔滔。

調(diào)用服務(wù)(Invocation):發(fā)現(xiàn)服務(wù)后溢陪,需要使用一組參數(shù)對(duì)其進(jìn)行調(diào)用,并選擇性地返回響應(yīng)睛廊。

彈性(Elasticity):微服務(wù)架構(gòu)的重要特征之一是每個(gè)服務(wù)都是彈性的形真,這意味著可以根據(jù)系統(tǒng)的關(guān)鍵程度或當(dāng)前的工作量等參數(shù)獨(dú)立地進(jìn)行縮放。(譯者注:這里的彈性只是資源的彈性)

彈性(Resiliency):在微服務(wù)架構(gòu)中超全,我們?cè)陂_發(fā)時(shí)應(yīng)牢記失敗咆霜,尤其是在與其他服務(wù)進(jìn)行通信時(shí)。在單體應(yīng)用中嘶朱,整個(gè)應(yīng)用程序處于啟動(dòng)或關(guān)閉狀態(tài)蛾坯。但是,當(dāng)此應(yīng)用程序分解為微服務(wù)體系結(jié)構(gòu)時(shí)疏遏,該應(yīng)用程序由多個(gè)服務(wù)組成脉课,并且所有這些服務(wù)都通過網(wǎng)絡(luò)互連,這意味著該應(yīng)用程序的某些部分可能正在運(yùn)行财异,而其他部分可能會(huì)失敗倘零。遏制故障對(duì)避免通過其他服務(wù)傳播錯(cuò)誤很重要。彈性(或應(yīng)用程序彈性)是應(yīng)用程序/服務(wù)對(duì)問題做出反應(yīng)并仍然提供最佳結(jié)果的能力戳寸。(譯者注:這里的彈性與容錯(cuò)相關(guān)呈驶,對(duì)失敗處理的彈性)

管道(Pipeline):服務(wù)應(yīng)獨(dú)立部署,而無需進(jìn)行任何形式的編排疫鹊。因此袖瞻,每個(gè)服務(wù)應(yīng)具有自己的部署管道。

身份驗(yàn)證(Authentication):關(guān)于微服務(wù)體系結(jié)構(gòu)中的安全性的關(guān)鍵方面之一是如何對(duì)內(nèi)部服務(wù)之間的調(diào)用進(jìn)行身份驗(yàn)證/授權(quán)拆吆。Web 令牌(通常是令牌)是在內(nèi)部服務(wù)中安全地表示聲明的首選方式聋迎。

日志記錄(Logging):在單體應(yīng)用程序中,日志記錄很簡(jiǎn)單锈拨,因?yàn)樵搼?yīng)用程序的所有組件都在同一節(jié)點(diǎn)上運(yùn)行砌庄。然后現(xiàn)在組件以服務(wù)的形式分布在多個(gè)節(jié)點(diǎn)上,因此,要擁有完整的日志記錄視圖娄昆,需要一個(gè)統(tǒng)一的日志記錄系統(tǒng)/數(shù)據(jù)收集器佩微。

監(jiān)控(Monitoring):衡量系統(tǒng)的性能、了解應(yīng)用程序的整體運(yùn)行狀況萌焰,以及在出現(xiàn)問題時(shí)發(fā)出警報(bào)是保持基于微服務(wù)的應(yīng)用程序正確運(yùn)行的關(guān)鍵方面哺眯。監(jiān)控是控制應(yīng)用程序的關(guān)鍵方面。

跟蹤(Tracing):跟蹤用于可視化程序的流程和數(shù)據(jù)進(jìn)度扒俯。作為開發(fā)人員/運(yùn)維人員奶卓,當(dāng)我們需要檢查用戶在整個(gè)應(yīng)用程序中的行程時(shí),這特別有用撼玄。

Kubernetes正在成為部署微服務(wù)的實(shí)際工具夺姑。這是一個(gè)用于自動(dòng)化、編排掌猛、擴(kuò)展和管理容器的開源系統(tǒng)盏浙。

使用 Kubernetes 時(shí),十個(gè)微服務(wù)特性中只有三個(gè)被涵蓋荔茬。

**服務(wù)發(fā)現(xiàn) **是通過 Kubernetes 服務(wù)的概念實(shí)現(xiàn)的废膘。它提供了一種使用穩(wěn)定的虛擬 IP 和 DNS 名稱將 Kubernetes Pod 分組(作為一個(gè)整體)的方法。發(fā)現(xiàn)服務(wù)只是使用 Kubernetes 的服務(wù)名作為 hostname 進(jìn)行請(qǐng)求慕蔚。

使用 Kubernetes 可以很容易地調(diào)用服務(wù)丐黄,因?yàn)槠脚_(tái)本身提供了調(diào)用任何服務(wù)所需的網(wǎng)絡(luò)。

從一開始孔飒,Kubernetes 就一直在考慮彈性(或可伸縮性)灌闺,例如運(yùn)行時(shí)kubectl scale deployment myservice --replicas=5 command,myservice deployment 可伸縮至五個(gè)副本或?qū)嵗肌ubernetes 平臺(tái)負(fù)責(zé)尋找合適的節(jié)點(diǎn)菩鲜,部署服務(wù)并始終保持所需數(shù)量的副本并正常運(yùn)行园细。

但是其余的微服務(wù)特性又如何呢惦积?Kubernetes 僅涵蓋其中的三個(gè),那么我們?nèi)绾螌?shí)現(xiàn)剩下的呢猛频?

根據(jù)所使用的語言或框架狮崩,可以遵循的策略很多。但是在本文中鹿寻,我們將了解如何使用 Quarkus 實(shí)現(xiàn)其中的一些策略睦柴。

什么是 Quarkus?

Quarkus 是針對(duì) Java 虛擬機(jī)(JVM)和本機(jī)編譯的全棧 Kubernetes 本地 Java 框架毡熏,專門針對(duì)容器優(yōu)化 Java坦敌,使其成為無服務(wù)器(Serverless)、云和 Kubernetes 環(huán)境的高效平臺(tái)。

Instead of reinventing the wheel, Quarkus uses well-known enterprise-grade frameworks backed by standards/specifications and makes them compilable to a binary using GraalVM.
Quarkus不用重新發(fā)明輪子狱窘,而是使用以標(biāo)準(zhǔn)/規(guī)范為后盾的知名企業(yè)級(jí)框架杜顺,并使用 GraalVM 將其編譯為二進(jìn)制文件

什么是 MicroProfile蘸炸?

Quarkus 與 MicroProfile 規(guī)范集成躬络,從而將企業(yè) Java 生態(tài)系統(tǒng)遷移到微服務(wù)體系結(jié)構(gòu)中。

在下圖中搭儒,我們看到了構(gòu)成 MicroProfile 規(guī)范的所有 API穷当。某些 API(例如 CDI、JSON-P 和 JAX-RS)基于 Jakarta EE(以前的 Java EE)規(guī)范淹禾。其余的由 Java 社區(qū)開發(fā)馁菜。

Let’s implement API, invocation, resilience, authentication, logging, monitoring, and tracing microservicilities using Quarkus.
讓我們使用Quarkus實(shí)現(xiàn)API、調(diào)用铃岔、彈性火邓、身份驗(yàn)證、日志記錄德撬、監(jiān)視和跟蹤微服務(wù)特性铲咨。

如何使用 Quarkus 實(shí)現(xiàn)微服務(wù)特性

入門

開始使用 Quarkus 的最快方法是通過在開始頁面中選擇所需的依賴。對(duì)于當(dāng)前示例蜓洪,選擇如下依賴關(guān)系以滿足微服務(wù)需求:

API:RESTEasy JAX-RS纤勒、RESTEasy JSON-B、OpenAPI
調(diào)用:REST Client JSON-B
彈性:Fault Tolerance
認(rèn)證:JWT
記錄:GELF
監(jiān)控:Micrometer metrics
跟蹤:OpenTracing

我們可以手動(dòng)選擇各自的依賴關(guān)系隆檀,或?yàn)g覽以下鏈接 Quarkus 微服務(wù)特性生成器摇天,所有這些都會(huì)被選中。然后按“生成應(yīng)用程序”按鈕以下載包含支架應(yīng)用程序的zip文件恐仑。

服務(wù)

對(duì)于當(dāng)前示例泉坐,僅使用兩個(gè)服務(wù)生成了一個(gè)非常簡(jiǎn)單的應(yīng)用程序。一個(gè)名為“評(píng)級(jí)服務(wù) rating service”的服務(wù)返回給定書籍的評(píng)級(jí)裳仆,而另一個(gè)名為“書籍服務(wù) book service”的服務(wù)則返回一本書的信息及其評(píng)級(jí)腕让。服務(wù)之間的所有調(diào)用都必須經(jīng)過身份驗(yàn)證。

在下圖中歧斟,我們看到了整個(gè)系統(tǒng)的概述:

評(píng)級(jí)服務(wù)已經(jīng)開發(fā)并作為 Linux 容器提供纯丸。通過運(yùn)行以下命令,在端口 9090 上啟動(dòng)服務(wù):

docker run --rm -ti -p 9090:8080 
quay.io/lordofthejars/rating-service:1.0.0

要驗(yàn)證服務(wù)静袖,請(qǐng)向 http://localhost:9090/rate/1 發(fā)出請(qǐng)求

curl localhost:8080/rate/1 -vv

> GET /rate/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< www-authenticate: Bearer {token}
< Content-Length: 0

返回的狀態(tài)碼是 401 Unauthorized 因?yàn)闆]有在請(qǐng)求中攜帶令牌(JWT)提供授權(quán)信息觉鼻。只有帶有 group Echoer 有效令牌才能訪問評(píng)級(jí)服務(wù)。

API

Quarkus 使用眾所周知的 JAX-RS 規(guī)范來定義 RESTful Web API队橙。在幕后坠陈,Quarkus 使用 RESTEasy 實(shí)現(xiàn)直接與 Vert.X 框架一起使用萨惑,而無需使用 Servlet 技術(shù)。

讓我們?yōu)閷?shí)現(xiàn)最常見操作的圖書服務(wù)定義一個(gè) API:

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

@Path("/book")
public class BookResource {

   @GET
   @Path("/{bookId}")
   @Produces(MediaType.APPLICATION_JSON)
   public Book book(@PathParam("bookId") Long bookId) {
    // logic
   }

   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   public Response getBook(Book book) {
       // logic

       return Response.created(
                   UriBuilder.fromResource(BookResource.class)
                     .path(Long.toString(book.bookId))
                     .build())
               .build();
   }

   @DELETE
   @Path("/{bookId}")
   public Response delete(@PathParam("bookId") Long bookId) {
       // logic

       return Response.noContent().build();
   }

   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @Path("search")
   public Response searchBook(@QueryParam("description") String description) {       
       // logic

       return Response.ok(books).build();
   }
}

首先要注意的是仇矾,定義了四個(gè)不同的端點(diǎn):

  • GET /book/{bookId} 使用 GET HTTP 方法返回帶有其評(píng)級(jí)的圖書信息咒钟。return 元素會(huì)自動(dòng)解編為 JSON。
  • POST /book 使用 POST HTTP 方法插入一本書作為正文內(nèi)容若未。正文內(nèi)容會(huì)自動(dòng)從 JSON 編組到 Java 對(duì)象朱嘴。
  • DELETE /book/{bookId} 使用 DELETE HTTP 方法通過書的 ID 刪除書。
  • GET /book/search?description={description} 按書名搜索書籍粗合。

注意的第二件事是返回類型萍嬉,有時(shí)是 Java 對(duì)象,有時(shí)是 Java 實(shí)例 javax.ws.rs.core.Response隙疚。使用 Java 對(duì)象時(shí)壤追,會(huì)將其從 Java 對(duì)象序列化為 @Produces 注解中設(shè)置的媒體類型。在此特定服務(wù)中供屉,輸出為 JSON 文檔行冰。通過該 Response 對(duì)象,我們可以對(duì)返回給調(diào)用方的內(nèi)容進(jìn)行細(xì)粒度的控制伶丐〉孔觯可以設(shè)置 HTTP 狀態(tài)代碼、標(biāo)頭或返回給調(diào)用方的內(nèi)容哗魂。取決于使用場(chǎng)景肛走,是偏愛一種方法而不是另一種方法。

調(diào)用

在定義了用于訪問圖書服務(wù)的 API 之后录别,是時(shí)候開發(fā)一段代碼來調(diào)用評(píng)級(jí)服務(wù)以檢索圖書的評(píng)級(jí)了朽色。

Quarkus 使用 MicroProfile Rest Client 規(guī)范來訪問外部(HTTP)服務(wù)。它提供了一種類型安全的方法组题,以通過某些 JAX-RS 2.0 API 通過 HTTP 調(diào)用 RESTful 服務(wù)葫男,以實(shí)現(xiàn)一致性和更易于重用。

要?jiǎng)?chuàng)建的第一個(gè)元素是一個(gè)使用 JAX-RS 批注表示遠(yuǎn)程服務(wù)的接口崔列。

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/rate")
@RegisterRestClient
public interface RatingService {
 
   @GET
   @Path("/{bookId}")
   @Produces(MediaType.APPLICATION_JSON)
   Rate getRate(@PathParam("bookId") Long bookId);

}

When the getRate() method is called, a remote HTTP call is invoked at /rate/{bookId} replacing the bookId with the value set in the method parameter. It is important to annotate the interface with the @RegisterRestClient annotation.
Then the RatingService interface needs to be injected into BookResource to execute the remote calls.
當(dāng) getRate()方法被調(diào)用時(shí)梢褐,遠(yuǎn)程 HTTP 請(qǐng)求在調(diào)用 /rate/{bookId} 替換 bookId 用在該方法中的參數(shù)值集合。用 @RegisterRestClient 注解對(duì)接口進(jìn)行注解很重要峻呕。

然后 RatingService 需要將接口注入 BookResource 以執(zhí)行遠(yuǎn)程調(diào)用利职。

import org.eclipse.microprofile.rest.client.inject.RestClient;

@RestClient
RatingService ratingService;

@GET
@Path("/{bookId}")
@Produces(MediaType.APPLICATION_JSON)
public Book book(@PathParam("bookId") Long bookId) {
    final Rate rate = ratingService.getRate(bookId);

    Book book = findBook(bookId);
    return book;
}

The @RestClient annotation injects a proxied instance of the interface, providing the implementation of the client.
The last thing is to configure the service location (the hostname part). In Quarkus, the configuration properties are set in src/main/resources/application.properties file. To configure the location of the service, we need to use the fully qualified name of the Rest Client interface with URL as key, and the location as a value:
@RestClient 注解注入界面的代理實(shí)例趣效,提供客戶端的實(shí)現(xiàn)瘦癌。

最后一件事是配置服務(wù)位置(hostname 部分)。在 Quarkus 中跷敬,配置屬性在 src/main/resources/application.properties 文件中設(shè)置讯私。要配置服務(wù)的位置,我們需要使用 Rest Client 接口的標(biāo)準(zhǔn)名稱,其中 URL 作為鍵斤寇,而 location 作為值:

org.acme.RatingService/mp-rest/url=http://localhost:9090

在正確訪問評(píng)估服務(wù)而沒有 401 Unauthorized 問題之前桶癣,必須解決相互認(rèn)證問題。

身份驗(yàn)證

基于令牌的身份驗(yàn)證機(jī)制允許系統(tǒng)基于安全令牌對(duì)身份進(jìn)行身份驗(yàn)證娘锁、授權(quán)和驗(yàn)證牙寞。Quarkus 與 MicroProfile JWT RBAC 安全規(guī)范集成在一起,以使用 JWT 令牌保護(hù)服務(wù)莫秆。

要使用 MicroProfile JWT RBAC 安全性保護(hù)端點(diǎn)间雀,我們只需要使用批注對(duì)方法進(jìn)行 @RolesAllowed 注解。

@GET
@Path("/{bookId}")
@RolesAllowed("Echoer")
@Produces(MediaType.APPLICATION_JSON)
public Book book(@PathParam("bookId") Long bookId)

然后镊屎,我們配置令牌的發(fā)行方和公鑰的位置惹挟,以驗(yàn)證令牌在 application.properties 文件中的簽名:

mp.jwt.verify.publickey.location=https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.jwt.pub
mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac

此擴(kuò)展名自動(dòng)驗(yàn)證:令牌有效;發(fā)行方是正確的缝驳;令牌尚未修改连锯;簽名有效;沒有過期用狱。

這兩種圖書服務(wù)評(píng)級(jí)服務(wù)現(xiàn)在是由同一 JWT 發(fā)行方和密鑰保護(hù)运怖,因此服務(wù)之間的通信要求驗(yàn)證提供在令牌的有效承載用戶 Authentication 頭部。

評(píng)級(jí)服務(wù)啟動(dòng)和運(yùn)行夏伊,讓我們開始用下面的命令圖書服務(wù)

./mvnw compile quarkus:dev

Finally, we can make a request to get book information providing a valid JSON Web Token as a bearer token.
The generation of the token is out of the scope of this article, and a token has been already generated:
最后驳规,我們可以請(qǐng)求獲取提供有效 JSON Web 令牌作為承載令牌的圖書信息。

令牌的生成不在本文的討論范圍之內(nèi)署海,并且已經(jīng)生成了令牌:

curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTcwMDk0MTcxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MjIwMDgxNDE3MSwiaWF0IjoxNTcwMDk0MTcxLCJqdGkiOiJhLTEyMyJ9.Hzr41h3_uewy-g2B-sonOiBObtcpkgzqmF4bT3cO58v45AIOiegl7HIx7QgEZHRO4PdUtR34x9W23VJY7NJ545ucpCuKnEV1uRlspJyQevfI-mSRg1bHlMmdDt661-V3KmQES8WX2B2uqirykO5fCeCp3womboilzCq4VtxbmM2qgf6ag8rUNnTCLuCgEoulGwTn0F5lCrom-7dJOTryW1KI0qUWHMMwl4TX5cLmqJLgBzJapzc5_yEfgQZ9qXzvsT8zeOWSKKPLm7LFVt2YihkXa80lWcjewwt61rfQkpmqSzAHL0QIs7CsM9GfnoYc0j9po83-P3GJiBMMFmn-vg" localhost:8080/book/1 -v

響應(yīng)又是 forbidden 錯(cuò)誤:

< HTTP/1.1 401 Unauthorized
< Content-Length: 0

你可能想知道為什么在提供有效令牌后仍然出現(xiàn)此錯(cuò)誤吗购。如果我們檢查圖書服務(wù)的控制臺(tái),就會(huì)發(fā)現(xiàn)拋出了以下異常:

org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 401
    at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)
    at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)

發(fā)生此異常的原因是砸狞,我們已獲得身份驗(yàn)證并有權(quán)訪問圖書服務(wù)捻勉,但承載令牌尚未傳播到評(píng)級(jí)服務(wù)

為了自動(dòng)將 Authorization 標(biāo)頭從傳入請(qǐng)求傳播到其余客戶端請(qǐng)求刀森,需要進(jìn)行兩次修改踱启。

第一個(gè)修改是修改 Rest Client 界面,并使用對(duì)其進(jìn)行注解 org.eclipse.microprofile.rest.client.inject.RegisterClientHeaders研底。

@Path("/rate")
@RegisterRestClient
@RegisterClientHeaders
public interface RatingService {}

第二個(gè)修改是配置在請(qǐng)求之間傳播哪些標(biāo)頭埠偿。這是在 application.properties 文件中設(shè)置的:

    org.eclipse.microprofile.rest.client.propagateHeaders=Authorization

執(zhí)行與之前相同的 curl 命令贡耽,我們將獲得正確的輸出:

< HTTP/1.1 200 OK
< Content-Length: 39
< Content-Type: application/json
<
* Connection #0 to host localhost left intact
{"bookId":2,"name":"Book 2","rating":1}* Closing connection 0

彈性

在微服務(wù)架構(gòu)中誊薄,具有容錯(cuò)能力很重要,這樣可以避免故障從一個(gè)服務(wù)傳播到該服務(wù)的所有直接和間接調(diào)用方欢搜。Quarkus 將 MicroProfile Fault Tolerance 規(guī)范與以下用于處理故障的注釋集成在一起:

@Timeout:定義拋出異常之前執(zhí)行的最長(zhǎng)時(shí)間乾胶。
@Retry:如果調(diào)用失敗抖剿,請(qǐng)?jiān)俅沃卦噲?zhí)行朽寞。
@Bulkhead:限制并發(fā)執(zhí)行,以使該區(qū)域中的故障不會(huì)使整個(gè)系統(tǒng)過載斩郎。
@CircuitBreaker:執(zhí)行反復(fù)失敗時(shí)脑融,將自動(dòng)進(jìn)行快速故障切換。
@Fallback:執(zhí)行失敗時(shí)缩宜,提供備用解決方案/默認(rèn)值肘迎。

讓我們添加三次重試,其中重試之間的延遲計(jì)時(shí)器為一秒锻煌,以防訪問評(píng)級(jí)服務(wù)時(shí)發(fā)生錯(cuò)誤膜宋。

@Retry(maxRetries = 3, delay = 1000)
Rate getRate(@PathParam("bookId") Long bookId);

現(xiàn)在,停止評(píng)級(jí)服務(wù)并執(zhí)行請(qǐng)求炼幔。引發(fā)以下異常:

org.jboss.resteasy.spi.UnhandledException: javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: 
org.apache.http.conn.HttpHostConnectException: Connect to localhost:9090 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused

顯然秋茫,這里存在錯(cuò)誤,但是請(qǐng)注意乃秀,由于執(zhí)行了三次重試(延遲一秒)肛著,因此引發(fā)異常之前,經(jīng)過了三秒鐘跺讯。

在這種情況下枢贿,評(píng)級(jí)服務(wù)已關(guān)閉,因此無法進(jìn)行恢復(fù)刀脏,但是在一個(gè)實(shí)際示例中局荚,評(píng)級(jí)服務(wù)可能僅在短時(shí)間內(nèi)就恢復(fù)了,或者部署了該服務(wù)的多個(gè)副本愈污,因此可以簡(jiǎn)單地重試操作可能足以恢復(fù)并提供有效的響應(yīng)耀态。

但是,當(dāng)引發(fā)異常時(shí)重試次數(shù)不夠時(shí)暂雹,我們可以將錯(cuò)誤傳播給調(diào)用方首装,也可以為調(diào)用提供替代值。這種選擇可以是對(duì)另一個(gè)系統(tǒng)的調(diào)用(即分布式緩存)或靜態(tài)值杭跪。

對(duì)于此用例仙逻,當(dāng)與評(píng)級(jí)服務(wù)的連接失敗時(shí),將返回評(píng)級(jí)值 0涧尿。

要實(shí)現(xiàn)回退邏輯系奉,首先要做的是實(shí)現(xiàn)將 org.eclipse.microprofile.faulttolerance.FallbackHandler 返回類型設(shè)置為與回退策略方法提供的替代類型相同的接口。對(duì)于這種情況姑廉,將 Rate 返回默認(rèn)對(duì)象缺亮。

import org.eclipse.microprofile.faulttolerance.ExecutionContext;
import org.eclipse.microprofile.faulttolerance.FallbackHandler;

public class RatingServiceFallback implements FallbackHandler<Rate> {

   @Override
   public Rate handle(ExecutionContext context) {
       Rate rate = new Rate();
       rate.rate = 0;
       return rate;
   }
 
}

最后要做的是用注解對(duì) getRating() 方法進(jìn)行 @org.eclipse.microprofile.faulttolerance.Fallback 注解,以配置無法恢復(fù)時(shí)要執(zhí)行的回退類庄蹋。

@Retry(maxRetries = 3, delay = 1000)
@Fallback(RatingServiceFallback.class)
Rate getRate(@PathParam("bookId") Long bookId);

如果重復(fù)與以前相同的請(qǐng)求瞬内,則不會(huì)引發(fā)任何異常迷雪,但是有效值的輸出將 rating 字段設(shè)置為 0限书。

* Connection #0 to host localhost left intact
{"bookId":2,"name":"Book 2","rating":0}* Closing connection 0

規(guī)范提供的任何其他策略都可以使用相同的方法虫蝶。例如,對(duì)于斷路器模式:

@CircuitBreaker(requestVolumeThreshold = 4,
               failureRatio=0.75,
               delay = 1000)

如果在四個(gè)連續(xù)調(diào)用的滾動(dòng)窗口中發(fā)生了三個(gè)(4 x 0.75)故障倦西,則電路將斷開 1000 ms能真,然后恢復(fù)到半斷開狀態(tài)。如果在半開時(shí)調(diào)用成功扰柠,則將其再次關(guān)閉粉铐。否則,它將保持打開狀態(tài)

日志記錄

在微服務(wù)架構(gòu)中卤档,建議將所有服務(wù)的日志收集在一個(gè)統(tǒng)一的日志中蝙泼,以更有效地使用和理解。

一種解決方案是使用 Fluentd劝枣,它是 Kubernetes 中用于統(tǒng)一日志記錄層的開源數(shù)據(jù)收集器汤踏。Quarkus 使用 Graylog 擴(kuò)展日志格式(GELF)與 Fluentd 集成。

集成真的很簡(jiǎn)單舔腾。首先溪胶,與其他任何 Quarkus 應(yīng)用程序一樣使用日志邏輯:

import org.jboss.logging.Logger;

private static final Logger LOG = Logger.getLogger(BookResource.class);

@GET
@Path("/{bookId}")
@RolesAllowed("Echoer")
@Produces(MediaType.APPLICATION_JSON)
public Book book(@PathParam("bookId") Long bookId) {
    LOG.info("Get Book");

接下來,啟用 GELF 格式并設(shè)置 Fluentd 服務(wù)器位置:

quarkus.log.handler.gelf.enabled=true
quarkus.log.handler.gelf.host=localhost
quarkus.log.handler.gelf.port=12201

最后稳诚,我們可以向記錄的端點(diǎn)發(fā)出請(qǐng)求:

curl -H "Authorization: Bearer ..." localhost:8080/book/1

{"bookId":1,"name":"Book 1","rating":3}

輸出方面沒有任何變化哗脖,但是日志行已傳輸?shù)?Fluentd。如果使用 Kibana 可視化數(shù)據(jù)扳还,我們將看到存儲(chǔ)的日志行:

監(jiān)控

Monitoring is another "microservicilitie" that needs to be implemented in our microservice architecture. Quarkus integrates with Micrometer for application monitoring. Micrometer provides a single entry point to the most popular monitoring systems, allowing you to instrument your JVM-based application code without vendor lock-in.

For this example, Prometheus format is used as monitoring output but Micrometer (and Quarkus) also supports other formats like Azure Monitor, Stackdriver, SignalFx, StatsD, and DataDog.

You can register the following Maven dependency to provide Prometheus output:
監(jiān)控是另一個(gè)需要在我們的微服務(wù)架構(gòu)中實(shí)現(xiàn)的 “微服務(wù)特性”才避。Quarkus 與 Micrometer 集成在一起以進(jìn)行應(yīng)用程序監(jiān)控。Micrometer 提供了最流行的監(jiān)控系統(tǒng)的單個(gè)入口點(diǎn)氨距,使你無需供應(yīng)商鎖定即可檢測(cè)基于 JVM 的應(yīng)用程序代碼工扎。

對(duì)于此示例,監(jiān)控輸出采用 Prometheus 格式衔蹲,但 Micrometer(和 Quarkus)還支持其他格式肢娘,例如 Azure Monitor、Stackdriver舆驶、SignalFx橱健、StatsD 和 DataDog。

你可以注冊(cè)以下 Maven 依賴項(xiàng)以提供 Prometheus 輸出:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>

默認(rèn)情況下沙廉,Micrometer 擴(kuò)展注冊(cè)了一些與系統(tǒng)拘荡,JVM 或 HTTP 相關(guān)的度量。收集的指標(biāo)的一個(gè)子集在 /q/metrics 端點(diǎn)處可用撬陵,如下所示:

curl localhost:8080/q/metrics

jvm_threads_states_threads{state="runnable",} 22.0
jvm_threads_states_threads{state="blocked",} 0.0
jvm_threads_states_threads{state="waiting",} 10.0
http_server_bytes_read_count 1.0
http_server_bytes_read_sum 0.0

但是珊皿,也可以使用 Micrometer API 來實(shí)現(xiàn)特定于應(yīng)用程序的指標(biāo)网缝。
讓我們實(shí)現(xiàn)一個(gè)自定義指標(biāo),該指標(biāo)用于衡量評(píng)價(jià)最高的圖書蟋定。

使用 io.micrometer.core.instrument.MeterRegistry 該類可以完成指標(biāo)(在這種情況下為量規(guī))的注冊(cè)粉臊。

private final MeterRegistry registry;
private final LongAccumulator highestRating = new LongAccumulator(Long::max, 0);
 
public BookResource(MeterRegistry registry) {
    this.registry = registry;
    registry.gauge("book.rating.max", this,
               BookResource::highestRatingBook);
}

請(qǐng)求一下,并驗(yàn)證量規(guī)是否正確更新驶兜。

curl -H "Authorization: Bearer ..." localhost:8080/book/1

{"bookId":1,"name":"Book 1","rating":3}

curl localhost:8080/q/metrics

# HELP book_rating_max
# TYPE book_rating_max gauge
book_rating_max 3.0

我們還可以設(shè)置一個(gè)計(jì)時(shí)器來記錄從評(píng)級(jí)服務(wù)獲取評(píng)級(jí)信息所花費(fèi)的時(shí)間扼仲。

Supplier<Rate> rateSupplier = () -> {
      return ratingService.getRate(bookId);
};
      
final Rate rate = registry.timer("book.rating.test").wrap(rateSupplier).get();

請(qǐng)求一下,并驗(yàn)證收集評(píng)價(jià)所花費(fèi)的時(shí)間抄淑。

# HELP book_rating_test_seconds
# TYPE book_rating_test_seconds summary
book_rating_test_seconds_count 4.0
book_rating_test_seconds_sum 1.05489108
# HELP book_rating_test_seconds_max
# TYPE book_rating_test_seconds_max gauge
book_rating_test_seconds_max 1.018622001

Micrometer 使用 MeterFilter 實(shí)例來自定義 MeterRegistry 實(shí)例發(fā)出的度量屠凶。Micrometer 擴(kuò)展將檢測(cè) MeterFilter CDI bean,并在初始化 MeterRegistry 實(shí)例時(shí)使用它們肆资。

例如矗愧,我們可以定義一個(gè)通用標(biāo)簽來設(shè)置運(yùn)行應(yīng)用程序的環(huán)境(產(chǎn)品、測(cè)試郑原、預(yù)發(fā)布等)唉韭。

@Singleton
public class MicrometerCustomConfiguration {
 
   @Produces
   @Singleton
   public MeterFilter configureAllRegistries() {
       return MeterFilter.commonTags(Arrays.asList(
               Tag.of("env", "prod")));
   }

}

發(fā)送新請(qǐng)求并驗(yàn)證指標(biāo)是否已標(biāo)記。

http_client_requests_seconds_max{clientName="localhost",env="prod",method="GET",outcome="SUCCESS",status="200",uri="/rate/2",} 0.0

請(qǐng)注意 env 包含值為 prod 的標(biāo)簽颤专。

跟蹤

Quarkus 應(yīng)用程序利用 OpenTracing 規(guī)范為交互式 Web 應(yīng)用程序提供分布式跟蹤纽哥。

讓我們配置 OpenTracing 以連接到 Jaeger 服務(wù)器,將 book-service 設(shè)置為服務(wù)名稱以標(biāo)識(shí)跟蹤:

quarkus.jaeger.enabled=true
quarkus.jaeger.endpoint=http://localhost:14268/api/traces
quarkus.jaeger.service-name=book-service
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1

現(xiàn)在發(fā)一個(gè)請(qǐng)求:

curl -H "Authorization: Bearer ..." localhost:8080/book/1 {"bookId":1,"name":"Book 1","rating":3}

訪問Jaeger UI以驗(yàn)證是否跟蹤了該調(diào)用:

總結(jié)

與開發(fā)整體應(yīng)用程序相比栖秕,開發(fā)和實(shí)現(xiàn)微服務(wù)體系結(jié)構(gòu)更具挑戰(zhàn)性春塌。我們認(rèn)為,微服務(wù)可以驅(qū)動(dòng)你根據(jù)應(yīng)用程序基礎(chǔ)結(jié)構(gòu)正確地開發(fā)服務(wù)簇捍。

此處介紹的大多數(shù)微服務(wù)(API 和管道除外)是新的只壳,或者在整體應(yīng)用中實(shí)現(xiàn)方式有所不同。原因是現(xiàn)在應(yīng)用程序被分解成幾部分暑塑,所有部分都在網(wǎng)絡(luò)中互連吼句。

如果你May 26, 2021打算開發(fā)微服務(wù)并將其部署到 Kubernetes,那么 Quarkus 是一個(gè)很好的解決方案事格,因?yàn)樗梢耘c Kubernetes 順利集成惕艳。實(shí)施大多數(shù)微服務(wù)很簡(jiǎn)單,只需要幾行代碼驹愚。

本文演示的源代碼可以在 github 上找到远搪。

關(guān)于作者

Alex Soto 是 Red Hat 開發(fā)人員經(jīng)驗(yàn)總監(jiān)。他對(duì) Java 世界逢捺,軟件自動(dòng)化充滿熱情谁鳍,并且他相信開源軟件模型。Soto 是 Manning 的合著者 | 測(cè)試 Java 微服務(wù)O'Reilly Quarkus Cookbook 和幾個(gè)開源項(xiàng)目的貢獻(xiàn)者。自 2017 年以來一直是 Java 冠軍倘潜,他還是 Salle URL University 的國際演講者和老師绷柒。你可以在 Twitter (Alex Soto)上關(guān)注他,以隨時(shí)了解 Kubernetes 和 Java 世界中正在發(fā)生的事情涮因。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末废睦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蕊退,更是在濱河造成了極大的恐慌郊楣,老刑警劉巖憔恳,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓤荔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡钥组,警方通過查閱死者的電腦和手機(jī)输硝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來程梦,“玉大人点把,你說我怎么就攤上這事∮旄剑” “怎么了郎逃?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)挺份。 經(jīng)常有香客問我褒翰,道長(zhǎng),這世上最難降的妖魔是什么匀泊? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任优训,我火速辦了婚禮,結(jié)果婚禮上各聘,老公的妹妹穿的比我還像新娘揣非。我一直安慰自己,他們只是感情好躲因,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布早敬。 她就那樣靜靜地躺著,像睡著了一般大脉。 火紅的嫁衣襯著肌膚如雪搞监。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天箱靴,我揣著相機(jī)與錄音腺逛,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棍矛,可吹牛的內(nèi)容都是我干的安疗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼够委,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼荐类!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茁帽,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤玉罐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后潘拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吊输,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年铁追,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了季蚂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琅束,死狀恐怖扭屁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涩禀,我是刑警寧澤料滥,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站艾船,受9級(jí)特大地震影響葵腹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丽声,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一礁蔗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雁社,春花似錦浴井、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至徒坡,卻和暖如春撕氧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喇完。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工伦泥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓不脯,卻偏偏與公主長(zhǎng)得像府怯,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子防楷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容