使用Spring Cloud和Docker構(gòu)建微服務(wù)架構(gòu)

原文:https://dzone.com/articles/microservice-architecture-with-spring-cloud-and-do
作者:Alexander Lukyanchikov
譯者:Oopsguy

本文通過一個使用了 Spring Boot、Spring Cloud 和 Docker 構(gòu)建的概念型應(yīng)用示例來提供了了解常見的微服務(wù)架構(gòu)模式的起點。

代碼可以在 Github 上獲得,同時 Docker Hub 上也提供了鏡像。你只需要一個命令即可啟動整個系統(tǒng)。

我選擇了一個老項目作為這個系統(tǒng)的基礎(chǔ),它的后端以前是單體應(yīng)用。該應(yīng)用提供了處理個人財務(wù)、整理收入開銷、管理儲蓄、分析統(tǒng)計和創(chuàng)建簡單預(yù)測等功能。

功能服務(wù)

整個應(yīng)用分解為三個核心微服務(wù)。它們都是可以獨立部署的應(yīng)用,圍繞某些業(yè)務(wù)功能進(jìn)行組織。

使用 Spring Cloud 和 Docker 構(gòu)建微服務(wù)架構(gòu)

賬戶服務(wù)

包含一般用戶輸入邏輯和相關(guān)驗證:收入/開銷記錄、儲蓄和賬戶設(shè)置。

方法 路徑 描述 用戶驗證 UI可用
GET /accounts/{account} 獲取指定賬戶數(shù)據(jù)
GET /accounts/current 獲取當(dāng)前賬戶數(shù)據(jù) x x
GET /accounts/demo 獲取演示賬戶數(shù)據(jù)(預(yù)先填入收入/開銷記錄等) x
PUT /accounts/current 保存當(dāng)前賬戶數(shù)據(jù) x x
POST /accounts/ 注冊新賬戶 x

統(tǒng)計服務(wù)

計算關(guān)鍵的統(tǒng)計參數(shù),并獲取每一個賬戶的時間序列。一個數(shù)據(jù)點包含了基于貨幣和時間段常規(guī)化后的值。該數(shù)據(jù)可用于跟蹤賬戶生命周期中的現(xiàn)金流量動態(tài)。

方法 路徑 描述 用戶驗證 UI可用
GET /statistics/{account} 獲取指定賬戶統(tǒng)計數(shù)據(jù)
GET /statistics/current 獲取當(dāng)前賬戶的統(tǒng)計數(shù)據(jù) x x
GET /statistics/demo 獲取演示賬戶統(tǒng)計數(shù)據(jù) x
PUT /statistics/{account} 創(chuàng)建或更新指定賬戶的時間序列數(shù)據(jù)點。

通知服務(wù)

存儲用戶的聯(lián)系信息和通知設(shè)置(如提醒和備份頻率)。安排工作人員從其它服務(wù)收集所需的信息并向訂閱的客戶發(fā)送電子郵件。

方法 路徑 描述 用戶驗證 UI可用
GET /notifications/settings/current 獲取當(dāng)前賬戶的通知i設(shè)置 x x
PUT /notifications/settings/current 保存當(dāng)前賬戶的通知設(shè)置 x x

注意

  • 每一個微服務(wù)擁有自己的數(shù)據(jù)庫,因此沒有辦法繞過 API 直接訪問和持久化數(shù)據(jù)。
  • 在這個項目中,我使用 MongoDB 作為每個服務(wù)的主數(shù)據(jù)庫。擁有一個混合持久化架構(gòu)(polyglot persistence architecture)也是很有意義的(數(shù)據(jù)庫的選擇根據(jù)微服務(wù)的要求而定)。
  • 服務(wù)間(Service-to-service)通信非常簡單:微服務(wù)僅使用同步的 REST API 進(jìn)行通信?,F(xiàn)實中的系統(tǒng)常見的做法是使用多種組合的交互風(fēng)格。例如,執(zhí)行同步的 GET 請求檢索數(shù)據(jù),并通過消息代理(broker)使用異步方法執(zhí)行創(chuàng)建/更新操作,以便解除服務(wù)和緩沖消息之間的耦合。然而,我們需要面臨最終的一致性的問題。

