欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Cloud 之Zuul初使用

程序员文章站 2022-06-13 21:33:29
...

Spring Cloud 之Zuul初使用

介绍

在微服务架构中,通常会有多个服务提供者。设想一个电商系统,可能会有商品、订单、支付、用户等多个类型的服务,而每个类型的服务数量也会随着整个系统体量的增大也会随之增长和变更。作为UI端,在展示页面时可能需要从多个微服务中聚合数据,而且服务的划分位置结构可能会有所改变。网关就可以对外暴露聚合API,屏蔽内部微服务的微小变动,保持整个系统的稳定性。

当然这只是网关众多功能中的一部分,它还可以做负载均衡,统一鉴权,协议转换,监控监测等一系列功能。

Zuul是Spring Cloud全家桶中的微服务API网关。

所有从设备或网站发出来的请求都会经过Zuul到达服务提供者。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul底层利用各种Filter实现如下功能:

  • 认证和安全: 识别每个需要认证的资源,拒绝不符合要求的请求。
  • 性能监测: 在服务边界追踪并统计数据,提供精确的生产视图。
  • 动态路由: 根据需要将请求动态路由到后端集群。
  • 压力测试: 逐渐增加对集群的流量以了解其性能。
  • 负载卸载: 预先为每种类型的请求分配容量,当请求超过容量时自动丢弃。
  • 静态资源处理: 直接在边界返回某些响应。

Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。

Zuul的过滤器之间没有直接的相互通信,他们之间通过一个RequestContext的静态类来进行数据传递。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据。

Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中,以便在相应情况下调用。

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

  1. PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  2. ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  3. POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  4. ERROR:在其他阶段发生错误时执行该过滤器。
    Spring Cloud 之Zuul初使用

Zuul是围绕一系列Filter展开的,这些Filter在整个HTTP请求过程中执行一连串的操作。

Zuul Filter有以下几个特征:

  • Type:用以表示路由过程中的阶段(内置包含PREROUTINGPOSTERROR)。
  • Execution Order:表示相同Type的Filter的执行顺序。
  • Criteria:执行条件。
  • Action:执行体。

Zuul提供了动态读取、编译和执行Filter的框架。各个Filter间没有直接联系,但是都通过RequestContext共享一些状态数据。

尽管Zuul支持任何基于JVM的语言,但是过滤器目前是用Groovy编写的。 每个过滤器的源代码被写入到Zuul服务器上的一组指定的目录中,这些目录将被定期轮询检查是否更新。Zuul会读取已更新的过滤器,动态编译到正在运行的服务器中,并后续请求中调用。

代码

首先需要创建服务注册中心,这里代码就不贴了,不了解的话可以看一下下面这篇博客。
Spring Cloud 之Eureka初使用

创建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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.kaven</groupId>
	<artifactId>zuul</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<name>zuul</name>
	<description>Demo project for Spring Boot</description>



    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.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.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <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-web</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>
        </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>

application.yml如下:

spring:
  application:
    name: zuul

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

server:
  port: 8084

启动类:

package com.kaven.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;

