如何使用Spring Boot、Spring Cloud、Docker和Netflix的一些開源工具來構建一個微服務架構。
本文通過使用Spring Boot、Spring Cloud和Docker構建的概念型應用示例,提供了了解常見的微服務架構模式的起點。
該代碼可以在Github上獲得,並且在Docker Hub上提供了鏡像。您只需要一個命令即可啟動整個系統。
我選擇了一個老項目作為這個系統的基礎,它的后端以前是單一應用。此應用提供了處理個人財務、整理收入開銷、管理儲蓄、分析統計和創建簡單預測等功能。
功能服務
整個應用分解為三個核心微服務。它們都是可以獨立部署的應用,圍繞着某些業務功能進行組織。
使用Spring Cloud和Docker構建微服務架構
賬戶服務
包含一般用戶輸入邏輯和驗證:收入/開銷記錄、儲蓄和賬戶設置。
方法 | 路徑 | 描述 | 用戶驗證 | UI可用 |
---|---|---|---|---|
GET | /accounts/ | 獲取指定賬戶數據 | ||
GET | /accounts/current | 獲取當前賬戶數據 | x | x |
GET | /accounts/demo | 獲取演示賬戶數據(預先填入收入/開銷記錄等) | x | |
PUT | /accounts/current | 保存當前賬戶數據 | x | x |
POST | /accounts/ | 注冊新賬戶 | x |
統計服務
計算主要的統計參數,並捕獲每一個賬戶的時間序列。數據點包含基於貨幣和時間段正常化后的值。該數據可用於跟蹤賬戶生命周期中的現金流量動態。
方法 | 路徑 | 描述 | 用戶驗證 | UI可用 |
---|---|---|---|---|
GET | /statistics/ | 獲取指定賬戶統計數據 | ||
GET | /statistics/current | 獲取當前賬戶的統計數據 | x | x |
GET | /statistics/demo | 獲取演示賬戶統計數據 | x | |
PUT | /statistics/ | 創建或更新指定賬戶的時間序列數據點。 |
通知服務
存儲用戶的聯系信息和通知設置(如提醒和備份頻率)。安排工作人員從其它服務收集所需的信息並向訂閱的客戶發送電子郵件。
方法 | 路徑 | 描述 | 用戶驗證 | UI可用 |
---|---|---|---|---|
GET | /notifications/settings/current | 獲取當前賬戶的通知i設置 | x | x |
PUT | /notifications/settings/current | 保存當前賬戶的通知設置 | x | x |
注意:
-
每一個微服務擁有自己的數據庫,因此沒有辦法繞過API直接訪問持久數據。
-
在這個項目中,我使用MongoDB作為每一個服務的主數據庫。擁有一個多種類持久化架構(polyglot persistence architecture)也是很有意義的。
-
服務間(Service-to-service)通信是非常簡單的:微服務僅使用同步的REST API進行通信。現實中的系統的常見做法是使用互動風格的組合。例如,執行同步的GET請求檢索數據,並通過消息代理(broker)使用異步方法執行創建/更新操作,以便解除服務和緩沖消息之間的耦合。然而,這帶給我們是最終的一致性。
基礎設施服務
分布式系統中常見的模式,可以幫助我們描述核心服務是怎樣工作的。Spring Cloud提供了強大的工具,可以增強Spring Boot應用的行為來實現這些模式。我會簡要介紹一下:
基礎設施服務
配置服務
Spring Cloud Config是分布式系統的水平擴展集中式配置服務。它使用了當前支持的本地存儲、Git和Subversion等可拔插存儲庫層(repository layer)。
在此項目中,我使用了native profile,它簡單地從本地classpath下加載配置文件。您可以在配置服務資源中查看shared目錄。現在,當通知服務請求它的配置時,配置服務將響應回shared/notification-service.yml和shared/application.yml(所有客戶端應用之間共享)。
客戶端使用
只需要使用sprng-cloud-starter-config依賴構建Spring Boot應用,自動配置將會完成其它工作。
現在您的應用中不需要任何嵌入的properties,只需要提供有應用名稱和配置服務url的bootstrap.yml即可:
-
spring:
-
application:
-
name:notification-service
-
cloud:
-
config:
-
uri:http://config:8888
-
fail-fast:true
使用Spring Cloud Config,您可以動態更改應用配置:
比如,EmailService bean使用了@RefreshScope注解。這意味着您可以更改電子郵件的內容和主題,而無需重新構建和重啟通知服務應用。
首先,在配置服務器中更改必要的屬性。然后,對通知服務執行刷新請求:
-
curl -H "Authorization: Bearer #token#"-XPOST
-
http://127.0.0.1:8000/notifications/refresh。
您也可以使用webhook來自動執行此過程。
注意:
-
動態刷新存在一些限制。@RefreshScope不能和@Configuraion類一同工作,並且不會作用於@Scheduled方法。
-
fail-fast屬性意味着如果Spring Boot應用無法連接到配置服務,將會立即啟動失敗。當您一起啟動所有應用時,這非常有用。
-
下面有重要的安全提示
授權服務
負責授權的部分被完全提取到單獨的服務器,它為后端資源服務提供OAuth2令牌。授權服務器用於用戶授權以及在周邊內進行安全的機器間通信。
在此項目中,我使用密碼憑據作為用戶授權的授權類型(因為它僅由本地應用UI使用)和客戶端憑據作為微服務授權的授權類型。
Spring Cloud Security提供了方便的注解和自動配置,使其在服務器端或者客戶端都可以很容易地實現。您可以在文檔中了解到更多信息,並在授權服務器代碼中檢查配置明細。
從客戶端來看,一切都與傳統的基於會話的授權完全相同。您可以從請求中檢索Principal對象、檢查用戶角色和其它基於表達式訪問控制和@PreAuthorize注解的內容。
PiggyMetrics(帳戶服務、統計服務、通知服務和瀏覽器)中的每一個客戶端都有一個范圍:用於后台服務的服務器、用於瀏覽器展示的UI。所以我們也可以保護控制器避免受到外部訪問,例如:
-
@PreAuthorize("#oauth2.hasScope('server')")
-
@RequestMapping(value ="accounts/",method =RequestMethod.GET)
-
publicList<DataPoint>getStatisticsByAccountName(@PathVariableStringname){
-
returnstatisticsService.findByAccountName(name);
-
}
API網關
您可以看到,有三個核心服務。它們向客戶端暴露外部API。在現實系統中,這個數量可以非常快速地增長,同時整個系統將變得非常復雜。實際上,一個復雜頁面的渲染可能涉及到數百個服務。
理論上,客戶端可以直接向每個微服務直接發送請求。但是這種方式是存在挑戰和限制的,如果需要知道所有端點的地址,分別對每一段信息執行http請求,將結果合並到客戶端。另一個問題是,這不是web友好協議,可能只在后端使用。
通常一個更好的方法是使用API網關。它是系統的單個入口點,用於通過將請求路由到適當的后端服務或者通過調用多個后端服務並聚合結果來處理請求。此外,它還可以用於認證、insights、壓力測試、金絲雀測試(canary testing)、服務遷移、靜態響應處理和主動變換管理。
Netflix開源這樣的邊緣服務,現在用Spring Cloud,我們可以用一個@EnabledZuulProxy注解來啟用它。在這個項目中,我使用Zuul存儲靜態內容(UI應用),並將請求路由到適當的微服務。以下是一個簡單的基於前綴(prefix-based)路由的通知服務配置:
-
zuul:
-
routes:
-
notification-service:
-
path:/notifications/**
-
serviceId:notification-service
-
stripPrefix:false
這意味着所有以/notification開頭的請求將被路由到通知服務。您可以看到,里面沒有硬編碼的地址。Zuul使用服務發現機制來定位通知服務實例以及斷路器和負載均衡器,如下所述。
服務發現
另一種常見的架構模式是服務發現。它允許自動檢測服務實例的網絡位置,由於自動擴展、故障和升級,它可能會動態分配地址。
服務發現的關鍵部分是注冊。我使用Netflix Eureka進行這個項目,當客戶端需要負責確定可以用的服務實例(使用注冊服務器)的位置和跨平台的負載均衡請求時,Eureka就是客戶端發現模式的一個很好的例子。
使用Spring Boot,您可以使用spring-cloud-starter-eureka-server依賴、@EnabledEurekaServer注解和簡單的配置屬性輕松構建Eureka注冊中心(Eureka Registry)。
使用@EnabledDiscoveryClient注解和帶有應用名稱的bootstrap.yml來啟用客戶端支持:
-
spring:
-
application:
-
name:notification-service
現在,在應用啟動時,它將向Eureka服務器注冊並提供元數據,如主機和端口、健康指示器URL、主頁等。Eureka接收來自從屬於某服務的每個實例的心跳消息。如果心跳失敗超過配置的時間表,該實例將從注冊表中刪除。
此外,Eureka還提供了一個簡單的界面,您可以通過它來跟蹤運行中的服務和可用實例的數量:http://localhost:8761
Eureka儀表盤
負載均衡器、斷路器和Http客戶端
Netflix OSS提供了另一套很棒的工具。
Ribbon
Ribbon是一個客戶端負載均衡器,可以很好地控制HTTP和TCP客戶端的行為。與傳統的負載均衡器相比,每次線上調用都不需要額外的跳躍——您可以直接聯系所需的服務。
它與Spring Cloud和服務發現是集成在一起的,可開箱即用。Eureka客戶端提供了可用服務器的動態列表,因此Ribbon可以在它們之間進行平衡。
Hystrix
Hystrix是斷路器模式的一種實現,它可以通過網絡訪問依賴來控制延遲和故障。中心思想是在具有大量微服務的分布式環境中停止級聯故障。這有助於快速失敗並盡快恢復——自我修復在容錯系統中是非常重要的。
除了斷路器控制,在使用Hystrix,您可以添加一個備用方法,在主命令失敗的情況下,該方法將被調用以獲取默認值。
此外,Hystrix生成每個命令的執行結果和延遲的度量,我們可以用它來監視系統的行為。
Feign
Feign是一個聲明式HTTP客戶端,能與Ribbon和Hystrix無縫集成。實際上,通過一個spring-cloud-starter-feign依賴和@EnabledFeignClients注解,您可以使用一整套負載均衡器、斷路器和HTTP客戶端,並附帶一個合理的的默認配置。
以下是賬戶服務的示例:
-
@FeignClient(name ="statistics-service")
-
publicinterfaceStatisticsServiceClient{
-
@RequestMapping(method =RequestMethod.PUT,value ="/statistics/",consumes =MediaType.APPLICATION_JSON_UTF8_VALUE)
-
voidupdateStatistics(@PathVariable("accountName")StringaccountName,Accountaccount);
-
}
-
您需要的只是一個接口
-
您可以在Spring MVC控制器和Feign方法之間共享@RequestMapping部分
-
以上示例僅指定所需要的服務ID——statistics-service,這得益於Eureka的自動發現(但顯然您可以使用特定的URL訪問任何資源)。
監控儀表盤
在這個項目配置中,Hystrix的每一個微服務都通過Spring Cloud Bus(通過AMQP broker)將指標推送到Turbine。監控項目只是一個使用了Turbine和Hystrix儀表盤的小型Spring Boot應用。
讓我們看看系統行為在負載下:賬戶服務調用統計服務和它在一個變化的模擬延遲下的響應。響應超時閾值設置為1秒。
監控儀表盤
日志分析
集中式日志記錄在嘗試查找分布式環境中的問題時非常有用。Elasticsearch、Logstash和Kibana技術棧可讓您輕松搜索和分析您的日志、利用率和網絡活動數據。在我的另一個項目中已經有現成的Docker配置。
安全
高級安全配置已經超過了此概念性項目的范圍。為了更真實地模擬真實系統,請考慮使用https和JCE密鑰庫來加密微服務密碼和配置服務器的properties內容(有關詳細信息,請參閱文檔)。
基礎設施自動化
部署微服務比部署單一的應用的流程要復雜得多,因為它們相互依賴。擁有完全基礎設置自動化是非常重要的。我們可以通過持續交付的方式獲得以下好處:
-
隨時發布軟件的能力。
-
任何構建都可能最終成為一個發行版本。
-
構建工件(artifact)一次,根據需要進行部署。
這是一個簡單的持續交付工作流程,在這個項目的實現:
在此配置中,Travis CI為每一個成功的Git推送創建了標記鏡像。因此,每一個微服務在Docker Hub上的都會有一個latest鏡像,而較舊的鏡像則使用Git提交的哈希進行標記。如果有需要,可以輕松部署任何一個,並快速回滾。
基礎設施自動化
如何運行全部?
這真的很簡單,我建議您嘗試一下。請記住,您將要啟動8個Spring Boot應用、4個MongoDB實例和RabbitMq。確保您的機器上有4GB的內存。您可以隨時通過網關、注冊中心、配置、認證服務和賬戶中心運行重要的服務。
運行之前
安裝Docker和Docker Compose。配置環境變量:
-
CONFIG_SERVICE_PASSWORD,NOTIFICATION_SERVICE_PASSWORD,STATISTICS_SERVICE_PASSWORD,
-
ACCOUNT_SERVICE_PASSWORD,MONGODB_PASSWORD
生產模式
在這種模式下,所有最新的鏡像都將從Docker Hub上拉取。只需要復制docker-compose.yml並執行docker-compose up -d即可。
開發模式
如果您想自己構建鏡像(例如,在代碼中進行一些修改),您需要克隆所有倉庫(repository)並使用Mavne構建工件(artifact)。然后,運行
-
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
docker-compose.dev.yml繼承了docker-compose.yml,附帶額外配置,可在本地構建鏡像,並暴露所有容器端口以方便開發。
重要的端點(Endpoint)
-
localhost:80 —— 網關
-
localhost:8761 —— Eureka儀表盤
-
localhost:9000 —— Hystrix儀表盤
-
localhost:8989 —— Turbine stream(Hystrix儀表盤來源)
-
localhost:15672 —— RabbitMq管理
注意:所有Spring Boot應用都需要運行配置服務器才能啟動。得益於Spring Boot的fail-fast屬性和docker-compsoe的restart:always選項,我們可以同時啟動所有容器。這意味着所有依賴的容器將嘗試重新啟動,直到配置服務器啟動運行為止。
此外,服務發現機制在所有應用啟動后需要一段時間。在實例、Eureka服務器和客戶端在其本地緩存中都具有相同的元數據之前,任何服務都不可用於客戶端發現,因此可能需要3次心跳。默認的心跳周期為30秒。
本文采用CC BY 3.0 CN協議進行許可,歸原創作者oopsguy所有, 在未標明作者和來源的情況下不得轉載、引用。
課程內容(1.5 小時)
RxJava
回顧傳統觀察者模式、反應器模式等設計模式,說明這些模式與 Reactive 模式的聯系,更好地解讀 Reactive Java 框架相關實現源碼,如:Observer、Subscriber等
Netflix Hystrix
Netflix Hystrix 作為 Spring Cloud Hystrix 底層實現,結合 RxJava 框架進行實現,如核心接口HystrixCommand,屬性設置HystrixCommandProperties以及配置信息HystrixConfiguration等
Spring Cloud Hystrix
詳細解讀@EnableCircuitBreaker 處理過程,以及@HystrixCommand AOP 攔截實現等