八、springcloud之服务网关zuul(一)

一、Zuul简介

  •   zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
  •   Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
  •   Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
  •   zuul的例子可以参考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文档说明来进行使用。
  •   Spring Cloud Zuul路由是微服务架构的不可或缺的一部分,提供动态路由,监控,弹性,安全等的边缘服务。
  •   Spring Cloud Zuu默认和Ribbon结合实现了负载均衡的功能。

二、zuul的工作原理

zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。

zuul把Request route到 用户处理逻辑 的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等。  

  com.netflix.zuul.http.ZuulServlet是ZuulFilter链执行的入口,通过service方法,提取请求到RequestContext,然后通过调用ZuulRunner,依次按顺序执行每种类型的Filter,完成整个Filter的生命周期,架构图如下所示。

  在zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。 

  执行流程图

  

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

    (1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

    (2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

    (3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

    (4) ERROR:在其他阶段发生错误时执行该过滤器。

  功能:

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

  ZuulServlet - 处理请求(调度不同阶段的filters,处理异常等) 

  • ZuulServlet类似SpringMvc的DispatcherServlet,所有的Request都要经过ZuulServlet的处理
  • 三个核心的方法preRoute(),route()postRoute(),zuul对request处理逻辑都在这三个方法里
  • ZuulServlet交给ZuulRunner去执行。
  • 由于ZuulServlet是单例,因此ZuulRunner也仅有一个实例。
  • ZuulRunner直接将执行逻辑交由FilterProcessor处理,FilterProcessor也是单例,其功能就是依据filterType执行filter的处理逻辑
  • FilterProcessor对filter的处理逻辑。
  1. 首先根据Type获取所有输入该Type的filter,List<ZuulFilter> list
  2. 遍历该list,执行每个filter的处理逻辑,processZuulFilter(ZuulFilter filter)
  3. RequestContext对每个filter的执行状况进行记录,应该留意,此处的执行状态主要包括其执行时间、以及执行成功或者失败,如果执行失败则对异常封装后抛出。 
  4. 到目前为止,zuul框架对每个filter的执行结果都没有太多的处理,它没有把上一filter的执行结果交由下一个将要执行的filter,仅仅是记录执行状态,如果执行失败抛出异常并终止执行。

  zuul的原理及生命周期深入解析:https://www.cnblogs.com/lexiaofei/p/7080257.html

  源码解析:

    https://blog.csdn.net/forezp/article/details/76211680(可以增加做日志处理)

    http://xujin.org/sc/sc-zuul-s2/

三、zuul入门使用

  1、添加依赖

  引入spring-cloud-starter-zuul

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

  2、配置文件

  应用名、服务端口等

spring.application.name=gateway-service-zuul
server.port=8888

#路由
#这里的配置表示,访问/it/** 直接重定向到http://www.baidu.com/**
zuul.routes.api-a-url.path=/it/**
zuul.routes.api-a-url.url=http://www.baidu.com/

    服务路由:

      在Zuul中提供了两种映射方式:

        方式一:通过url直接映射,上面配置就是

          api-a-url部分为路由的名字,可以任意定义,但是一组映射关系的path和url要相同

        方式二:服务化的方式(使用serviceId的映射方式)(推荐)

          通过url映射的方式对于Zuul来说,并不是特别友好,Zuul需要知道我们所有为服务的地址,才能完成所有的映射配置。而实际上,我们在实现微服务架构时,服务名与服务实例地址的关系在eureka server中已经存在了,所以只需要将Zuul注册到eureka server上去发现其他服务,我们就可以实现对serviceId的映射

        需要添加spring-cloud-starter-eureka依赖

        修改配置:

 

zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A

zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

 

        针对我们在准备工作中实现的两个微服务service-A和service-B,定义了两个路由api-a和api-b来分别映射。另外为了让Zuul能发现service-A和service-B,也加入了eureka的配置。

    这两种方式推荐使用serviceId的映射方式,除了对Zuul维护上更加友好之外,serviceId映射方式还支持了断路器,对于服务故障的情况下,可以有效的防止故障蔓延到服务网关上而影响整个系统的对外服务

 

  3、启动类

  启动类添加@EnableZuulProxy,支持网关路由。

@SpringCloudApplication
@EnableZuulProxy
public class GatewayServiceZuulApplication {

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

  注:1、这里用了@SpringCloudApplication注解,之前没有提过,通过源码我们看到,它整合了@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreaker,主要目的还是简化配置。

    @EnableCircuitBreaker:启动断路器

    2、@EnableZuulProxy简单理解为@EnableZuulServer的增强版,当Zuul与Eureka、Ribbon等组件配合使用时,我们使用@EnableZuulProxy。 

  4、服务过滤

  一、在服务网关中定义过滤器只需要继承ZuulFilter抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。

public class AccessFilter extends ZuulFilter  {

    private static Logger log = LoggerFactory.getLogger(AccessFilter.class);

    @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();

        log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));

        Object accessToken = request.getParameter("accessToken");
        if(accessToken == null) {
            log.warn("access token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            return null;
        }
        log.info("access token ok");
        return null;
    }

}
View Code
  1. filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
    • pre:可以在请求被路由之前调用
    • routing:在路由请求时候被调用
    • post:在routing和error过滤器之后被调用
    • error:处理请求时发生错误时被调用
  2. filterOrder:通过int值来定义过滤器的执行顺序
  3. shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。
  4. run:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回body内容进行编辑等。

  Zuul中默认实现的Filter

    

类型顺序过滤器功能
pre -3 ServletDetectionFilter 标记处理Servlet的类型
pre -2 Servlet30WrapperFilter 包装HttpServletRequest请求
pre -1 FormBodyWrapperFilter 包装请求体
route 1 DebugFilter 标记调试标志
route 5 PreDecorationFilter 处理请求上下文供后续使用
route 10 RibbonRoutingFilter serviceId请求转发
route 100 SimpleHostRoutingFilter url请求转发
route 500 SendForwardFilter forward请求转发
post 0 SendErrorFilter 处理有错误的请求响应
post 1000 SendResponseFilter 处理正常的请求响应

  禁用指定的Filter

  可以在application.yml中配置需要禁用的filter,格式:

zuul:
    FormBodyWrapperFilter:
        pre:
            disable: true

 

  二、在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,我们只需要在应用主类中或者在配置类中配置:

@Configuration
public class MyZuulFilterConfig {
    @Bean
    public AccessFilter accessFilter() {
        return new AccessFilter();
    }
}

  启动该服务网关后,访问:

  • http://localhost:5555/api-a/add?a=1&b=2:返回401错误
  • http://localhost:5555/api-a/add?a=1&b=2&accessToken=token:正确路由到server-A,并返回计算内容

四、服务网关的重要性

  • 不仅仅实现了路由功能来屏蔽诸多服务细节,更实现了服务级别、均衡负载的路由。
  • 实现了接口权限校验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
  • 实现了断路器,不会因为具体微服务的故障而导致服务网关的阻塞,依然可以对外服务。

参考:http://blog.didispace.com/springcloud5/

   http://www.ityouknow.com/springcloud/2017/06/01/gateway-service-zuul.html

posted @ 2018-06-29 11:52  灬小乙  阅读(439)  评论(0编辑  收藏  举报