天天看點

Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

一、為什麼要使用微服務網關

二、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路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

如果讓用戶端直接與各個微服務通信,會有以下的問題:

  • 用戶端會多次請求不同的微服務,增加了用戶端的複雜性。
  • 存在跨域請求,在一定場景下處理相對複雜。
  • 認證複雜,每個服務都需要獨立認證。
  • 難以重構,随着項目的疊代,可能需要重新劃分微服務。例如,可能将多個服務整個成一個或者将一個服務拆分成多個。如果用戶端直接與微服務通信,那麼重構将會很難實施。
  • 某些微服務可能使用了防火牆/浏覽器不友好協定,直接通路會有一定的困難。

以上問題可借助微服務網管解決。微服務網關是介于用戶端和伺服器之間的中間層,所有外部請求都會先經過微服務網關。使用微服務網關後架構演變為下圖。

Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、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上。

測試:

  1. 啟動項目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
  2. 通路http://localhost:7400/eureka-client-consumer-feign/hello/hcj,請求就會轉發到http://localhost:7204/hello/hcj上
  3. 通路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;
    }
}
           

測試:

  1. 啟動項目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
  2. 分别通路http://localhost:7400/eureka-client-consumer-feign/hello/hcj,http://localhost:7400/eureka-client-provider/hello/hcj
  3. 通路http://localhost:7400/hystrix.stream,可以得到如下内容
    Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

說明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,可以看到以下内容:

Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

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請求的生命周期如下圖,該圖較長的描述了各種類型的過濾器的執行順序。

Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

也可通過檢視源碼中com.netflix.zuul.http.ZuulServlet類的service了解執行順序。

Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

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 過濾器的具體業務邏輯

測試:

  1. 啟動項目gateway-zuul-filter、eureka-server、eureka-client-provider
  2. 通路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調用一個不可用、不存在、當機了的服務時,可能就會直接傳回類似于這樣的不友好的畫面:

Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

我們可以通過編寫一個異常過濾器來處理這種情況:

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;
    }
}
           

測試:

  1. 啟動項目gateway-zuul-filter、eureka-server、eureka-client-provider
  2. 通路http://localhost:7401/eureka-client-provider/hello/hcj,正常響應
  3. 停止項目eureka-client-provider後再次通路http://localhost:7401/eureka-client-provider/hello/hcj,響應如下:
    Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

說明我們的異常處理過濾器正常運作。

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,也可以是*代表所有微服務。

測試:

  1. 啟動項目gateway-zuul-fallback、eureka-server、eureka-client-provider
  2. 通路http://localhost:7402/eureka-client-provider/hello/hcj,正常響應
  3. 停止項目eureka-client-provider後再次通路http://localhost:7402/eureka-client-provider/hello/hcj,響應如下:
    Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul

說明我們為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;
    }
}
           

測試:

  1. 啟動項目gateway-zuul-aggregation、eureka-server、eureka-client-provider、eureka-client-consumer-feign、eureka-client-consumer-hystrix
  2. 通路http://localhost:7403/aggregate/hcj,響應如下
    Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul
    控制台輸出:
    2019-07-11 22:56:53.639  INFO 5448 --- [nio-7403-exec-1] c.s.g.controllers.AggregationController  : 完成...
               
  3. 停止項目eureka-client-provider、eureka-client-consumer-feign、eureka-client-consumer-hystrix後再次通路http://localhost:7403/aggregate/hcj,響應如下:
    Zuul微服務網關、容錯與監控、Zuul路由端點、路由配置、Zuul上傳檔案、Zuul過濾器、Zuul異常處理、Zuul回退、Zuul聚合微服務一、為什麼要使用微服務網關二、Zuul
    控制台輸出:
    2019-07-11 23:03:58.003  INFO 5448 --- [io-7403-exec-10] c.s.g.controllers.AggregationController  : 完成...
               
    說明我們已經成功用Zuul聚合了兩個微服務的接口。

繼續閱讀