1. 概述
Spring Cloud 是構建健壯雲應用程序的框架。該框架通過提供解決移動到分佈式環境時所面臨的許多常見問題的解決方案,從而簡化了應用程序的開發。
使用微服務架構運行的應用程序旨在簡化開發、部署和維護。應用程序分解的特性使開發人員能夠一次專注於一個問題,而無需影響系統中的其他部分,從而可以引入改進。
然而,採用微服務方法時,也會出現一些挑戰:
- 將配置外部化,以便在更改時無需重新構建服務
- 服務發現
- 隱藏部署在不同主機上的服務的複雜性
在本文中,我們將構建五個微服務:一個配置服務器、一個發現服務器、一個網關服務器、一個圖書服務,以及最後是一個評分服務。這五個微服務構成了一個堅實的基礎應用程序,可以開始雲開發,並解決上述挑戰。
2. 配置服務器
在開發雲應用程序時,維護和分發配置是一個問題。我們不想花費時間在每個環境上配置服務,也不希望通過將配置嵌入到應用程序中來承擔安全風險。
為了解決這個問題,我們將所有配置集中到一個單一的 Git 倉庫中,並將其連接到一個應用程序,該應用程序管理所有應用程序的配置。我們將設置一個非常簡單的實現。
要了解更多詳細信息並查看更復雜的示例,請查看我們的 Spring Cloud Configuration 文章。
2.1. 環境搭建
前往 https://start.spring.io,選擇 Maven 和 Spring Boot 2.7.x。
設置 Artifact 為 “config”。 在依賴項部分,搜索 “config server” 並添加該模塊。 然後點擊 generate 按鈕,我們就能下載一個預配置的項目壓縮包,可以直接使用。
或者,我們可以生成一個 Spring Boot 項目,並手動將依賴項添加到 POM 文件中。
這些依賴項將會在所有項目中共享:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>讓我們添加對配置服務器的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>如參考,最新版本可在 Maven Central 上找到:(spring-cloud-dependencies, test, config-server).
2.2. Spring 配置
為了啓用配置服務器,我們需要向主應用程序類添加一些註解:
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {...}@EnableConfigServer 將我們的應用程序變成配置服務器。
2.3. 屬性
讓我們在 src/main/resources 中添加 application.properties:
server.port=8081
spring.application.name=config
spring.cloud.config.server.git.uri=file://${user.home}/application-config配置服務器最重要的設置是 git.uri 參數。當前該參數設置為一個相對文件路徑,通常在 Windows 上解析為 c:\Users\{username}\,在 *nix 系統上解析為 /Users/{username}/。該屬性指向一個 Git 倉庫,其中存儲了所有其他應用程序的屬性文件。如果需要,可以將其設置為絕對文件路徑。
提示:在 Windows 機器上,請在值前加上“file:///”,在 *nix 系統上則使用“file://”。
2.4. Git 倉庫
導航到由 <em style="line-height: 1.5">spring.cloud.config.server.git.uri</em> 定義的文件夾,並添加 application-config 文件夾。進入該文件夾,然後輸入 <em style="line-height: 1.5">git init</em>。這將初始化一個 Git 倉庫,用於存儲文件並跟蹤其更改。
2.5. 運行
運行配置服務器並確保其正常工作。 在命令行中輸入 mvn spring-boot:run。 這將啓動服務器。
您應該看到以下輸出,指示服務器正在運行:
Tomcat started on port(s): 8081 (http)2.6. 啓動配置
在後續的服務器中,我們將希望它們的應用程序屬性由該配置服務器管理。為此,我們需要進行一些“原始雞和蛋”的操作:配置每個知道如何與該服務器通信的應用程序的屬性。
這是一個啓動過程,每個應用程序都將有一個名為 bootstrap.properties 的文件。 它將包含與 application.properties 類似屬性,但具有一個特殊之處:
一個父級 Spring ApplicationContext 首先加載 bootstrap.properties。 這一點至關重要,因為這樣才能使 Config Server 能夠管理 application.properties 中的屬性。 這種特殊的 ApplicationContext 還會解密任何加密的應用程序屬性。
這是 bootstrap.properties 的示例:
spring.cloud.config.name=rating-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/<div>
<p><strong></strong><br> 保持這些屬性文件分開維護是明智之舉。</strong> <em>bootstrap.properties</em> 用於準備配置服務器,<em>application.properties</em> 用於我們應用程序的特定屬性。 實際上,雖然可以把應用程序屬性放在<em>bootstrap.properties</em>中。</p>
<p>最後,由於配置服務器管理我們的應用程序屬性,人們可能會想,為什麼還要有<em>application.properties</em>呢?答案是,這些仍然可以作為配置服務器可能沒有的默認值而派上用場。</p>
</div>
3. 發現 (Discovery)
現在我們已經完成了配置,需要一種方法讓所有服務器能夠互相發現彼此。我們將通過設置 Eureka 發現服務器來解決這個問題。由於我們的應用程序可能在任何 IP/端口組合上運行,因此我們需要一箇中心地址註冊表,作為應用程序地址查找服務。
當新服務器被配置時,它將與發現服務器進行通信,並註冊其地址,以便其他服務器能夠與之通信。 這樣,其他應用程序就可以利用此信息進行請求。
要了解更多詳細信息並查看更復雜的發現實現,請參閲 Spring Cloud Eureka 文章。
3.1. 部署準備
再次訪問 start.spring.io。 將 Artifact 設置為“discovery”。 搜索“eureka server”並添加該依賴項。 搜索“config client”並添加該依賴項。 最後,生成項目。
或者,我們可以創建一個 Spring Boot項目,複製 config server 的 POM文件內容,並替換為以下依賴項:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
為了參考,您可以在 Maven Central 上找到這些包:config-client、eureka-server。
3.2. Spring 配置
讓我們將 Java 配置添加到主類中:
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {...}@EnableEurekaServer 將此服務器配置為使用 Netflix Eureka 作為發現服務器。 Spring Boot 將自動檢測 classpath 上的配置依賴,並從配置服務器查找配置。
3.3. 屬性
現在我們將添加兩個屬性文件:
首先,我們將 <em >bootstrap.properties</em> 添加到 <em >src/main/resources</em> 中:
spring.cloud.config.name=discovery
spring.cloud.config.uri=http://localhost:8081這些屬性將允許發現服務器在啓動時查詢配置服務器。
其次,我們把 discovery.properties 添加到我們的 Git 倉庫中。
spring.application.name=discovery
server.port=8082
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false文件名必須與 spring.application.name 屬性相匹配。
此外,我們正在告知這個服務器它正在運行在默認區域,這與配置客户端的區域設置相符。我們還告訴服務器不要與另一個發現實例註冊。
在生產環境中,我們會有多個這樣的配置,以在發生故障時提供冗餘,並且該設置也將為真。
讓我們將文件提交到 Git 倉庫。否則,文件將無法被檢測到。
3.4. 將依賴項添加到配置服務器
將此依賴項添加到配置服務器的 POM 文件中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>為了參考,您可以在 Maven Central 上找到該包:eureka-client。
將這些屬性添加到配置服務器的 application.properties 文件中,該文件位於 src/main/resources 目錄中:
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/3.5. 運行
使用相同的命令啓動發現服務器:mvn spring-boot:run。命令行輸出應包含:
Fetching config from server at: http://localhost:8081
...
Tomcat started on port(s): 8082 (http)停止並重新運行配置服務。如果一切正常,輸出結果應如下所示:
DiscoveryClient_CONFIG/10.1.10.235:config:8081: registering service...
Tomcat started on port(s): 8081 (http)
DiscoveryClient_CONFIG/10.1.10.235:config:8081 - registration status: 2044. Gateway
現在我們已經解決了配置和發現的問題,但仍然存在客户端無法訪問所有應用程序的問題。
如果在分佈式系統中保留現狀,那麼我們將不得不管理複雜的 CORS 標頭以允許客户端跨域請求。我們可以通過創建一個網關服務器來解決這個問題。該服務器將作為反向代理,將客户端的請求轉發到我們的後端服務器。
網關服務器是微服務架構中的一個理想應用,因為它允許所有響應從單個主機發起。這將消除 CORS 的需求,併為處理常見的難題,如身份驗證提供一個方便的位置。
4.1. 環境搭建
現在你應該已經熟悉了流程。請訪問 https://start.spring.io。 將 Artifact 設置為“gateway”。 搜索“gateway”並添加該依賴項。 搜索“config client”並添加該依賴項。 搜索“eureka discovery client”並添加該依賴項。 最後,生成該項目。
或者,我們可以創建一個使用以下依賴項的 Spring Boot 應用:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>如參考,我們可以從 Maven Central 找到該包 (config-client, eureka-client, zuul).
4.2. Spring 配置
讓我們將配置添加到主類中:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GatewayApplication {...}4.3. 屬性
現在我們將添加兩個屬性文件:
bootstrap.properties 在 src/main/resources 中:
spring.cloud.config.name=gateway
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/在我們的 Git 倉庫中,請查閲 gateway.properties。
spring.application.name=gateway
server.port=8080
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5請務必將更改提交到倉庫!
4.4. 運行
運行配置和發現應用程序,等待配置應用程序與發現服務器註冊完成。如果它們已經在運行,則無需重啓它們。完成這些步驟後,運行網關服務器。網關服務器應在 8080 端口啓動並將其自身註冊到發現服務器。控制枱輸出應包含:
Fetching config from server at: http://10.1.10.235:8081/
...
DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080: registering service...
DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080 - registration status: 204
Tomcat started on port(s): 8080 (http)一個容易犯的錯誤是,在配置服務器與 Eureka 註冊之前啓動服務器。在這種情況下,我們會看到以下日誌輸出:
Fetching config from server at: http://localhost:8888這是配置服務器的默認 URL 和端口,表明我們的發現服務在配置請求時沒有地址。請稍等幾秒鐘,然後再次嘗試。一旦配置服務器已與 Eureka 註冊,問題就會解決。
5. 圖書服務
在微服務架構中,我們可以根據業務目標創建多個應用程序。工程師通常會根據領域將服務劃分為不同的服務。我們將遵循此模式,創建一個圖書服務,用於處理應用程序中的所有圖書操作。
5.1. 環境搭建
再次確認。 導航至 https://start.spring.io。 將 Artifact 設置為“book-service”。 搜索“web”並添加該依賴項。 搜索“config client”並添加該依賴項。 搜索“eureka discovery client”並添加該依賴項。 生成該項目。
或者,將以下依賴項添加到項目中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>如參考,我們可以從 Maven Central 找到該包(config-client, eureka-client, web).
5.2. Spring 配置
讓我們修改我們的主類:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@RequestMapping("/books")
public class BookServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookServiceApplication.class, args);
}
private List<Book> bookList = Arrays.asList(
new Book(1L, "Baeldung goes to the market", "Tim Schimandle"),
new Book(2L, "Baeldung goes to the park", "Slavisa")
);
@GetMapping("")
public List<Book> findAllBooks() {
return bookList;
}
@GetMapping("/{bookId}")
public Book findBook(@PathVariable Long bookId) {
return bookList.stream().filter(b -> b.getId().equals(bookId)).findFirst().orElse(null);
}
}我們還添加了一個 REST 控制器,並使用來自我們的屬性文件設置的字段,以返回我們在配置期間設置的值。
現在,讓我們添加書本 POJO:
public class Book {
private Long id;
private String author;
private String title;
// standard getters and setters
}5.3. 屬性
現在,我們需要添加我們的兩個屬性文件:
<em style="font-style: italic;">bootstrap.properties</em> 在 <em style="font-style: italic;">src/main/resources</em> 中:
spring.cloud.config.name=book-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/請查看我們 Git 倉庫中的 book-service.properties 文件:
spring.application.name=book-service
server.port=8083
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/讓我們將更改提交到存儲庫。
5.4. 啓動
一旦所有其他應用程序啓動完畢,就可以啓動 Book 服務。控制枱輸出應如下所示:
DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083: registering service...
DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083 - registration status: 204
Tomcat started on port(s): 8083 (http)一旦服務啓動,我們就可以使用瀏覽器訪問我們剛剛創建的端點。 導航到 http://localhost:8080/book-service/books,我們將會收到一個包含我們先前在控制器中添加的兩本書的 JSON 對象。 請注意,我們並非直接在端口 8083 上訪問 book service,而是通過網關服務器進行訪問。
6. 評分服務
與我們的圖書服務類似,我們的評分服務將是一個領域驅動服務,負責處理與評分相關的操作。
6.1. 搭建環境
再次確認。導航至 https://start.spring.io。將 Artifact 設置為 “rating-service”。搜索 “web” 並添加該依賴項。搜索 “config client” 並添加該依賴項。搜索 “eureka discovery client“ 並且添加該依賴項。然後,生成該項目。
或者,將以下依賴項添加到項目中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>如參考,我們可以從 Maven Central 找到該包(config-client, eureka-client, web).
6.2. Spring 配置
讓我們修改我們的主類:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@RequestMapping("/ratings")
public class RatingServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RatingServiceApplication.class, args);
}
private List<Rating> ratingList = Arrays.asList(
new Rating(1L, 1L, 2),
new Rating(2L, 1L, 3),
new Rating(3L, 2L, 4),
new Rating(4L, 2L, 5)
);
@GetMapping("")
public List<Rating> findRatingsByBookId(@RequestParam Long bookId) {
return bookId == null || bookId.equals(0L) ? Collections.EMPTY_LIST : ratingList.stream().filter(r -> r.getBookId().equals(bookId)).collect(Collectors.toList());
}
@GetMapping("/all")
public List<Rating> findAllRatings() {
return ratingList;
}
}我們還添加了一個 REST 控制器,並使用來自我們的屬性文件設置的字段來返回一個在配置期間設置的值。
現在,讓我們添加評分 POJO:
public class Rating {
private Long id;
private Long bookId;
private int stars;
//standard getters and setters
}6.3. 屬性
現在我們需要添加我們的兩個屬性文件:
bootstrap.properties 在 src/main/resources 目錄下:
spring.cloud.config.name=rating-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/在我們的Git倉庫中:
spring.application.name=rating-service
server.port=8084
eureka.client.region=default
eureka.client.registryFetchIntervalSeconds=5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/讓我們將更改提交到存儲庫。
6.4. 運行
一旦所有其他應用程序啓動完畢,我們就可以啓動評分服務。控制枱輸出應如下所示:
DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083: registering service...
DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083 - registration status: 204
Tomcat started on port(s): 8084 (http)一旦服務啓動,我們就可以使用瀏覽器訪問我們剛剛創建的端點。 訪問 http://localhost:8080/rating-service/ratings/all, 我們將收到包含所有評分的 JSON 數據。 請注意,我們並非直接訪問評分服務,而是通過網關服務器進行訪問。
7. 結論
現在我們已經能夠將各種 Spring Cloud 組件連接成一個可運行的微服務應用程序。這為我們構建更復雜的應用程序奠定了基礎。