基礎(chǔ)設(shè)施服務(wù)

分布式系統(tǒng)中常見的模式,可以幫助我們描述核心服務(wù)如何工作。Spring Cloud 提供了強(qiáng)大的工具,可以增強(qiáng) Spring Boot 應(yīng)用的行為來實現(xiàn)這些模式。我會簡要介紹一下:

基礎(chǔ)設(shè)施服務(wù)

配置服務(wù)

Spring Cloud Config 是分布式可水平擴(kuò)展的配置服務(wù)中心。它使用了一個可拔插存儲庫層(repository layer),當(dāng)前支持本地存儲、Git 和 Subversion 等。

在此項目中,我使用了 native profile,它簡單地從本地 classpath 下加載配置文件。你可以在配置服務(wù)資源中查看 shared 目錄。此時,當(dāng)通知服務(wù)請求它的配置時,配置服務(wù)將響應(yīng)回 shared/notification-service.ymlshared/application.yml(所有客戶端應(yīng)用之間共享)。

客戶端使用

只需要使用 sprng-cloud-starter-config 依賴構(gòu)建 Spring Boot 應(yīng)用,自動配置將會完成其它工作。

現(xiàn)在你的應(yīng)用中不需要任何嵌入的 properties,只需要提供有應(yīng)用名稱和配置服務(wù) url 的 bootstrap.yml 即可:

spring:
  application:
    name: notification-service
  cloud:
    config:
      uri: http://config:8888
      fail-fast: true

使用 Spring Cloud Config,你可以動態(tài)更改應(yīng)用配置

比如,EmailService bean 使用了 @RefreshScope 注解。這意味著你可以更改電子郵件的內(nèi)容和主題,無需重新構(gòu)建和重啟通知服務(wù)應(yīng)用。

首先,在配置服務(wù)器中更改必要的屬性。然后,對通知服務(wù)執(zhí)行刷新請求:curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh。

你也可以使用 webhook 來自動執(zhí)行此過程。

注意

  • 動態(tài)刷新存在一些限制。@RefreshScope 不能和 @Configuraion 類一同工作,并且不會作用于 @Scheduled 方法。
  • fail-fast 屬性意味著如果 Spring Boot 應(yīng)用無法連接到配置服務(wù),將會立即啟動失敗。當(dāng)你同時啟動所有應(yīng)用時,它非常有用。
  • 下面有重要的安全提示

授權(quán)服務(wù)

負(fù)責(zé)授權(quán)的部分被完全提取到單獨的服務(wù)器中,它為后端資源服務(wù)提供OAuth2 令牌。授權(quán)服務(wù)器用于用戶授權(quán)以及在一定范圍內(nèi)保護(hù)機(jī)器間的通信安全。

在此項目中,我使用密碼憑據(jù)作為用戶授權(quán)的授權(quán)類型(因為它僅被本地應(yīng)用 UI 使用)和客戶端憑據(jù)作為微服務(wù)授權(quán)的授權(quán)類型。

Spring Cloud Security 提供了方便的注解和自動配置,使其在服務(wù)器端或者客戶端都可以很容易地實現(xiàn)。你可以在文檔中了解到更多信息,并在授權(quán)服務(wù)器代碼中檢查配置明細(xì)。

從客戶端角度來看,一切都與傳統(tǒng)基于會話的授權(quán)方式完全相同。你可以從請求中獲取 Principal 對象、檢查用戶角色和其他基于表達(dá)式訪問控制和 @PreAuthorize 注解的內(nèi)容。

