一、為什麼要使用微服務網關
二、Zuul
1、編寫Zuul微服務網關
2、Zuul的Hystrix容錯與監控
3、Zuul的路由端點
4、路由配置
1.自定義指定微服務的通路路徑
2.忽略指定微服務
3.忽略所有微服務,隻路由指定微服務
4.同時指定微服務的serviceId和對應路徑
5.同時指定path和URL
6.使用正規表達式指定Zuul的路由比對規則
7.路由字首
8.忽略某些路徑
5、Zuul的安全與Header
1.指定敏感Header
2.忽略Header
6、Zuul上傳檔案
7、Zuul過濾器
1.編寫Zuul過濾器
2.Zuul異常處理過濾器
3.Zuul預設過濾器
8、Zuul回退
9、Zuul聚合微服務
一、為什麼要使用微服務網關
不同的微服務一般會經過不同的網絡位址,而外部用戶端可能需要調用多個服務的接口才能完成一個業務需求。
如果讓用戶端直接與各個微服務通信,會有以下的問題:
- 用戶端會多次請求不同的微服務,增加了用戶端的複雜性。
- 存在跨域請求,在一定場景下處理相對複雜。
- 認證複雜,每個服務都需要獨立認證。
- 難以重構,随着項目的疊代,可能需要重新劃分微服務。例如,可能将多個服務整個成一個或者将一個服務拆分成多個。如果用戶端直接與微服務通信,那麼重構将會很難實施。
- 某些微服務可能使用了防火牆/浏覽器不友好協定,直接通路會有一定的困難。
以上問題可借助微服務網管解決。微服務網關是介于用戶端和伺服器之間的中間層,所有外部請求都會先經過微服務網關。使用微服務網關後架構演變為下圖。
如圖,微服務網關封裝了應用程式的内部結構,用戶端隻需跟網關互動,而無需直接調用特定微服務的接口。這樣,開發就可以簡化。不僅如此,使用微服務網關還有以下優點:
- 易于監控。可在微服務網關收集監控資料并将其推送到外部系統進行分析。
- 易于認證。可在微服務網關上進行認證,然後再将請求轉發到後端的微服務,而無需再每個微服務中進行認證。
- 減少了用戶端與各個微服務之間的互動次數。
二、Zuul
Zuul是Netflix開源的微服務網關,核心是一系列的過濾器,這些過濾器可以完成以下功能。
- 身份認證與安全:識别每個資源的驗證需求,并拒絕那些與要求不符的請求。
- 審查與監控:在邊緣位置追蹤有意義的資料和統計結果,進而帶來精确的生産視圖。
- 動态路由:動态地請求路由到不同的後端叢集。
- 壓力測試:逐漸增加執行叢集的流量,以了解性能。
- 負載配置設定:為每一種負載類型配置設定對應容量,并棄用超出限定值的請求。
- 靜态響應處理:在邊緣位置直接建立部分響應,進而避免其轉發到内部叢集。
- 多區域彈性:跨越AWS Region進行請求路由,旨在實作ELB(Elastic Load Balancing)使用多樣化,以及讓系統的邊緣更貼近系統的使用者。
1、編寫Zuul微服務網關
1.建立項目gateway-zuul
以下是pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springclouddemo</groupId>
<artifactId>gateway-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway-zuul</name>
<description>微服務網關</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</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>
</project>
2.編寫application配置檔案
spring.application.name=gateway-zuul
server.port=7400
eureka.client.service-url.defaultZone=http://localhost:7000/eureka/
eureka.instance.prefer-ip-address=true
3.在main類添加@EnableZuulProxy注解
package com.springclouddemo.gatewayzuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @author 何昌傑
*/
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayZuulApplication.class, args);
}
}
這樣一個簡單的微服務網關就搭建成功了,并且将這個微服務注冊到Eureka Server上。
測試:
- 啟動項目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
- 通路http://localhost:7400/eureka-client-consumer-feign/hello/hcj,請求就會轉發到http://localhost:7204/hello/hcj上
- 通路http://localhost:7400/eureka-client-provider/hello/hcj,請求就會轉發到http://localhost:7100/hello/hcj上
預設情況下Zuul會代理所有注冊到Eureka Server的微服務,并且Zuul的路由規則是:http://ZUUL_HOST:ZUUL_PORT/微服務名稱/**會轉發到對應的微服務上。
2、Zuul的Hystrix容錯與監控
Zuul是預設繼承了負載均衡和熔斷的,負載均衡無需任何操作,Greenwich版本的Hystrix需要添加@Bean配置路徑才可以通路/hystrix.stream
将項目gateway-zuul的main類修改如下:
package com.springclouddemo.gatewayzuul;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
/**
* @author 何昌傑
*/
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayZuulApplication.class, args);
}
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
測試:
- 啟動項目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
- 分别通路http://localhost:7400/eureka-client-consumer-feign/hello/hcj,http://localhost:7400/eureka-client-provider/hello/hcj
- 通路http://localhost:7400/hystrix.stream,可以得到如下内容
說明Zuul已經整合了Hystrix(預設整合)。
3、Zuul的路由端點
當@EnableZuulProxy與Spring Boot Actuator配合使用時,Zuul會暴露一個路由管理端點/actuator/routes(低版本為/routes端點)。借助這個端點,可以友善、直覺地檢視以及管理Zuul的路由。
/actuator/routes端點的使用非常簡單,使用GET方法通路端點,即可以傳回Zuul當時映射的路由清單;使用POST方式通路該端點就會強制重新整理Zuul當時映射的路由清單(盡管路由會自動重新整理,Spring Cloud依然提供了強制立即重新整理的方式)。
由于spring-cloud-starter-netflix-zuul已經包含了spring-boot-starter-actuator,是以之前編寫的gateway-zuul項目已經具備路由管理的能力,不過需要在application配置檔案中添加以下配置。
management.endpoints.web.exposure.include=routes
通路http://localhost:7400/actuator/routes,可以看到以下内容:
4、路由配置
某些場景寫我們隻想讓Zuul代理部分微服務,或者需要對URL進行更加精确的控制。
1.自定義指定微服務的通路路徑
配置zuul.routes,指定微服務的serviceId=指定路徑 即可:
zuul.routes.eureka-client-consumer-feign=/feign/**
這樣配置,eureka-client-consumer-feign微服務就會被映射到/feign/**路徑。
2.忽略指定微服務
zuul.ignored-services=eureka-client-provider,eureka-client-consumer-feign
這樣配置,Zuul就會忽略eureka-client-provider,eureka-client-consumer-feign微服務,隻代理其他微服務。
3.忽略所有微服務,隻路由指定微服務
某些場景下我們隻想讓Zuul代理指定微服務:
zuul.ignored-services='*'
zuul.routes.eureka-client-consumer-feign=/feign/**
這樣配置,Zuul就會隻路由eureka-client-consumer-feign這個微服務。
4.同時指定微服務的serviceId和對應路徑
zuul.routes.feign.service-id=eureka-client-consumer-feign
zuul.routes.feign.path=/feign/**
- zuul.routes.feign.***的feign隻是一個路由名稱,可以任意修改名稱
5.同時指定path和URL
zuul.routes.feign.service-id=http://localhost:7204/
zuul.routes.feign.path=/feign/**
- zuul.routes.feign.***的feign隻是一個路由名稱,可以任意修改名稱
6.使用正規表達式指定Zuul的路由比對規則
借助PatternServiceRouteMapper,實作微服務的映射路由的正則配置:
package com.springclouddemo.gatewayzuul;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.discovery.PatternServiceRouteMapper;
import org.springframework.context.annotation.Bean;
/**
* @author 何昌傑
*/
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayZuulApplication.class, args);
}
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>.+$)","${version}/${name}");
}
}
通過上述配置可以實作eureka-client-consumer-feign這個微服務,映射到/feign/eureka-client-consumer/**這個路徑。
7.路由字首
zuul.routes.eureka-client-consumer-feign.path=/feign/**
zuul.routes.eureka-client-consumer-feign.strip-prefix=false
這樣通路Zuul的/hello/**路徑,請求就會被轉發到eureka-client-consumer-feign的/hello/**上。
8.忽略某些路徑
某些場景下我們想讓Zuul代理某些微服務,同時又想保護該微服務的某些敏感路徑,我們可以使用
zuul.ignored-patterns=/**/user/**
這樣配置就是可以忽略微服務中所有包含/admin/的路徑
5、Zuul的安全與Header
1.指定敏感Header
一般情況下同一個系統的服務之間共享Header,不過應盡量防止讓一些敏感的Header外洩。是以,在很多場景下,需要通過為路由指定一系列敏感Header清單。
zuul.routes.eureka-client-consumer-feign.path=/feign/**
zuul.routes.eureka-client-consumer-feign.sensitive-headers=Cookie,Set-Cookie,Authorization
這樣配置就可以為eureka-client-consumer-feign指定微服務通路路徑和指定敏感Header
也可以全局指定敏感Header:
zuul.sensitive-headers=Cookie,Set-Cookie,Authorization
2.忽略Header
可以通過zuul.ignored-headers屬性指定需要忽略的Header。
zuul.ignored-headers=Authorization
這樣配置後Authorization将不會傳播到其他的微服務中。
zuul.ignored-headers的預設值為空值,但如果Spring Security在項目的classpath中,那麼zuul.ignored-headers的預設值就是Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expires。是以當Spring Security在項目的classpath中,同時又需要使用下遊微服務的Spriing Security的Header時,可以将zuul.ignore-security-headers設定為false。
6、Zuul上傳檔案
對于大檔案(10M以上)上傳,需要為上傳路徑添加/zuul字首。也可以使用zuul.servlet-path自定義字首。
例如假如zuul.routes.eureka-client-consumer-feign-upload=/upload/**,http://localhost/{HOST}:{PORT}/upload是微服務eureka-client-consumer-feign-upload的上傳路徑,則需要用Zuul的/zuul/upload路徑進行上傳(添加/zuul字首)。
如果Zuul使用了Ribbon負載均衡,name對于超大檔案,需要擴大逾時設定:
(Hystrix與Ribbon的預設請求逾時時間都是1秒)
hystrix.command.connect.execution.isolation.thread.timeoutInMilliseconds=60000
ribbon.connectTimeout=3000
ribbon.readTimeout=60000
還需要為提供上傳檔案的微服務添加以下配置:
(max-file-size預設1MB,max-request-size預設10MB)
spring.servlet.multipart.max-file-size=2000MB
spring.servlet.multipart.max-request-size=2500MB
7、Zuul過濾器
Zuul大部分功能都是通過過濾器來實作的,Zuul定義了4種标準的過濾器類型,這些過濾器類型對應于請求的典型生命周期。
- pre: 這種過濾器在請求被路由之前調用。可利用這種過濾器實作身份驗證、在叢集中選擇請求的微服務,記錄調試資訊等。
- routing: 這種過濾器将請求路由到微服務。這種過濾器用于建構發送給微服務的請求,并使用apache httpclient或netflix ribbon請求微服務。
- post: 這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應添加标準的http header、收集統計資訊和名額、将響應從微服務發送給用戶端等。
- error: 在其他階段發送錯誤時執行該過濾器。
除了預設的過濾器類型,zuul還允許建立自定義的過濾器類型。例如,可以定制一種static類型的過濾器,直接在zuul中生成響應,而不将請求轉發到後端的微服務。
Zuul請求的生命周期如下圖,該圖較長的描述了各種類型的過濾器的執行順序。
也可通過檢視源碼中com.netflix.zuul.http.ZuulServlet類的service了解執行順序。
1.編寫Zuul過濾器
1.複制項目gateway-zuul為gateway-zuul-filter
2.端口修改為7401,微服務名修改為gateway-zuul-filter
3.編寫自定義Zuul過濾器filters/PreRequestLogFilter.java
package com.springclouddemo.gatewayzuulfilter.filters;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author 何昌傑
*/
@Component
public class PreRequestLogFilter extends ZuulFilter {
private static final Logger log= LoggerFactory.getLogger(PreRequestLogFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("send {} request to {}",request.getMethod(),request.getRequestURL());
return null;
}
}
- filterType 指定過濾器類型,對應上文幾種過濾器
- filterOrder 指定過濾器執行順序(預設越小越先執行)
- shouldFilter 是否啟用該過濾器(true為啟用,false為禁用)
- run 過濾器的具體業務邏輯
測試:
- 啟動項目gateway-zuul-filter、eureka-server、eureka-client-provider
- 通路http://localhost:7401/eureka-client-provider/hello/hcj,正常響應,gateway-zuul-filter控制台輸出以下内容
2019-07-11 21:47:54.622 INFO 5128 --- [nio-7401-exec-4] c.s.g.filters.PreRequestLogFilter : send GET request to http://localhost:7401/eureka-client-provider/hello/hcj
說明我們的自定義Zuul過濾器正常運作。
2.Zuul異常處理過濾器
當zuul通過eureka調用一個不可用、不存在、當機了的服務時,可能就會直接傳回類似于這樣的不友好的畫面:
我們可以通過編寫一個異常過濾器來處理這種情況:
package com.springclouddemo.gatewayzuulfilter.filters;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author 何昌傑
*/
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger log = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return -1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
response.setStatus(HttpStatus.SC_NOT_FOUND);
response.setContentType("application/json;charset=UTF-8");
Throwable throwable = ctx.getThrowable();
try (PrintWriter writer = response.getWriter()) {
writer.print("{\"resultCode\":404,\"data\":null,\"cause\":\"" + throwable.getCause() + "\",\"message\":\"路由轉發錯誤\"}");
} catch (IOException e) {
log.error("系統異常{}", e.getMessage());
}
return null;
}
}
測試:
- 啟動項目gateway-zuul-filter、eureka-server、eureka-client-provider
- 通路http://localhost:7401/eureka-client-provider/hello/hcj,正常響應
- 停止項目eureka-client-provider後再次通路http://localhost:7401/eureka-client-provider/hello/hcj,響應如下:
說明我們的異常處理過濾器正常運作。
3.Zuul預設過濾器
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标記處理Servlet的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 标記調試标志 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉發 |
route | 100 | SimpleHostRoutingFilter | url請求轉發 |
route | 500 | SendForwardFilter | forward請求轉發 |
post | SendErrorFilter | 處理有錯誤的請求響應 | |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
8、Zuul回退
想為Zuul添加回退需要實作FallbakcProvider接口,指定為哪些微服務提供回退并且提供一個ClientHTTPResponse作為回退響應。
1.複制項目gateway-zuul為gateway-zuul-fallback
2.端口修改為7402,微服務名修改為gateway-zuul-fallback
3.編寫Zuul的回退類
package com.springclouddemo.gatewayzuulfallback.fallbacks;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* @author 何昌傑
*/
@Component
public class ProviderFallback implements FallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
//fallback時的狀态碼
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException{
//數字類型的狀态碼
return 200;
}
@Override
public String getStatusText() throws IOException{
//狀态文本
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException{
//響應體
return new ByteArrayInputStream("此微服務不可用,請稍後重試!".getBytes());
}
@Override
public HttpHeaders getHeaders() {
//響應頭部
HttpHeaders httpHeaders = new HttpHeaders();
MediaType mediaType = new MediaType("application", "json", Charset.forName("UTF-8"));
httpHeaders.setContentType(mediaType);
return httpHeaders;
}
};
}
}
- getRoute() 傳回值指定微服務的serviceId,也可以是*代表所有微服務。
測試:
- 啟動項目gateway-zuul-fallback、eureka-server、eureka-client-provider
- 通路http://localhost:7402/eureka-client-provider/hello/hcj,正常響應
- 停止項目eureka-client-provider後再次通路http://localhost:7402/eureka-client-provider/hello/hcj,響應如下:
說明我們為Zuul添加回退成功。
9、Zuul聚合微服務
在很多次場景下,外部請求需要查詢Zuul後端的多個微服務。舉個例子,一個電影售票手機APP,在購票訂單頁上,既需要查詢“電影微服務”獲得電影相關資訊,又需要查詢“使用者微服務”獲得目前使用者的資訊。如果讓手機端直接請求各個微服務(即使使用Zuul進行轉發),那麼網絡開銷、流量耗費、耗費時長可能都無法令人滿意。那麼對于這種場景,可使用Zuul聚合微服務請求——手機APP隻需發送一個請求給Zuul,由于Zuul請求使用者微服務以及電影微服務,并組織好資料給手機APP。
使用這種方式,手機端隻須發送一次請求即可,簡化了用戶端側的開發;不僅如此,由于Zuul、使用者微服務、電影微服務一般都在同一區域網路,是以速度非常快,效率會非常高。
下面圍繞以上這個場景,來編寫代碼。
1.複制項目gateway-zuul為gateway-zuul-aggregation
2.将端口修改為7403,微服務名修改為gateway-zuul-aggregation
3.修改啟動類
package com.springclouddemo.gatewayzuulaggregation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @author 何昌傑
*/
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulAggregationApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayZuulAggregationApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
4.建立業務類services/AggregationService.java
package com.springclouddemo.gatewayzuulaggregation.services;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
/**
* @author 何昌傑
*/
@Service
public class AggregationService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public Observable<String> helloDemo1(String name) {
// 建立一個被觀察者
return Observable.create(observer -> {
// 請求微服務1的/hello/{name}端點
String res = restTemplate.getForObject("http://eureka-client-consumer-feign/hello/{name}", String.class, name);
observer.onNext(res);
observer.onCompleted();
});
}
@HystrixCommand(fallbackMethod = "fallback")
public Observable<String> helloDemo2(String name) {
return Observable.create(syncOnSubscribe -> {
// 請求微服務2的/hello/{name}端點
String res = restTemplate.getForObject("http://eureka-client-consumer-hystrix/hello/{name}", String.class, name);
syncOnSubscribe.onNext(res);
syncOnSubscribe.onCompleted();
});
}
public String fallback(String name) {
return "預設值:"+name;
}
}
5.建立controllers/AggregationController.java
在Controller中聚合多個請求
package com.springclouddemo.gatewayzuulaggregation.controllers;
import com.google.common.collect.Maps;
import com.springclouddemo.gatewayzuulaggregation.services.AggregationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import rx.Observable;
import rx.Observer;
import java.util.HashMap;
/**
* @author 何昌傑
*/
@RestController
public class AggregationController {
public static final Logger LOGGER = LoggerFactory.getLogger(AggregationController.class);
@Autowired
private AggregationService aggregationService;
@GetMapping("/aggregate/{name}")
public DeferredResult<HashMap<String, String>> aggregate(@PathVariable String name) {
Observable<HashMap<String, String>> result = this.aggregateObservable(name);
return this.toDeferredResult(result);
}
public Observable<HashMap<String, String>> aggregateObservable(String name) {
// 合并兩個或者多個Observables發射出的資料項,根據指定的函數變換它們
return Observable.zip(
this.aggregationService.helloDemo1(name),
this.aggregationService.helloDemo1(name),
(res1, res2) -> {
HashMap<String, String> map = Maps.newHashMap();
map.put("microservice1", res1);
map.put("microservice2", res2);
return map;
}
);
}
public DeferredResult<HashMap<String, String>> toDeferredResult(Observable<HashMap<String, String>> details) {
DeferredResult<HashMap<String, String>> result = new DeferredResult<>();
// 訂閱
details.subscribe(new Observer<HashMap<String, String>>() {
@Override
public void onCompleted() {
LOGGER.info("完成...");
}
@Override
public void onError(Throwable throwable) {
LOGGER.error("發生錯誤...", throwable);
}
@Override
public void onNext(HashMap<String, String> movieDetails) {
result.setResult(movieDetails);
}
});
return result;
}
}
測試:
- 啟動項目gateway-zuul-aggregation、eureka-server、eureka-client-provider、eureka-client-consumer-feign、eureka-client-consumer-hystrix
- 通路http://localhost:7403/aggregate/hcj,響應如下 控制台輸出:
2019-07-11 22:56:53.639 INFO 5448 --- [nio-7403-exec-1] c.s.g.controllers.AggregationController : 完成...
- 停止項目eureka-client-provider、eureka-client-consumer-feign、eureka-client-consumer-hystrix後再次通路http://localhost:7403/aggregate/hcj,響應如下: 控制台輸出:
說明我們已經成功用Zuul聚合了兩個微服務的接口。2019-07-11 23:03:58.003 INFO 5448 --- [io-7403-exec-10] c.s.g.controllers.AggregationController : 完成...