前一篇文章《微服務操作模型》中,我們定義了微服務使用的操作模型。這篇文章中,我們將開始使用Spring Cloud和Netflix OSS實現這一模型,包含核心部分:服務發現(Service Discovery)、動態路由(Dynamic Routing)、負載均衡(Load Balancing),和邊緣伺服器(Edge Server),其他部分在後面的文章中介紹。
我們將使用來自Spring Cloud和Netflix OSS的一些核心元件,實現在已部署的微服務互動,不必手動管理配置,如每一個微服務的埠或者手工配置路由規則等等。為了避免埠衝突,我們的微服務在啟動時,將從埠段中動態獲取可用的埠。為了方便訪問微服務,我們將使用Edge Server提供一個微服務的訪問入口點。
在簡要介紹Spring Cloud和Netflix OSS元件之後,我們將描述本系列文章使用的系統,以及如何訪問原始碼,並編譯。同時,也會簡要指出原始碼中的最重要部分。最後,我們將執行一些訪問服務的測試程式碼,也會演示如何簡單地建立一個新的服務例項,獲取並使用負載均衡,所有這一些都不必手工配置。
1. Spring Cloud和Netflix OSS
Spring Cloud是spring.io家庭的一個新專案,包含一系列元件,可用來實現我們的操作模型。很大程度上而言,Spring Cloud 1.0 是基於Netflix OSS元件。在Spring環境中,Spring Cloud 非常友好地整合了Netflix 元件,使用了和Spring Boot相似的自動配置和慣例優於配置。
下表對映了操作模式中介紹的元件和我們將要使用的實際元件:
本文將包含Eureka、Ribbon和Zuul:
1/Netflix Eureka – Service Discover Server服務發現
Netflix Eureka允許微服務在執行時自我註冊
2/Netflix Ribbon-Dynamic Routing and Load Balancer動態路由和負載均衡
Netflix Ribbon可以在服務消費方執行時查詢微服務。Ribbon使用Eureka中的資訊定位合適的服務例項。如果發現了多個服務例項,Ribbon將應用負載均衡來轉發請求到可用的微服務例項。Ribbon 不作為一個單獨的服務執行,而是嵌入在每一個服務消費方中。
3/Netflix Zuul – Edge Server邊緣伺服器
Zuul是我們對外部世界的守門員,禁止任一未授權的外部請求進入。Zuul在系統內部也提供了方便的進入入口點。透過使用動態分配的埠,可以避免埠衝突,以及最小化管理成本,但是也導致服務消費方更難接入。Zuul 使用Ribbon來查詢可用的服務,並路由外部的請求到合適的服務例項。在本文中,我們將僅僅使用Zuul提供了便利的訪問入口點,安全部分在下一篇文章中討論。
備註:透過邊緣伺服器(Edge Server),可被外部訪問的微服務,在系統中可稱為API。
2. 系統架構
為了測試這些元件,我們需要一個可實施的業務系統。本文章的目標是開發實現如下系統:
上圖包含4個業務服務(綠色文字框):
1/ 三個核心服務負責處理資訊:產品、推薦和評論;
2/ 一個組合服務 product-composite,用來聚合3個核心服務的資訊,組合包含評論和推薦的產品資訊檢視;
為了支援業務服務,我們使用瞭如下基礎設施服務和元件(藍色文字框):
1/ 服務發現伺服器(Service Discovery Server – Netflix Eureka)
2/ 動態路由和負載均衡(Netflix Ribbon)
3/ 邊緣伺服器(Edge Server – Netflix Zuul)
為了強調微服務和單體應用的差異,我們將每一個服務執行在單獨的微服務程式中。在一個大系統中,如此細粒度的微服務可能並不方便。相應地,一組相關的微服務可能合併為一組,保持微服務的數量在可管理的水平,但這並不是退回到巨大的單體應用。
3. 獲取原始碼並編譯
獲取原始碼,並進行測試,需要已安裝Java SE8和Git,接著執行如下操作:
$ git clone https://github.com/callistaenterprise/blog-microservices.git
$ cd blog-microservices
$ git checkout -b B1 M1.1
將生成如下的目錄結構:
每一個元件獨立編譯(記住我們不再編譯單體應用),因此每一個元件都有自己的build檔案。我們使用Gradle編譯系統,如果你沒有安裝Gradle,build檔案將自動下載。為了簡化編譯過程,我們提供了一個小的shell指令碼,可用來編譯元件:
$ ./build-all.sh
如果在Windows環境下,你可以執行相應的bat檔案 build-all.bat。
將顯示6個log訊息,並顯示:BUILD SUCCESSFUL
4. 閱讀原始碼
快速看看關鍵的原始碼,每一個微服務開發為一個獨立的Spring Boot應用,並使用Undertow(一個輕量級的Servlet 3.1容器)作為web server。Spring MVC用來實現 REST-based服務,Spring RestTemplate 用來執行外部呼叫。如果你想更多地瞭解這些核心技術,你可以檢視相關的文章。
這裡,我們關注如何使用Spring Cloud和Netflix OSS功能。
備註:為了讓原始碼易於理解,我們特意讓實現儘量簡單。
4.1 Gradle依賴
本著Spring Boot的精髓,Spring Cloud定義了一組starter 依賴,便於引入需要的特定依賴。為了在微服務中使用Eureka和Ribbon,以及方便呼叫其他微服務,在build檔案中新增如下:
compile("org.springframework.cloud:spring-cloud-starter-eureka:1.0.0.RELEASE")
可以檢視product-service/build.gradle 獲取完整的例子。
為了搭建Eureka 伺服器,新增如下依賴:
compile('org.springframework.cloud:spring-cloud-starter-eureka-server:1.0.0.RELEASE')
完整的例子,可以檢視discovery-server/build.gradle。
4.2 基礎設施伺服器
基於Spring Cloud和Netflix OSS搭建基礎設施伺服器相當方便。例如,在一個標準的Spring Boot應用中,新增@EnableEurekaServer標註來搭建Eureka 伺服器。
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
完整的例項,可以檢視EurekaApplication.java程式碼。
搭建Zuul 伺服器,可以新增@EnableZuulProxy標註。完整的例項,可以檢視ZuulApplication.java程式碼。
透過這些簡單的標註,可以搭建一個預設的伺服器配置。根據需要,也可以透過特定的設定覆蓋預設配置。例如,我們可以透過覆蓋預設的配置,限制邊緣伺服器允許路由呼叫的微服務。預設情況下,Zuul搭建了Eureka中可以發現的每一個微服務的路由。透過如下的application.yml配置,限制了只允許訪問組合服務-product service的路由。
zuul:
ignoredServices: "*"
routes:
productcomposite:
path: /productcomposite/**
檢視edge-server/application.yml獲取完整的例子。
4.3 業務服務
透過在Spring Boot應用中,新增@EnableDiscoveryClient標註,自動註冊微服務到Eureka Server中。
@SpringBootApplication @EnableDiscoveryClient public class ProductServiceApplication { public static void main(String[] args) { SpringApplication.run(ProductServiceApplication.class, args); } }
完整的例子,可以檢視ProductServiceApplication.java。
為了查詢和呼叫微服務例項,可以使用Ribbon和Spring RestTemplate,如下所示:
@Autowired private LoadBalancerClient loadBalancer; ... public ResponseEntity<List<Recommendation>> getReviews(int productId) { ServiceInstance instance = loadBalancer.choose("review"); URI uri = instance.getUri(); ... response = restTemplate.getForEntity(url, String.class);
服務消費方只需要知道服務的名字,如上述例子中的review,Ribbon(LoadBalancerClient類)將發現服務例項,並返回URI給服務消費方。
5. 啟動系統
在本文中,我們將在本地開發環境中作為一個java程式來啟動微服務。在接下來的文章中,我們將描述如何部署微服務到雲環境和Docker容器中。
為了執行下面的一些命令,需要安裝curl和jq工具。
每一個微服務使用命令 ./gradlew bootRun 來啟動。
首先,啟動微服務基礎設施,如:
$ cd .../blog-microservices/microservices
$ cd support/discovery-server; ./gradlew bootRun
$ cd support/edge-server; ./gradlew bootRun
一旦啟動了上述基礎設施微服務,接著啟動業務微服務:
$ cd core/product-service; ./gradlew bootRun
$ cd core/recommendation-service; ./gradlew bootRun
$ cd core/review-service; ./gradlew bootRun
$ cd composite/product-composite-service; ./gradlew bootRun
如果在Windows環境下,可以執行相應的bat檔案,start-all.bat檔案。
一旦微服務啟動了,將自注冊到服務發現伺服器(Service Discovery Server - Eureka)中去,並輸出如下日誌:
DiscoveryClient ... - registration status: 204
在服務發現web 應用中,可以看到如下4個業務服務,和邊緣伺服器(Edge Server)(http://localhost:8761):
為了瞭解上述服務的更多資訊,如使用的ip地址和埠,可使用Eureka REST API:
$ curl -s -H "Accept: application/json" http://localhost:8761/eureka/apps | jq '.applications.application[] | {service: .name, ip: .instance.ipAddr, port: .instance.port."$"}' { "service": "PRODUCT", "ip": "192.168.0.116", "port": "59745" } { "service": "REVIEW", "ip": "192.168.0.116", "port": "59178" } { "service": "RECOMMENDATION", "ip": "192.168.0.116", "port": "48014" } { "service": "PRODUCTCOMPOSITE", "ip": "192.168.0.116", "port": "51658" } { "service": "EDGESERVER", "ip": "192.168.0.116", "port": "8765" }
現在,我們已經準備好進行測試了。首先,驗證可以到達我們的微服務,接著,我們建立一個新的微服務例項,並透過Ribbon在多個服務例項上實施負載均衡。
備註:在接下來的文章中,我們也會嘗試失敗的場景,演示電路斷路器(Circuit Breaker)是如何工作的。
5.1 開始測試
透過邊緣伺服器來呼叫組合服務,邊緣伺服器在埠8765(檢視application.yml檔案)。我們透過邊緣伺服器,以及路徑/productcomposite/** 可到達 productcomposite 服務。返回的組合響應如下:
$ curl -s localhost:8765/productcomposite/product/1 | jq . { "name": "name", "productId": 1, "recommendations": [ { "author": "Author 1", "rate": 1, "recommendationId": 1 }, ... ], "reviews": [ { "author": "Author 1", "reviewId": 1, "subject": "Subject 1" }, ... ], "weight": 123 }
如果在微服務內部,我們實際上可以直接呼叫微服務,不必透過邊緣伺服器。當然,問題是我們不知道服務執行在什麼埠,因為服務是動態分配的。但是,我們可以檢視呼叫Eureka REST API 的輸出,就知道服務監聽的埠了。我們可以使用如下的命令呼叫3個核心服務(埠號採用Eureka REST API輸出的埠資訊):
$ curl -s localhost:51658/product/1 | jq . $ curl -s localhost:59745/product/1 | jq . $ curl -s localhost:59178/review?productId=1 | jq . $ curl -s localhost:48014/recommendation?productId=1 | jq .
在自己的環境中,使用相應的埠號。
5.2 動態負載均衡
為了避免服務故障或者臨時的網路問題,通常需要多個服務例項,透過負載均衡分發請求。因為我們使用動態分配的埠和服務發現Server,可以非常容易新增新的服務例項。例如,可以簡單啟動一個新的review服務,動態分配一個新的埠,並自我註冊到服務發現伺服器(Service Discovery Server)中。
$ cd .../blog-microservices/microservices/core/review-service
$ ./gradlew bootRun
稍等片刻,第二個服務例項出現在服務發現web應用中(http://localhost:8761):
如果你執行之前的curl命令多次(curl -s localhost:8765/productcomposite/product/1 | jq .),檢視2個review例項的log日誌,可以發現負載均衡在2個例項之間自動處理呼叫請求,不必手工配置。
6. 總結
我們已經瞭解到Spring Cloud和Netflix OSS 元件是如何用來簡化獨立部署微服務協同工作的,不必人工管理每一個微服務的埠,或者人工配置路由規則。當新的例項啟動之後,它們會自動被服務發現Server監測到,並透過負載均衡來接收請求。透過使用邊緣伺服器(Edge Server),我們可以控制什麼微服務暴露給外部消費方,建立系統的API。
7. 下一步
OK,完成測試之後。接下來還有一些問題沒有回答,例如:
1/ 發生故障將如何處理,如出現一個失敗的微服務;
2/ 如何阻止對API的未授權訪問;
3/ 如何獲知微服務內部的執行圖,例如為什麼訂單#123456沒有交付?
在接下來的文章中,我們將瞭解如何使用電路斷路器(Circuit Breaker)來提升服務彈性,使用OAuth 2 限制外部訪問等等。也將瞭解如何使用ELK技術棧來收集所有微服務的日誌,並呈現日誌資訊。
原文英文連結:Building microservices with Spring Cloud and Netflix OSS, part 1
譯者:Rickie(RickieChina#hotmail*com)