天天看點

Spring Cloud學習之路由網關-Zuul

1、為什麼需要Zuul

1.1、Zuul、Ribbon和Eureka相結合,可以實作智能路由和負載均衡的功能,Zuul可以将請求流量按照某種政策分發到叢集的多個服務執行個體上;

1.2、gateway将所有API接口統一聚合,并統一對外暴露,外界系統統一調用gateway提供的接口,外界無需知道微服務直接接口互相的調用關系,為微服務的API接口提供了更高的安全性;

1.3、Gateway服務可以用作身份認證和權限控制,防止非法通路API接口;

1.4、Gateway可以實作監控、日志輸出和對請求進行記錄;

1.5、Gateway可以實作流量的監控,在高流量的情況下,可以對服務進行降級;

1.6、Gateway将所有接口都統一對外,友善測試。

2、Zuul的工作原理

Zuul是通過Servlet實作的,通過自定義的ZuulServlet對請求進行控制。Zuul的核心是一系列的過濾器,可以在Http請求的發起到相應傳回期間執行一系列的過濾器,主要有一下四種過濾器:

a、Pre過濾器,請求路由之前執行的過濾器,主要用做安全驗證;

b、Routing過濾器,用于具體路由到微服務的執行個體,預設使用Http Client進行網絡請求;

c、Post過濾器,在請求被路由到微服務後執行的,用作收集統計資訊,名額,已經将相應傳輸到用戶端;

d、Error過濾器,在其他過濾器發生錯誤執行。

由于Zuul的過濾器之間是無法直接通信,是以隻能通過RequestContext對象進行共享資料,每個請求都會建立一個RequestContext對象。每個過濾器都有如下特性:

a、Type(類型),過濾器的類型,定義以上4中過濾器的一種;

b、Execution Order(執行順序),過濾的執行順序,Order值越小,越優先執行;

c、Criteria(标準),過濾器執行所需的條件;

d、Action(動作),符合Criteria的,則執行具體的Action,具體的邏輯代碼實作。

3、源碼實戰

3.1、建立Zuul服務

建立一個eureka-zuul-client的工程,添加相關依賴,Pom檔案如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hzlitai</groupId>
        <artifactId>eureka-memo</artifactId>
        <version>1.1-SNAPSHOT</version>
    </parent>
    <groupId>com.hzlitai</groupId>
    <artifactId>eureka-zuul-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-zuul-client</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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-netflix-zuul</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           

程式的啟動類添加相關注解,代碼如下:

package com.hzlitai.eurekazuulclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
public class EurekaZuulClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaZuulClientApplication.class, args);
    }

}
           

在application.yml配置中增加如下代碼:

server:
  port: 5000

spring:
  application:
    name: service-zuul
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

zuul:
  prefix: /v1
  routes:
    hiapi:
      path: /hiapi/**
      serviceId: eureka-client
    ribbonapi:
      path: /rebbonapi/**
      serviceId: eureka-ribbon-client
    feignapi:
      path: /feignapi/**
      serviceId: eureka-feign-client
           

服務端口為:5000,服務執行個體名:service-zuul,注冊到服務中心:serviceUrl,統一版本:prefix,路由配置:routes。

依次啟動各個服務後,通路:http://localhost:5000/ribbonapi/sayHi?name=zhuheliang,浏覽器會交替出現通路不同端口的結果。可見Zuul在路由轉發實作了負載均衡。

3.2、在Zuul上配置熔斷器Hystrix

在Zuul實作熔斷功能需要實作FallbackProvider接口,實作兩個方法:getRoute()和fallbackResponse(),源碼如下:

package com.hzlitai.eurekazuulclient.provider;

import com.netflix.hystrix.exception.HystrixTimeoutException;
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;

@Component
public class ZuulFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        //api服務id(eureka-client),如果需要所有調用都支援回退,則return "*"或 return null
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ClientHttpResponse response(final HttpStatus status) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                //return status;
                return HttpStatus.BAD_REQUEST;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                //return status.value();
                return HttpStatus.BAD_REQUEST.value();
            }

            @Override
            public String getStatusText() throws IOException {
                //return status.getReasonPhrase();
                //return HttpStatus.BAD_REQUEST.name();
                return HttpStatus.BAD_REQUEST.getReasonPhrase();
            }

            @Override
            public void close() {
            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(("fallback:"+ZuulFallbackProvider.this.getRoute()).getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}
           

3.3、在Zuul中自定義過濾器

Zuul自定義過濾器隻需繼承ZuulFilter類,實作ZuulFilter裡的抽象方法,有filterType()和filterOrder(),還有IZuulFilter的shouldFilter()和Object run(),示例代碼如下:

package com.hzlitai.eurekazuulclient.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

@Component
public class MyFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request =  ctx.getRequest();
        Object accessToken = request.getParameter("accessToken");
        if(accessToken == null){
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try{
                ctx.getResponse().getWriter().write("token is empty");
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return null;
    }
}