从零开始搭建spring-cloud(5) ----zuul

zuul是什么

zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
zuul的例子可以参考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文档说明来进行使用(https://github.com/Netflix/zuul/wiki)。

zuul的工作原理

过滤器机制

zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
zuul把Request route到 用户处理逻辑 的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等。

486074-20170220185335288-1703224333.png

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:在其他阶段发生错误时执行该过滤器。

 内置的特殊过滤器

zuul还提供了一类特殊的过滤器,分别为:StaticResponseFilter和SurgicalDebugFilter
StaticResponseFilter:StaticResponseFilter允许从Zuul本身生成响应,而不是将请求转发到源。
SurgicalDebugFilter:SurgicalDebugFilter允许将特定请求路由到分隔的调试集群或主机。

自定义的过滤器

除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。
例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

过滤器的生命周期

Zuul请求的生命周期如图,该图详细描述了各种类型的过滤器的执行顺序。

1099841-20170630111344414-1260445909.png

zuul 能做什么

Zuul可以通过加载动态过滤机制,从而实现以下各项功能:

  • 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
  • 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
  • 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
  • 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
  • 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
  • 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。

 搭建zuul服务

首先搭建Eureka server和一个Eureka-Provider

在上一节从零开始搭建spring-cloud(1) ----eureka中拷贝spring-cloud-eureka-server到这个项目中。

在上一节从零开始搭建spring-cloud(1) ----eureka中拷贝spring-cloud-eureka-provider-A-1到这个项目中。

搭建zuul服务

新建项目spring-cloud-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>

    <groupId>com.vincent</groupId>
    <artifactId>spring-cloud-zuul</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

</project>

我们都知道,在zuul过滤器里PRE_TYPE类型是在路由前执行的,所以我要给大家演示配置三个PRE_TYPE类型的过滤器,按照顺序依次处理不同的业务。以及,三个PRE_TYPE类型过滤器中任意一个出现异常时他的下游业务应该怎么处理。

我们首先看一下项目的目录结构:

debc3c354472aab634d4160e4a1a4d438f3.jpg

各个Filter内容如下:

ErrorFilter.java 

package com.vincent.filter;

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

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

/**
 * @author vincent
 * @time 2019-06-23 16:15
 */
@Component
public class ErrorFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return ERROR_TYPE;
    }

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

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

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        System.out.println("这是ErrorFilter");

        return null;
    }
}

FirstPreFilter.java

package com.vincent.filter;

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

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

/**
 * @author vincent
 * @time 2019-06-23 16:17
 */
@Component
public class FirstPreFilter 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 {
        System.out.println("这是第一个自定义Zuul Filter!");
        return null;
    }
}

SecondPreFilter.java

package com.vincent.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;

/**
 * @author vincent
 * @time 2019-06-23 16:18
 */
@Component
public class SecondPreFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

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

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

    @Override
    public Object run() throws ZuulException {
        System.out.println("这是SecondPreFilter!");
        //从RequestContext获取上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        //从上下文获取HttpServletRequest
        HttpServletRequest request = ctx.getRequest();
        //从request尝试获取a参数值
        String a = request.getParameter("a");
        //如果a参数值为空则进入此逻辑
        if (null == a) {
            //对该请求禁止路由,也就是禁止访问下游服务
            ctx.setSendZuulResponse(false);
            //设定responseBody供PostFilter使用
            ctx.setResponseBody("{\"status\":500,\"message\":\"a param is null\"}");
            //logic-is-success保存于上下文,作为同类型下游Filter的执行开关
            ctx.set("logic-is-success", false);
            //到这里此Filter逻辑结束
            return null;
        }
        //设置避免报空
        ctx.set("logic-is-success", true);
        return null;
    }
}

ThirdPreFilter.java

package com.vincent.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;

/**
 * @author vincent
 * @time 2019-06-23 16:19
 */
