SpringCloud Zuul 微服务网关

前言

ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的 核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 动态路由:动态将请求路由到不同后端集群
  • 压力测试:逐渐增加指向集群的流量,以了解性能
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  • 静态响应处理:边缘位置进行响应,避免转发到内部集群
  • 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。Spring Cloud对Zuul进行了整合和增强。

zuul网关搭建

创建工程导入依赖

在IDEA中创建ZUUL网关工程 shop_zuul_server ,并添加响应依赖

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

编写启动类

创建启动类 ZuulServerApplication

@SpringBootApplication
@EnableZuulProxy // 开启Zuul的网关功能
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class,args);
    }
}

编写配置

创建配置文件 application.yml ,并添加相应配置

server:
  port: 8080

spring:
  application:
    name: shop-zuul-server

zuul中的路由转发

最直观的理解:“路由”是指根据请求URL,将请求分配到对应的处理程序。在微服务体系中,Zuul负责 接收所有的请求。根据不同的URL匹配规则,将不同的请求转发到不同的微服务处理。

zuul:
  routes:
    product-service:
      path: /order-service/** # 这里是映射路径自定义
      url: http://127.0.0.1:9002 # 映射路径对应的实际url地址
      sensitiveHeaders: #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取 消默认的黑名单,如果设置了具体的头信息则不会传到下游服务

以上配置会将请求路径为order-service开头的请求转发到http://127.0.0.1:9002进行处理

面向服务的路由

微服务一般是由几十、上百个服务组成,对于一个URL请求,最终会确认一个服务实例进行处理。如果 对每个服务实例手动指定一个唯一访问地址,然后根据URL去手动实现请求匹配,这样做显然就不合理。

Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的 好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的 路由配置。

引入依赖

添加Eureka客户端依赖

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

添加注解

ZuulServerApplication启动类上添加@EnableDiscoveryClient注解开启Eureka客户端功能

@SpringBootApplication
@EnableZuulProxy // 开启Zuul的网关功能
@EnableDiscoveryClient //开启eureka
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class,args);
    }
}

YML配置

在application.yml中添加eureka相关配置

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
  instance:
    prefer-ip-address: true #使用ip注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册的服务id

路由配置

因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而 是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。

zuul:
  routes:
    product-service:
      path: /order-service/** # 这里是映射路径
      serviceId: shop-service-order # 配置转发的微服务名称

依次启动Eureka,product,order,微服务之后访问 http://127.0.0.1:8079/order-service/order/buy/1即可看到最终效果

路由配置的简化

在上面的配置中,我们的配置规则是这样的

  • zuul.routes.<route>.path=/xxx/** : 来指定映射路径
  • zuul.routes.<route>.serviceId= shop-service-order :来指定服务名。

而大多数情况下,我们的 路由名称往往和服务名会写成一样的。因此Zuul就提供了一种简化的 配置语法: zuul.routes.<serviceId>=<path>

zuul:
  routes:
    shop-service-order: /order-service/** 

默认的路由规则

在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则,默认情况下,一切服务的映射路径就是服务名本身
例如:服务名为:shop-service-order ,则默认的映射路径就是: /shop-service-order/**
因此如果使用默认路由配置的话,就无须配置了

Zuul中的过滤器

Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,它们能够在进行 HTTP 请求或者响应的时候执行相关操作。可以说,没有 Filter 责任链,就没有如今的 Zuul,更不可能构成功能丰富的网关。基本上你想要在网关实现的功能都要与 Filter 有关。它是 Zuul 中最为开放与核心的功能。 Zuul Filter 中的过滤器一共有四种类型

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

生命周期

  • 正常流程
    请求到达首先会经过pre类型过滤器,然后到达ruoting类型过滤器,进行路由,请求就到达真正的 服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程

    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕 后,会将请求交给POST过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求 不会再到达POST过滤器了。
  • 不同的过滤器场景

    • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
    • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
    • 服务调用时长统计:pre和post结合使用。

自定义过滤器

定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数, 则认为请求有效,放行。

@Component
public class LoginFilter extends ZuulFilter {
    @Override
    public String filterType() {
        // 登录校验,肯定是在前置拦截
        return "pre";
    }

    @Override
    public int filterOrder() {
        // 顺序设置为1
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        // 返回true,代表过滤器生效。
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 登录校验逻辑。
        // 1)获取Zuul提供的请求上下文对象
        RequestContext ctx = RequestContext.getCurrentContext();
        // 2) 从上下文中获取request对象
        HttpServletRequest request = ctx.getRequest();
        // 3) 从请求中获取token
        String token = request.getParameter("access-token");
        // 4) 判断
        if(token == null || "".equals(token.trim())){
            // 没有token,登录校验失败,拦截
            ctx.setSendZuulResponse(false);
            // 返回401状态码。也可以考虑重定向到登录页。
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        // 校验通过,可以考虑把用户信息放入上下文,继续向后执行
        return null;
    }
}

关于Zuul存在的问题

在实际使用中我们会发现直接使用Zuul会存在诸多问题包括:

  • 性能问题
    Zuul1x版本本质上就是一个同步Servlet,采用多线程阻塞模型进行请求转发。简单讲,每来 一个请求,Servlet容器要为该请求分配一个线程专门负责处理这个请求,直到响应返回客户 端这个线程才会被释放返回容器线程池。如果后台服务调用比较耗时,那么这个线程就会被 阻塞,阻塞期间线程资源被占用,不能干其它事情。我们知道Servlet容器线程池的大小是有 限制的,当前端请求量大,而后台慢服务比较多时,很容易耗尽容器线程池内的线程,造成 容器无法接受新的请求。
  • 不支持任何长连接,如websocket

Zuul 1.x 是一个基于阻塞 IO 的 API Gateway 以及 Servlet;直到 2018 年 5 月,Zuul 2.x(基于 Netty,也是非阻塞的,支持长连接)才发布,但 Spring Cloud 暂时还没有整合计划

添加新评论

评论列表