SpringCloud-微服务-Zuul


路由网关 SpringCloud Zuul

智能路由网关组件Zuul,作为微服务系统的网关组件,用于构建边界服务。致力于动态路由,过滤,监控,弹性伸缩和安全。

为什么需要Zuul?

  • Zuul Ribbon 以及 Eureka 相结合,可以实现智能路由和负载均衡的功能, Zuul 能够将请求流量按某种策略分发到集群状态的多个服务实例。
  • 网关将所有服务的 API 接口统一聚合,并统一对外暴露。外界系统调用 API 接口时,都是由网关对外暴露的 API 接口,外界系统不需要知道微服务系统中各服务相互调用的复杂性。微服务系统也保护了其内部微服务单元的 API 接口,防止其被外界直接调用,导致服务的敏感信息对外暴露。
  • 网关服务可以做用户身份认证和权限认证,防止非法请求操作 API 接口,对服务器起到保护作用。
  • 网关可以实现监控功能,实时日志输出,对请求进行记录。
  • 网关可以用来实现流量监控,在高流量的情况下,对服务进行降级。
  • API 接口从内部服务分离出来 ,方便做测试。

Zuul工作原理

  • PRE 过滤器 它是在请求路由到具体的服务之前执行的,这种类型的过滤器可以做安全验证,例如身份验证、 参数验证等。
  • ROUTING 过滤器,它用于将请求路由到具体的微服务 。在默认情况下,它使用Http Client 进行网络请求。
  • POST 过滤器:它是在请求己被路由到微服务后执行的。一般情况下,用作收集统计信息、指标,以及将响传输到客户端。
  • ERROR 过滤器:它是在其他过滤器发生错误时执行的。
    Zuul 采取了动态读取、编译和运行这些过滤器,过滤器,间不能直接通信,而是通过RequestContext 对象来共享数据 。每个请求都会创建 RequestContext 对象。

Zuul 请求的生命周期

当一个客户端 Request 请求进入 Zuul 服务时,网关先进入“pre filter”进行一系列的验证、操作或者判断。然后交给”routing filter”进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完后,最后由”post filter”,进行处理,该类型的处理器处理完之后,将 Response 信息返回给客户端。

Zuul请求生命周期

搭建 Zuul 服务

配置

在上一个案例基础上,新建module,eureka-zuul-client,然后添加依赖:

<?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.zwq</groupId>
        <artifactId>eureka</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zwq</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.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

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

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

</project>

修改application.yml配置文件:

spring:
  application:
    name: service-zuul
server:
  port: 5000

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

zuul:
  routes:
    hiapi:
      path: /hiapi/**
      serviceId: eureka-client
    ribbonapi:
      path: /ribbonapi/**
      serviceId: eureka-ribbon-client
  prefix: /zwq

来看zuul配置:
zuul.routes.hiapi.path和serviceId可以将以”/hiapi”开头的url路由到eureka-client服务,hiapi是自定义的,需要指定path,serciceId,下面的ribbonapi同理
最后的zuul.prefix是给每个服务 API 接口加上前缀。

访问测试

启动eureka-server、2个eureka-client实例、eureka-ribbon-client、eureka-zuul-client服务。
多次访问浏览器:http://localhost:5000/hiapi/hi?name=zwq ,浏览器会轮流显示:


可见Zuul在路由转发做了负载均衡。

zuul使用熔断器

配置

在Zuul 实现熔断功能需要实现 ZuulFallbackProvider 接口。实现该接口有两个方法,一个是getRoute方法,用于指定熔断功能应用于哪些路由的服务,另一个方法 fallbackResponse为进入熔断功能时执行的逻辑
在eureka-zuul-client工程创建config包,新建MyFallBackProvider类实现ZuulFallbackProvider:

import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
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.InputStream;

/**
 * description: 熔断
 *
 * @author zwq
 * @date 2021/10/22 15:19
 */
@Component
public class MyFallBackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "eureka-client";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() {
                return 200;
            }

            @Override
            public String getStatusText() {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() {
                return new ByteArrayInputStream("ops,error,i'm fallback".getBytes());
            }

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

启动测试

启动 eureka-zuul-client 工程,并且关闭 eureka-client 的所有实例,在浏览器上访问
http://localhost:5000/hiapi/hi?name=zwq ,浏览器显示:

以上代码是对eureka-client服务的熔断器,其中把getRoute()方法返回值改成”*”,则是给所有路由功能加熔断功能。

zuul使用过滤器

配置

zuul使用过滤器需要继承 ZuulFilter ,并实现 ZuulFilter 中的抽象方法,包括 filterType,filterOrder,以及IZuulFilter的shouldFilter和 Object run()的两个方法。其中:

  • filterType()即过滤器的类型,它有 4种类型:pre,post,routing,error。
  • filterOrder()是过滤顺序,它为 Int 类型的值,值越小,越早执行该过滤器。
  • shouldFilter()表示该过滤器是否过滤逻辑,如果为 true ,则执行 run()方法:如果为 false ,则不执行 run()方法。run()方法写具体的过滤的逻辑

下面来模拟一个用户身份验证功能,使用过滤器检查请求参数中是否传了token这个参数,如果没有,直接返回响应,不被路由。
在eureka-zuul-client工程的config包中新建MyFilter类:

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

import javax.servlet.http.HttpServletRequest;

/**
 * description: 过滤器
 *
 * @author zwq
 * @date 2021/10/22 15:55
 */
@Component
public class MyFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

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

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

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

访问测试

开启服务eureka-server、eureka-client、eureka-ribbon-client、eureka-zuul-client:
访问:http://localhost:5000/zwq/ribbonapi/hi?name=zwqzwq ,提示没有token:

接下来携带token: http://localhost:5000/zwq/ribbonapi/hi?name=zwqzwq&token=121212 ,成功:

可见, MyFilter对请求进行了过滤,并在请求路由转发之前进行了逻辑判断。在实际开发中,可以用此过滤器进行安全验证。
本例架构图:

zuul常见使用方式架构图:


  目录