@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulApplication {

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

@EnableDiscoveryClient@EnableZuulProxy注解要记得加上。

创建一个服务提供者。

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 https://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.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.kaven</groupId>
    <artifactId>server-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>server-provider</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.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.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>

application.yml如下:

spring:
  application:
    name: server-provider
    
server:
  port: 8002

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

启动类:

package com.kaven.client;

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

@SpringBootApplication
@EnableDiscoveryClient
public class ServerProviderApplication {

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

@EnableDiscoveryClient注解要记得加上。
接口:

package com.kaven.client.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageController{

    @GetMapping("/getMessage")
    public String getMessage(){
        return "hello , Kaven";
    }
}

访问http://localhost:8761/,可以看到服务全部注册到Eureka上了。
Spring Cloud 之Zuul初使用

访问http://localhost:8084/server-provider/getMessage,可以看到,通过Zuul已经成功请求到服务提供者的接口了。
Spring Cloud 之Zuul初使用
由此得知,Zuul可以通过使用服务提供者注册到Eureka的名称,来路由到该服务提供者指定的接口。

Zuul也可以自己来设置路由路径,需要修改配置:

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

server:
  port: 8084

zuul:
  routes:
#      自定义一个名称
    myRoute:
#      自定义路由路径,**是通配符
      path: /route/**
#      服务提供者注册到Eureka的名称
      serviceId: server-provider

访问http://localhost:8084/route/getMessage,也可以成功请求到服务提供者指定的接口。
Spring Cloud 之Zuul初使用
还有一种方式来自定义路由路径,直接服务提供者注册到Eureka的名称:路由路径即可:

zuul:
  routes:
    server-provider: /route/**

如果某个接口不想通过Zuul被请求到。

zuul:
  routes:
    server-provider: /route/**
  ignored-patterns:
    - /**/getMessage

再访问http://localhost:8084/route/getMessage,可以看到已经404了。
Spring Cloud 之Zuul初使用
该配置参数是Set类型。
Spring Cloud 之Zuul初使用
下面是ZuulProperties的所有属性,通过属性名很容易知道是什么意思。

    public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma", "Cache-Control", "X-Frame-Options", "X-Content-Type-Options", "X-XSS-Protection", "Expires");
    private String prefix = "";
    private boolean stripPrefix = true;
    private Boolean retryable = false;
    private Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap();
    private boolean addProxyHeaders = true;
    private boolean addHostHeader = false;
    private Set<String> ignoredServices = new LinkedHashSet();
    private Set<String> ignoredPatterns = new LinkedHashSet();
    private Set<String> ignoredHeaders = new LinkedHashSet();
    private boolean ignoreSecurityHeaders = true;
    private boolean forceOriginalQueryStringEncoding = false;
    private String servletPath = "/zuul";
    private boolean ignoreLocalService = true;
    private ZuulProperties.Host host = new ZuulProperties.Host();
    private boolean traceRequestBody = true;
    private boolean removeSemicolonContent = true;
    private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
    private boolean sslHostnameValidationEnabled = true;
    private ExecutionIsolationStrategy ribbonIsolationStrategy;
    private ZuulProperties.HystrixSemaphore semaphore;
    private ZuulProperties.HystrixThreadPool threadPool;
    private boolean setContentLength;
    private boolean includeDebugHeader;
    private int initialStreamBufferSize;

比如被请求的接口需要一些Header里面敏感的数据,如CookieSet-CookieAuthorization,可以直接把sensitiveHeaders置空:

zuul:
  sensitive-headers:

接下来介绍Zuul的过滤器怎么使用,原理在之前就介绍过了,这里直接上代码。

package com.kaven.apigateway.filter;

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

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
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 PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest httpServletRequest = requestContext.getRequest();
        if("/route/getMessage".equals(httpServletRequest.getRequestURI())){
            return true;
        }
        return false;
    }

    @Override
    public Object run() throws ZuulException{

        System.out.println("请求通过了");
        return null;
    }
}

使用PRE类型的过滤器。
Spring Cloud 之Zuul初使用
执行顺序是在PRE_DECORATION_FILTER_ORDER之前(越小优先级越高)。
Spring Cloud 之Zuul初使用
执行条件,通过代码很容易知道是通过判断请求URI是否是/route/getMessage,是就通过,否则不通过。
执行体,就输出请求通过了。

自定义过滤器是不是很简单。

自定义限流过滤器:

package com.kaven.apigateway.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;



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

@Component
public class RateFilter extends ZuulFilter {

    private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return SERVLET_DETECTION_FILTER_ORDER - 1;
    }

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

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();

        if(!RATE_LIMITER.tryAcquire()){

            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.BAD_REQUEST.value());
        }

        return null;
    }
}

处理响应数据过滤器:

package com.kaven.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;

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

@Component
public class ResponseFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return POST_TYPE;
    }

    @Override
    public int filterOrder() {
        return SEND_RESPONSE_FILTER_ORDER - 1;
    }

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

    @Override
    public Object run(){
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse httpServletResponse = requestContext.getResponse();

        httpServletResponse.setHeader("kaven","hello");
        return null;
    }
}

Spring Cloud的Zuul组件就介绍到这里。

写博客是博主记录自己的学习过程,如果有错误请指正,谢谢!