@Component
public class ThirdPreFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

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

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        //从上下文获取logic-is-success值,用于判断此Filter是否执行
        return (boolean)ctx.get("logic-is-success");
    }

    @Override
    public Object run() throws ZuulException {
        System.out.println("这是ThirdPreFilter!");
        //从RequestContext获取上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        //从上下文获取HttpServletRequest
        HttpServletRequest request = ctx.getRequest();
        //从request尝试获取b参数值
        String b = request.getParameter("b");
        //如果b参数值为空则进入此逻辑
        if (null == b) {
            //对该请求禁止路由,也就是禁止访问下游服务
            ctx.setSendZuulResponse(false);
            //设定responseBody供PostFilter使用
            ctx.setResponseBody("{\"status\":500,\"message\":\"b param is null\"}");
            //logic-is-success保存于上下文,作为同类型下游Filter的执行开关,假定后续还有自定义Filter当设置此值
            ctx.set("logic-is-success", false);
            //到这里此Filter逻辑结束
            return null;
        }
        return null;
    }
}

PostFilter.java

package com.vincent.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

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

/**
 * @author vincent
 * @time 2019-06-23 16:20
 */
public class PostFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return POST_TYPE;
    }

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

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

    @Override
    public Object run() throws ZuulException {
        System.out.println("这是PostFilter!");
        //从RequestContext获取上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        //处理返回中文乱码
        ctx.getResponse().setCharacterEncoding("GBK");
        //获取上下文中保存的responseBody
        String responseBody = ctx.getResponseBody();
        //如果responseBody不为空,则说明流程有异常发生
        if (null != responseBody) {
            //设定返回状态码
            ctx.setResponseStatusCode(500);
            //替换响应报文
            ctx.setResponseBody(responseBody);
        }
        return null;
    }
}

新建App.java,内容如下:

@SpringBootApplication
@EnableZuulProxy
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

我们开始配置application.properties,内容如下:

eureka.client.service-url.defaultZone=http://127.0.0.1:8080/eureka/
spring.application.name=service-zuul

zuul.routes.users.url=http://localhost:8081/
zuul.routes.users.path=/**
zuul.ignored-headers=Access-Controller-Allow-Credentials, Access-Control-Allow-Origin
zuul.host.connect-timeout-millis=10000000
zuul.host.socket-timeout-millis=10000000

server.port=8082

这里zuul的匹配规则是通过url进行匹配。

zuul.host.connect-timeout-millis=10000000
zuul.host.socket-timeout-millis=10000000

这两句的作用是防止服务提供方返回响应的时间过长

先添加参数访问微服务

f16d6ca51f418b7e09cef0913c036286fcd.jpg

控制台输出结果如下:

这是第一个自定义Zuul Filter!
这是SecondPreFilter!
这是PostFilter!

添加参数a访问,

439df5e3dfeaa21537f6b04ef4538aa7441.jpg

控制台输出结果如下:

这是第一个自定义Zuul Filter!
这是SecondPreFilter!
这是ThirdPreFilter!
这是PostFilter!

添加参数a和参数b访问

41e71b69210937cf5babc50222978fd8375.jpg

控制台输出结果如下:

2019-06-23 17:29:44.348  INFO 3363 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
这是第一个自定义Zuul Filter!
这是SecondPreFilter!
这是ThirdPreFilter!
这是PostFilter!

zuul转发有两种配置

根据 eureka server 的serviceId 转发

zuul.routes.serviceName.path=/exampleService/**
zuul.routes.serviceName.serviceId=serviceId

注:
zuul.routes 是固定的
serviceName 是可以随便写的,但最好根据要路由的服务取
serviceId 是 eureka 服务注册时的名称
exampleService 是前端请求某个微服务的一个公共的路径名,如/users
而微服务在 Controller层的 RequestMapping 注解中可以不包含/users

例如本项目中的配置如下:

zuul.routes.myservice.path=/**
zuul.routes.myservice.service-id=service-provider-A

根据具体 URL 转发:

zuul.routes.serviceName.path=/exampleService/**
zuul.routes.serviceName.url=http://127.0.0.1:8080/

如果项目尚未使用eureka,可以采用了第二种转发规则。这种转发有很多好处,最大的好处就是可以很好地过渡到Spring Cloud,使用Zuul可以直接HTTP调用,方便很多。

例如本项目中的URL规则转发如下:

zuul.routes.users.url=http://localhost:8081/
zuul.routes.users.path=/**

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值