1. 程式人生 > >SpringCloud學習-(7)閘道器服務(Zuul)

SpringCloud學習-(7)閘道器服務(Zuul)

1.Zuul是什麼

zuul 是netflix開源的一個API Gateway 伺服器, 本質上是一個web servlet應用。
zuul 在雲平臺上提供動態路由,監控,彈性,安全等邊緣服務的框架。zuul 相當於是裝置和 Netflix 流應用的 Web 網站後端所有請求的前門。
zuul的例子可以參考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文件說明來進行使用。

2.Zuul的工作原理
1、過濾器機制
zuul的核心是一系列的filters, 其作用可以類比Servlet框架的Filter,或者AOP。

zuul把Request route到 使用者處理邏輯 的過程中,這些filter參與一些過濾處理,比如Authentication,Load Shedding等。
這裡寫圖片描述

3.springcloud-zuul

Zuul的主要功能是路由轉發和過濾器。路由功能是微服務的一部分,比如/api/user轉發到到user服務,/api/shop轉發到到shop服務。zuul預設和Ribbon結合實現了負載均衡的功能。

使用zuul可以實現以下功能:

Authentication認證
Insights觀察
Stress Testing壓力測試
Canary Testing
Dynamic Routing
Service Migration
Load Shedding
Security
Static Response handling
Active/Active traffic management
(摘自spring官網)

說一堆也不知道是啥,上一個小例子跑起來。

3.1 準備
springcloud-eureka-server服務註冊中心,埠8761
springcloud-eureka-client1服務提供者1,服務名service-sayHello,方法sayHello,埠8762
springcloud-eureka-client2服務提供者2,服務名service-sayHello,方法sayHello,埠8763
springcloud-eureka-ribbon負載均衡器,服務名service-ribbon
springcloud-eureka-feign負載均衡器,服務名service-feign
這些都是前面幾篇介紹過的,在使用zuul之前,需要準備好,並將服務註冊到eureka上。

3.2 新建zuul工程
GroupId:com.tangjinyi
ArtifactId:springcloud-eureka-zuul

3.3 pom.xml配置
需要引入zuul的依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

eureka依賴也是必須的

<?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>

    <groupId>com.tangjinyi</groupId>
    <artifactId>springcloud-eureka-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springcloud-eureka-feign</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.M9</spring-cloud.version>
    </properties>

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

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>


</project>

3.4 application.properties配置檔案
這裡的c1,c2為自定義,api-a,api-b也為自定義

#將服務註冊到eureka
eureka.client.service-url.defaultZone = http://localhost:8761/eureka/
#路由服務埠
server.port=8772
#服務名
spring.application.name=service-zuul
#所有以/c1/開頭的請求都交由service-ribbon服務進行處理
zuul.routes.api-a.path=/c1/**
zuul.routes.api-a.serviceId=service-ribbon
#所有以/c2/開頭的請求都交由service-feign服務進行處理
zuul.routes.api-b.path=/c2/**
zuul.routes.api-b.serviceId=service-feign

如果配置檔案採用.yml檔案,配置如下:
這裡寫圖片描述

3.5 啟動類配置
啟動類新增@EnableZuulProxy註解,開啟zuul的功能

package com.tangjinyi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class SpringcloudEurekaZuulApplication {

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

}

最終的zuul工程架構如下:
這裡寫圖片描述

3.6 啟動所有的工程
先啟動eureka,然後將其他服務註冊到eureka上。
訪問eureka管控臺:瀏覽器訪問http://localhost:8761/
這裡寫圖片描述

先將請求路由到ribbon服務,由ribbon請求服務提供者,重新整理瀏覽器埠號在8762和8763輪詢切換,表示ribbon負載均衡沒問題。
這裡寫圖片描述
再將請求路由到feign服務,由feign請求服務提供者,重新整理瀏覽器埠號在8762和8763輪詢切換,表示feign負載均衡沒問題。
這裡寫圖片描述

5 Zuul過濾器
過濾器是一個很有用的機制,可以實現:token校驗/安全認證、動態修改請求引數、灰度釋出(Gated Launch/Gray Release)。

5.1 token校驗/安全認證
閘道器直接暴露在公網上時,終端要呼叫某個服務,通常會把登入後的token傳過來,閘道器層對token進行有效性驗證,如果token無效(或沒傳token),提示重新登入或直接拒絕。另外,閘道器後面的微服務,如果設定了spring security中的basic Auth(即:不允許匿名訪問,必須提供使用者名稱、密碼),也可以在Filter中處理。

介紹一下里面的一些方法:
filterType()方法返回值確定是在路由的什麼時間進行過濾,如返回值為FilterConstants.PRE_TYPE表示是在請求被路由之前呼叫,FilterConstants.ROUTE_TYPE表示是在路由請求時候被呼叫,FilterConstants.POST_TYPE表示是在route和error過濾器之後被呼叫,FilterConstants.ERROR_TYPE表示是在處理請求時發生錯誤時被呼叫。

filterOrder()方法通過int值來定義過濾器的執行順序。優先順序為0,數字越大,優先順序越低。

shouldFilter()方法返回一個boolean型別來判斷該過濾器是否要執行,所以通過此函式可實現過濾器的開關。

run()方法是過濾器的具體邏輯。需要注意,通過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,然後通過ctx.setResponseStatusCode(401)設定了其返回的錯誤碼

需要使用@Component註解將該過濾器加入到Ioc容器中,否則該過濾器不會生效。

package com.tangjinyi.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class AccessFilter extends ZuulFilter{

    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    public int filterOrder() {
        return 0;
    }

    public boolean shouldFilter() {
        return true;
    }

    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        Object token = request.getParameter("token");

        //校驗token
        if (token == null) {
            System.out.println("token為空,請重新輸入!");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try{
                ctx.getResponse().getWriter().write("token is empty");
            }catch (Exception e){

            }
            return null;
        } else {
            //TODO
        }

        //新增Basic Auth認證資訊
        ctx.addZuulRequestHeader("Authorization", "Basic " );
        return null;
    }
}

5.2 動態引數
可以攔截所有請求引數,並對其進行修改,比如:終端發過來的資料,出於安全要求,可能是經過加密處理的,需要在閘道器層進行引數解密,再傳遞到後面的服務;再比如:使用者傳過來的token值,需要轉換成userId/userName這些資訊,再傳遞到背後的微服務。

package com.tangjinyi.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.util.StreamUtils;

import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

import static org.springframework.util.ReflectionUtils.rethrowRuntimeException;

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

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

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

    @Override
    public Object run() throws ZuulException {
        try {
            RequestContext context = RequestContext.getCurrentContext();
            InputStream in = (InputStream) context.get("requestEntity");
            if (in == null) {
                in = context.getRequest().getInputStream();
            }
            String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
            body = "你好: " + body;
            byte[] bytes = body.getBytes("UTF-8");
            context.setRequest(new HttpServletRequestWrapper(RequestContext.getCurrentContext().getRequest()) {
                public ServletInputStream getInputStream() throws IOException {
                    return new ServletInputStreamWrapper(bytes);
                }

                public int getContentLength() {
                    return bytes.length;
                }

                public long getContentLengthLong() {
                    return bytes.length;
                }
            });
        } catch (IOException e) {
            rethrowRuntimeException(e);
        }
        return null;
    }
}

5.3 灰度釋出