PiggyMetrics(帳戶服務(wù)、統(tǒng)計服務(wù)、通知服務(wù)和瀏覽器)中的每一個客戶端都有一個邊界:用于后臺服務(wù)的服務(wù)器、用于瀏覽器展示的 UI。所以我們也可以保護(hù)控制器避免受到外部訪問,例如:

@PreAuthorize("#oauth2.hasScope('server')")
@RequestMapping(value = "accounts/{name}", method = RequestMethod.GET)
public List<DataPoint> getStatisticsByAccountName(@PathVariable String name) {
    return statisticsService.findByAccountName(name);
}

API 網(wǎng)關(guān)

你可以看到,有三個核心服務(wù)。它們向客戶端暴露外部 API。在現(xiàn)實系統(tǒng)中,這個數(shù)量可以增長得非常快速,同時整個系統(tǒng)將會變得非常復(fù)雜。實際上,一個復(fù)雜頁面的渲染可能涉及到數(shù)百個服務(wù)。

理論上,客戶端可以直接向每個微服務(wù)發(fā)送請求。但這種方式是存在挑戰(zhàn)和限制的,比如需要知道所有端點的地址,分別對每一段信息執(zhí)行 HTTP 請求,然后在客戶端合并所有請求結(jié)果。另一個問題是,部分微服務(wù)使用的協(xié)議并不是 Web 友好協(xié)議,可能只適用于后端。

通常,一個更好的方法是使用 API 網(wǎng)關(guān)。它是正系統(tǒng)的單入口點,用于通過將請求路由到適當(dāng)?shù)暮蠖朔?wù)或者通過調(diào)用多個后端服務(wù)并聚合結(jié)果來處理請求。此外,它還可以用于認(rèn)證、insights、壓力測試、金絲雀測試(canary testing)、服務(wù)遷移、靜態(tài)響應(yīng)處理和動態(tài)流量管理。

Netflix 開源了這樣的邊緣服務(wù),現(xiàn)在用 Spring Cloud,我們可以用一個 @EnabledZuulProxy 注解來啟用它。在這個項目中,我使用 Zuul 存儲靜態(tài)內(nèi)容(UI 應(yīng)用),并將請求路由到適當(dāng)?shù)奈⒎?wù)。以下是一個簡單的基于前綴(prefix-based)路由的通知服務(wù)配置:

