Spring Cloud入門教程(五):API服務閘道器(Zuul) 上
微服務場景下,每一個微服務對外暴露了一組細粒度的服務。客戶端的請求可能會涉及到一串的服務呼叫,如果將這些微服務都暴露給客戶端,那麼客戶端需要多次請求不同的微服務才能完成一次業務處理,增加客戶端的程式碼複雜度。另外,對於微服務我們可能還需要服務呼叫進行統一的認證和校驗等等。微服務架構雖然可以將我們的開發單元拆分的更細,降低了開發難度,但是如果不能夠有效的處理上面提到的問題,可能會造成微服務架構實施的失敗。
Zuul參考GOF設計模式中的Facade模式,將細粒度的服務組合起來提供一個粗粒度的服務,所有請求都匯入一個統一的入口,那麼整個服務只需要暴露一個api,對外遮蔽了服務端的實現細節,也減少了客戶端與伺服器的網路呼叫次數。這就是API服務閘道器(API Gateway)服務。我們可以把API服務閘道器理解為介於客戶端和伺服器端的中間層,所有的外部請求都會先經過API服務閘道器。因此,API服務閘道器幾乎成為實施微服務架構時必須選擇的一環。
Spring Cloud Netflix的Zuul元件可以做反向代理的功能,透過路由定址將請求轉發到後端的粗粒度服務上,並做一些通用的邏輯處理。
透過Zuul我們可以完成以下功能:
動態路由
監控與審查
身份認證與安全
壓力測試: 逐漸增加某一個服務叢集的流量,以瞭解服務效能;
金絲雀測試
服務遷移
負載剪裁: 為每一個負載型別分配對應的容量,對超過限定值的請求棄用;
靜態應答處理
1. 構建閘道器
1.1 構建Zuul-Server
編寫pom.xml檔案
Zuul-Server
是一個標準的Spring Boot應用,所以還是繼承自我們之前的parent:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="" xmlns:xsi="" xsi:schemaLocation=" "> <modelVersion>4.0.0</modelVersion> <parent> <groupId>twostepsfromjava.cloud</groupId> <artifactId>twostepsfromjava-cloud-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <relativePath>../parent</relativePath> </parent> <artifactId>zuul-server</artifactId> <name>Spring Cloud Sample Projects: Zuul Proxy Server</name> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
這裡我們增加了spring-cloud-starter-zuul
的依賴。
編寫啟動類
/** * TwoStepsFromJava Cloud -- Zuul Proxy 伺服器 * * @author CD826(CD826Dong@gmail.com) * @since 1.0.0 */@EnableZuulProxy@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
這裡我們增加了對主應用類增加了@EnableZuulProxy
,用以啟動Zuul的路由服務。
編寫配置檔案application.properties
server.port=8280spring.application.name=ZUUL-PROXY eureka.client.service-url.defaultZone=
這裡定義服務名稱為: ZUUL-PROXY
,埠設為: 8280
。
1.2 構建User-Service
為了後面的則是我們再增加一個微服務: 使用者服務。
編寫pom.xml檔案
同樣繼承自我們之前的parent:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="" xmlns:xsi="" xsi:schemaLocation=" "> <modelVersion>4.0.0</modelVersion> <parent> <groupId>twostepsfromjava.cloud</groupId> <artifactId>twostepsfromjava-cloud-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <relativePath>../parent</relativePath> </parent> <artifactId>user-service</artifactId> <name>Spring Cloud Sample Projects: User Service Server</name> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>service-api</artifactId> <version>${project.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
編寫啟動類
啟動類和之前的Product-Service
一樣,所以這裡不再列出來。
編寫服務介面
示例的服務介面非常簡單,就是根據給定的登入名稱查詢一個使用者資訊。如下:
/** * User API服務 * * @author CD826(CD826Dong@gmail.com) * @since 1.0.0 */@RestController@RequestMapping("/users")public class UserEndpoint { protected Logger logger = LoggerFactory.getLogger(UserEndpoint.class); @Value("${server.port:2200}") private int serverPort = 2200; @RequestMapping(value = "/{loginName}", method = RequestMethod.GET) public User detail(@PathVariable String loginName) { String memos = "I come form " + this.serverPort; return new User(loginName, loginName, "/avatar/default.png", memos); } }
其中User
類定義在之前的service-api
專案中,程式碼如下:
/** * 使用者資訊DTO物件 * * @author CD826(CD826Dong@gamil.com) * @since 1.0.0 */public class User { private static final long serialVersionUID = 1L; // ======================================================================== // fields ================================================================= private String loginName; // 使用者登陸名稱 private String name; // 使用者姓名 private String avatar; // 使用者頭像 private String memos; // 資訊備註 // ======================================================================== // constructor ============================================================ public User() { } public User(String loginName, String name, String avatar, String memos) { this.loginName = loginName; this.name = name; this.avatar = avatar; this.memos = memos; } // ================================================================== // setter/getter ==================================================== // ... 省略,請自行補充 ...}
編寫配置檔案application.properties
server.port=2200spring.application.name=USER-SERVICE eureka.client.service-url.defaultZone=
這裡定義服務名稱為: USER-SERVICE
,預設埠設為: 2200
。
程式碼修改,就是這麼多,下面讓我們啟動進行測試。
1.3 啟動測試
啟動各服務
請按照下面的順序啟動各伺服器:
Service-discovery
Product-Service
User-Service(2200)
User-Service(2300):
java -jar user-service-1.0.0-SNAPSHOT.jar --server.port=2300
Zuul-Server
Ok, 服務啟動後我們可以在Eureka伺服器看到如下介面:
Zuul-proxy-010
這裡我們啟動兩個User-Service
主要是為了後面進行負載均衡測試使用。
測試路由服務
首先,我們在瀏覽器中輸入以下地址: ,將會顯示以下介面:
Zuul-proxy-020
然後,我們在瀏覽器中輸入以下地址: ,將會顯示以下介面:
Zuul-proxy-030
可見,Zuul-Server
已經幫我們路由到相應的微服務。
負載均衡測試
接下來我們測試一下負載均衡是否可以正常工作。前面我們已經啟動了兩個User-Service
微服務,埠分別為:2200和2300。我們多次在瀏覽器中輸入以下地址: 進行請求,我們將會看到以下資訊會在螢幕中交替輸出:
{"loginName":"admin","name":"admin","avatar":"/avatar/default.png","memos":"I come form 2200"}
{"loginName":"admin","name":"admin","avatar":"/avatar/default.png","memos":"I come form 2300"}
可見,負載均衡也是正常工作的。
Hystrix容錯與監控測試
之前我們是在Mall-Web
專案中整合Hystrix
的監控,那麼我們啟動該服務。然後在Hystrix Dashboard中輸入: ,然後進行監控,那麼我們將看到如下介面:
Zuul-proxy-050
這說明,Zuul已經整合了Hystrix。
spring-cloud-starter-zuul
本身已經整合了hystrix和ribbon,所以Zuul天生就擁有執行緒隔離和斷路器的自我保護能力,以及對服務呼叫的客戶端負載均衡功能。但是,我們需要注意,當使用path與url的對映關係來配置路由規則時,對於路由轉發的請求則不會採用HystrixCommand
來包裝,所以這類路由請求就沒有執行緒隔離和斷路器保護功能,並且也不會有負載均衡的能力。因此,我們在使用Zuul的時候儘量使用path和serviceId的組合進行配置,這樣不僅可以保證API閘道器的健壯和穩定,也能用到Ribbon的客戶端負載均衡功能。
2. Zuul配置
2.1 路由配置詳解
或許你會覺得神奇,之前我們什麼也沒有配置,透過、已經可以正確的訪問到我們的微服務了,這就是Zuul的預設路由對映功能在起作用,那麼接下來具體來看看Zuul是怎麼進行路由配置的。
1) 服務路由預設規則
當我們構建API服務閘道器時引入Eureka時,那麼Zuul會自動為每個服務都建立一個預設路由規則: 訪問路徑的字首為serviceId
配置的服務名稱,也就是之前為什麼我們能夠所使用:
來訪問Product-Service中所提供的products服務端點的原因。
2) 自定義微服務訪問路徑
配置格式為: zuul.routes.微服務Id = 指定路徑,如:
zuul.routes.user-service = /user/**
這樣,我們後面就可以透過/user/
來訪問user-service
所提供的服務,比如之前的訪問可以更改為: 。
所要配置的路徑可以指定一個正規表示式來匹配路徑,因此,/user/*
只能匹配一級路徑,但是透過/user/**
可以匹配所有以/user/
開頭的路徑。
3) 忽略指定微服務
配置格式為: zuul.ignored-services=微服務Id1,微服務Id2...,多個微服務之間使用逗號分隔。如:
zuul.ignored-services=user-service,product-service
4) 同時指定微服務Id和對應路徑
zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=service-A zuul.routes.api-b.path=/api-b/** zuul.routes.api-b.serviceId=service-B
5) 同時指定微服務Url和對應路徑
zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.url=
如之前所述,透過url配置的路由不會由HystrixCommand來執行,自然,也就得不到Ribbon的負載均衡、降級、斷路器等功能。所以在實施儘量使用serviceId進行配置,也可以採用下面的配置方式。
6) 指定多個服務例項及負載均衡
如果需要配置多個服務例項,則配置如下:
zuul.routes.user.path: /user/** zuul.routes.user.serviceId: user ribbon.eureka.enabled=false user.ribbon.listOfServers: http://192.168.1.10:8081, http://192.168.1.11:8081
7) forward跳轉到本地url
zuul.routes.user.path=/user/** zuul.routes.user.url=forward:/user
8) 路由字首
可以透過zuul.prefix
可為所有的對映增加統一的字首。如: /api
。預設情況下,代理會在轉發前自動剝離這個字首。如果需要轉發時帶上字首,可以配置: zuul.stripPrefix=false
來關閉這個預設行為。例如:
zuul.routes.users.path=/myusers/** zuul.routes.users.stripPrefix=false
注意:
zuul.stripPrefix
只會對zuul.prefix
的字首起作用。對於path指定的字首不會起作用。
9) 路由配置順序
如果想按照配置的順序進行路由規則控制,則需要使用YAML,如果是使用propeties檔案,則會丟失順序。例如:
zuul: routes: users: path: /myusers/** legacy: path: /**
上例如果是使用properties檔案進行配置,則legacy
就可能會先生效,這樣users
就沒效果了。
10) 自定義轉換
我們也可以一個轉換器,讓serviceId
和路由之間使用正規表示式來自動匹配。例如:
@Beanpublic PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
這樣,serviceId為“users-v1”的服務,就會被對映到路由為“/v1/users/”的路徑上。任何正規表示式都可以,但是所有的命名組必須包括servicePattern和routePattern兩部分。如果servicePattern沒有匹配一個serviceId,那就會使用預設的。在上例中,一個serviceId為“users”的服務,將會被對映到路由“/users/”中(不帶版本資訊)。這個特性預設是關閉的,而且只適用於已經發現的服務。
2.2 Zuul的Header設定
敏感Header設定
同一個系統中各個服務之間透過Headers來共享資訊是沒啥問題的,但是如果不想Headers中的一些敏感資訊隨著HTTP轉發洩露出去話,需要在路由配置中指定一個忽略Header的清單。
預設情況下,Zuul在請求路由時,會過濾HTTP請求頭資訊中的一些敏感資訊,預設的敏感頭資訊透過zuul.sensitiveHeaders
定義,包括Cookie
、Set-Cookie
、Authorization
。配置的sensitiveHeaders
可以用逗號分割。
對指定路由的可以用下面進行配置:
# 對指定路由開啟自定義敏感頭 zuul.routes.[route].customSensitiveHeaders=true zuul.routes.[route].sensitiveHeaders=[這裡設定要過濾的敏感頭]
設定全域性:
zuul.sensitiveHeaders=[這裡設定要過濾的敏感頭]
忽略Header設定
如果每一個路由都需要配置一些額外的敏感Header時,那你可以透過zuul.ignoredHeaders
來統一設定需要忽略的Header。如:
zuul.ignoredHeaders=[這裡設定要忽略的Header]
在預設情況下是沒有這個配置的,如果專案中引入了Spring Security
,那麼Spring Security
會自動加上這個配置,預設值為: Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expries
。
此時,如果還需要使用下游微服務的Spring Security的Header時,可以增加下面的設定:
zuul.ignoreSecurityHeaders=false
2.3 Zuul Http Client
Zuul的Http客戶端支援Apache Http、Ribbon的RestClient和OkHttpClient,預設使用Apache HTTP客戶端。可以透過下面的方式啟用相應的客戶端:
# 啟用Ribbon的RestClient ribbon.restclient.enabled=true# 啟用OkHttpClient ribbon.okhttp.enabled=true
如果需要使用OkHttpClient需要注意在你的專案中已經包含
com.squareup.okhttp3
相關包。
3. Zuul容錯與回退
我們再來仔細看一下之前Hystrix的監控介面:
Zuul-proxy-080
請注意,Zuul的Hystrix監控的粒度是微服務,而不是某個API,也就是所有經過Zuul的請求都會被Hystrix保護起來。假如,我們現在把Product-Service
服務關閉,再來訪問會出現什麼結果呢?結果可能不是我們所想那樣,如下:
Zuul-proxy-060
呃,比較鬱悶是麼!那麼如何為Zuul實現容錯與回退呢?
Zuul提供了一個ZuulFallbackProvider
介面,透過實現該介面就可以為Zuul實現回退功能。那麼讓我們改造之前的Zuul-Server
。
3.1 實現回退方法
程式碼如下:
/** * Product Service服務失敗回退處理 * * @author CD826(CD826Dong@gmail.com) * @since 1.0.0 */@Componentpublic class ProductServiceFallbackProvider implements ZuulFallbackProvider { protected Logger logger = LoggerFactory.getLogger(ProductServiceFallbackProvider.class); @Override public String getRoute() { // 注意: 這裡是route的名稱,不是服務的名稱, // 如果這裡寫成大寫PRODUCT-SERVICE將無法起到回退作用 return "product-service"; } @Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("商品服務暫不可用,請稍後重試!".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); return headers; } }; } }
需要說明的是:
getRoute
方法返回了我們要為那個微服務提供回退。這裡需要注意的返回的值是route的名稱,不是服務的名稱,不能夠寫為:PRODUCT-SERVICE
,否則該回退將不起作用;fallbackResponse
方法返回ClientHttpResponse
物件,作為我們的回退響應。這裡實現非常簡單僅僅是返回:商品服務暫不可用,請稍後重試! 的提示。
3.2 重啟測試
重啟Zuul-Server
,再重複上面的實驗,將會看到以下介面:
Zuul-proxy-070
說明,回退方法已經起作用了。如果你的沒有起作用,那麼仔細檢查一下getRoute
的返回是否正確。
你可以到下載本篇的程式碼。
作者:CD826
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2334/viewspace-2820473/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Cloud實戰系列(五) - 服務閘道器ZuulSpringCloudZuul
- Spring Cloud Zuul API服務閘道器之請求路由SpringCloudZuulAPI路由
- Spring Cloud Zuul 閘道器SpringCloudZuul
- Spring Cloud教程 第九彈 微服務閘道器ZuulSpringCloud微服務Zuul
- Spring Cloud Zuul 閘道器(一)SpringCloudZuul
- Spring cloud(5)-路由閘道器(Zuul)SpringCloud路由Zuul
- 微服務閘道器Zuul遷移到Spring Cloud Gateway微服務ZuulSpringCloudGateway
- Spring Boot整合Zuul API閘道器Spring BootZuulAPI
- SpringCloud系列之API閘道器(Gateway)服務ZuulSpringGCCloudAPIGatewayZuul
- Spring Cloud Zuul與閘道器中介軟體SpringCloudZuul
- Spring Cloud 專題之四:Zuul閘道器SpringCloudZuul
- 在spring boot中整合微服務閘道器係統Spring Cloud ZuulSpring Boot微服務CloudZuul
- (六)spring cloud微服務分散式雲架構-服務閘道器Zuul高階篇SpringCloud微服務分散式架構Zuul
- (三)spring cloud微服務分散式雲架構-服務閘道器zuul初級篇SpringCloud微服務分散式架構Zuul
- idea建立springcloud專案圖文教程(zuul實現api閘道器服務)(十)IdeaSpringGCCloudZuulAPI
- 閘道器 zuul 與 spring-cloud gateway的區別ZuulSpringCloudGateway
- spring cloud構建網際網路分散式微服務雲平臺-服務閘道器zuulSpringCloud分散式微服務Zuul
- Spring Cloud構建微服務架構-服務閘道器SpringCloud微服務架構
- 微服務閘道器 Spring Cloud Gateway微服務SpringCloudGateway
- SpringCloud之路由閘道器zuul(五)SpringGCCloud路由Zuul
- Gateway服務閘道器 (入門到使用)Gateway
- Spring Cloud構建微服務架構—服務閘道器過濾器SpringCloud微服務架構過濾器
- 最全面的改造Zuul閘道器為Spring Cloud Gateway(包含Zuul核心實現和Spring Cloud Gateway核心實現)ZuulSpringCloudGateway
- Spring Cloud Alibaba系列(四)使用gateway作為服務閘道器SpringCloudGateway
- .Net Core微服務入門全紀錄(五)——Ocelot-API閘道器(下)微服務API
- spring cloud微服務分散式雲架構-服務閘道器過濾器SpringCloud微服務分散式架構過濾器
- 微服務閘道器實戰——Spring Cloud Gateway微服務SpringCloudGateway
- Zuul路由閘道器Zuul路由
- SpringCloud微服務治理三(Zuul閘道器)SpringGCCloud微服務Zuul
- .Net Core微服務入門全紀錄(四)——Ocelot-API閘道器(上)微服務API
- spring cloud構建網際網路分散式微服務雲平臺-路由閘道器(zuul)SpringCloud分散式微服務路由Zuul
- 閘道器服務:zuul與nginx的效能測試對比ZuulNginx
- springboot-zuul閘道器Spring BootZuul
- 隨行付微服務之基於Zuul自研服務閘道器微服務Zuul
- 《springcloud 二》SrpingCloud Zuul 微服務閘道器搭建SpringGCCloudZuul微服務
- 史上最簡單的 SpringCloud 教程 | 第五篇: 路由閘道器 (zuul)SpringGCCloud路由Zuul
- 業餘草 SpringCloud教程 | 第五篇: 路由閘道器(zuul)(Finchley版本)SpringGCCloud路由Zuul
- Spring Cloud Gateway 閘道器嚐鮮SpringCloudGateway