zuul:
  routes:
    notification-service:
        path: /notifications/**
        serviceId: notification-service
        stripPrefix: false

上述配置這意味著所有以 /notification 開頭的請求將被路由到通知服務(wù)。你可以看到,里面沒有硬編碼的地址。Zuul 使用服務(wù)發(fā)現(xiàn)機(jī)制來定位通知服務(wù)實例以及斷路器和負(fù)載均衡器,如下所述。

服務(wù)發(fā)現(xiàn)

另一種常見的架構(gòu)模式是服務(wù)發(fā)現(xiàn)。它允許自動檢測服務(wù)實例的網(wǎng)絡(luò)位置,由于自動擴(kuò)展、故障和升級,服務(wù)實例的地址可能是動態(tài)分配的。

服務(wù)發(fā)現(xiàn)的關(guān)鍵部分是注冊。我將 Netflix Eureka 引入這個項目,當(dāng)客戶端需要負(fù)責(zé)確定可用的服務(wù)實例(使用注冊服務(wù)器)的位置和跨平臺的負(fù)載均衡請求時,Eureka 就是客戶端發(fā)現(xiàn)模式的一個很好例子。

使用 Spring Boot,你只需使用 spring-cloud-starter-eureka-server 依賴、@EnabledEurekaServer 注解和簡單的配置屬性就能輕松構(gòu)建 Eureka 注冊中心(Eureka Registry)。

使用 @EnabledDiscoveryClient 注解和帶有應(yīng)用名稱的 bootstrap.yml 來啟用客戶端支持:

spring:
  application:
    name: notification-service

現(xiàn)在,在應(yīng)用啟動時,它將向 Eureka 服務(wù)器注冊并提供元數(shù)據(jù),如主機(jī)和端口、健康檢查指示器 URL、主頁等信息。Eureka 接收來自從某服務(wù)的每個實例的心跳消息。如果心跳失敗超過配置的時間限制,該實例將從注冊表中刪除。

此外,Eureka 還提供了一個簡單的界面,你可以通過它來跟蹤運(yùn)行中的服務(wù)和可用實例的數(shù)量:http://localhost:8761

Eureka儀表盤

負(fù)載均衡器、斷路器和 HTTP 客戶端

Netflix OSS 提供了另一套很棒的工具。

Ribbon

Ribbon 是一個客戶端負(fù)載均衡器,可以很好地控制 HTTP 和 TCP 客戶端的行為。與傳統(tǒng)的負(fù)載均衡器相比,每次線上調(diào)用都不需要額外的跨越 —— 你可以直接請求所需的服務(wù)。

它與 Spring Cloud 和服務(wù)發(fā)現(xiàn)是集成在一起的,可開箱即用。Eureka 客戶端提供了可用服務(wù)器的動態(tài)列表,因此 Ribbon 可以在它們之間進(jìn)行平衡。

Hystrix

Hystrix 是斷路器模式的一種實現(xiàn)。它可以通過網(wǎng)絡(luò)訪問依賴來控制延遲和故障,旨在能在具有大量微服務(wù)的分布式環(huán)境中停止級聯(lián)故障。這有助于快速失敗并盡快恢復(fù) —— 自我修復(fù)在容錯系統(tǒng)中是非常重要的。

除了斷路器控制,在使用 Hystrix,你可以添加一個備用方法,在主命令失敗的情況下,該方法將被調(diào)用以獲取默認(rèn)值。

此外,Hystrix 生成每個命令的執(zhí)行結(jié)果和延遲的指標(biāo)信息,我們可以用它來監(jiān)視系統(tǒng)的行為。

Feign

Feign 是一個聲明式 HTTP 客戶端,能與 Ribbon 和 Hystrix 無縫集成。實際上,通過一個 spring-cloud-starter-feign 依賴和 @EnabledFeignClients 注解,你可以使用一整套負(fù)載均衡器、斷路器和 HTTP 客戶端,并附帶一個合理的的默認(rèn)配置。

以下是賬戶服務(wù)的示例:

@FeignClient(name = "statistics-service")
public interface StatisticsServiceClient {
    @RequestMapping(method = RequestMethod.PUT, value = "/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    void updateStatistics(@PathVariable("accountName") String accountName, Account account);
}
  • 你需要的只是一個接口
  • 你可以在 Spring MVC 控制器和 Feign 方法之間共享 @RequestMapping 部分
  • 以上示例僅指定所需要的服務(wù) ID —— statistics-service,這得益于 Eureka 的自動發(fā)現(xiàn)(但顯然你可以使用特定的 URL 訪問任何資源)。

監(jiān)控儀表盤

在這個項目配置中,每一個集成了 Hystrix 的微服務(wù)都通過 Spring Cloud Bus(通過 AMQP broker)將指標(biāo)推送到 Turbine。監(jiān)控項目只是一個使用了 TurbineHystrix 儀表盤的小型 Spring Boot 應(yīng)用。

讓我們看看系統(tǒng)在負(fù)載下的行為:賬戶服務(wù)調(diào)用統(tǒng)計服務(wù)和它在一個變化的模擬延遲下的響應(yīng)。響應(yīng)超時閾值設(shè)置為 1 秒。

監(jiān)控儀表盤

日志分析

當(dāng)你嘗試查找分布式環(huán)境中的問題時,集中式日志記錄就顯得非常有用。Elasticsearch、Logstash 和 Kibana 技術(shù)棧可讓你輕松搜索和分析日志、利用率和網(wǎng)絡(luò)活動數(shù)據(jù)。在我的另一個項目中已經(jīng)有現(xiàn)成的 Docker 配置。

安全

高級安全配置已經(jīng)超過了此概念性項目的范圍。為了模擬真實系統(tǒng),請考慮使用 HTTPS 和 JCE 密鑰庫來加密微服務(wù)的密碼和配置服務(wù)器的 properties 內(nèi)容(有關(guān)詳細(xì)信息,請參閱文檔)。

基礎(chǔ)設(shè)施自動化

部署微服務(wù)比部署單一的應(yīng)用的流程要復(fù)雜得多,因為它們相互依賴,因此擁有完全基礎(chǔ)設(shè)置自動化是非常重要的。我們可以通過持續(xù)交付的方式獲得以下好處:

  • 隨時發(fā)布軟件的能力。
  • 任何構(gòu)建都可能成為一個發(fā)行版本。
  • 構(gòu)建工件(artifact)一次,根據(jù)需要進(jìn)行部署。

這是一個簡單的持續(xù)交付工作流程,該項目的實現(xiàn):

在此配置中,Travis CI 為每一次的 Git 推送創(chuàng)建了標(biāo)簽鏡像。因此,每個微服務(wù)在 Docker Hub 上的都會有一個 latest 鏡像,而較舊的鏡像則使用 Git 提交的哈希進(jìn)行標(biāo)記。在需要時,可以輕松部署任何一個鏡像,并快速回滾。

基礎(chǔ)設(shè)施自動化

如何全部運(yùn)行?

這真的很簡單,我建議你嘗試一下。請記住,你將要啟動 8 個 Spring Boot 應(yīng)用、4 個 MongoDB 實例和一個 RabbitMq。請確保你的機(jī)器上有 4GB 的內(nèi)存來運(yùn)行網(wǎng)關(guān)、注冊中心、配置中心、認(rèn)證服務(wù)和賬戶中心這些重要的服務(wù)。

運(yùn)行之前

  • 安裝 Docker 和 Docker Compose
  • 配置環(huán)境變量:CONFIG_SERVICE_PASSWORD, NOTIFICATION_SERVICE_PASSWORD, STATISTICS_SERVICE_PASSWORD, ACCOUNT_SERVICE_PASSWORD, MONGODB_PASSWORD

生產(chǎn)模式

這種模式下,所有最新的鏡像都將從 Docker Hub 上拉取。只需要復(fù)制 docker-compose.yml 并執(zhí)行 docker-compose up -d 即可。

開發(fā)模式

如果你想自己構(gòu)建鏡像(例如,修改部分代碼),你需要克隆所有倉庫(repository)并使用 Mavne 構(gòu)建工件(artifact)。然后,運(yùn)行 docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

docker-compose.dev.yml 繼承了 docker-compose.yml,附帶額外配置,可在本地構(gòu)建鏡像,并暴露所有容器端口以方便開發(fā)。

重要的端點(Endpoint)

  • localhost:80 —— 網(wǎng)關(guān)
  • localhost:8761 —— Eureka儀表盤
  • localhost:9000 —— Hystrix儀表盤
  • localhost:8989 —— Turbine stream(Hystrix 儀表盤來源)
  • localhost:15672 —— RabbitMq管理

注意

所有 Spring Boot 應(yīng)用都需要在配置服務(wù)器運(yùn)行時才能啟動。得益于 Spring Boot 的 fail-fast 屬性和 docker-compsoe 的 restart:always 選項,我們可以同時啟動所有容器。這意味著所有依賴的容器將嘗試重啟,直到配置服務(wù)器啟動運(yùn)行為止。

此外,服務(wù)發(fā)現(xiàn)機(jī)制在所有應(yīng)用啟動后需要一段時間。在實例、Eureka 服務(wù)器和客戶端的本地緩存中都具有相同的元數(shù)據(jù)之前,任何服務(wù)都不可用于客戶端發(fā)現(xiàn),因此可能需要 3 次心跳。默認(rèn)的心跳周期為 30 秒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容