• SpringCloud学习之Zuul路由转发、拦截和熔断处理(七)


    Spring Cloud Zuul

    服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。

    Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

    在Spring Cloud体系中, Spring Cloud Zuul 封装了Zuul组件,作为一个API网关,负责提供负载均衡、反向代理和权限认证。

    Zuul工作机制

    过滤器机制

    Zuul的核心是一系列的filters, 其作用类似Servlet框架的Filter,Zuul把客户端请求路由到业务处理逻辑的过程中,这些filter在路由的特定时期参与了一些过滤处理,比如实现鉴权、流量转发、请求统计等功能。Zuul的整个运行机制,可以用下图来描述。

    过滤器的生命周期

    Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示。

    基于Zuul的这些过滤器,可以实现各种丰富的功能,而这些过滤器类型则对应于请求的典型生命周期。

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

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

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

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

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

    Zuul中默认实现的Filter

    Zuul默认实现了很多Filter,这些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 处理正常的请求响应

    我们先看看Zuul的工作原理,Zuul是主要起路由转发和路由拦截作用的,这里直接说Zuul和Feign集成的工作流程如下:

    新建一个Consumer服务消费模块,他的pom.xml添加如下依赖:

    看看他的主程序:

    再看看application.yml里的配置文件:

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
      instance:
          prefer-ip-address: true
    server:
      port: 8768
    spring:
      application:
        name: service-ribbon
    zuul:
      routes:
        #标识你服务的名字,这里可以自己定义,一般方便和规范来讲还是跟自己服务的名字一样
        service-feign:
          #服务映射的路径,通过这路径就可以从外部访问你的服务了,目的是为了不爆露你机器的IP,面向服务的路由了,给你选一个可用的出来,
          path: /service-feign/**
          serviceId: service-feign

    启动我们的注册中心服务、至少一个服务提供者、启动Feign服务消费者、启动我们的Zuul服务消费者,启动正常:

    打开浏览器器,输入http://localhost:8768/service-feign/hello?name=111这是我们基于Feign的请求转发

    打开浏览器器,输入http://localhost:8768/service-feign/hi?name=111这是我们基于RestTemplate的请求转发

    可以看到,我们成功将我们的路由请求转发到Feign服务消费者模块上了,接下来我们要实现以下Zuul对请求的拦截和熔断处理,

    拦截就是在转发之前或者之后当请求到达Feign之前做拦截处理,这个是很重要的功能,熔断是当我们路由转发请求的服务不可用该怎么处理响应呢?

    一、路由请求拦截

    package com.xu.serviceconsumer.component;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Component
    public class MyTokenFilter extends ZuulFilter {
    
        private Logger logger = LoggerFactory.getLogger(MyTokenFilter.class);
    
        //四种类型:pre,routing,error,post
        //pre:主要用在路由映射的阶段是寻找路由映射表的
        //routing:具体的路由转发过滤器是在routing路由器,具体的请求转发的时候会调用
        //error:一旦前面的过滤器出错了,会调用error过滤器。
        //post:当routing,error运行完后才会调用该过滤器,是在最后阶段的
        @Override
        public String filterType() {
            return "pre";
        }
    
        //自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行
        @Override
        public int filterOrder() {
            return 1;
        }
    
        //控制过滤器生效不生效,可以在里面写一串逻辑来控制
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        //执行过滤逻辑
        @Override
        public Object run() {
    
            RequestContext context = RequestContext.getCurrentContext();
            HttpServletRequest request = context.getRequest();
            String url = request.getRequestURL().toString();
            logger.info("请求URL:"+url);
            String token = request.getParameter("name");
            if (token == null){
                context.setSendZuulResponse(false);
                context.setResponseStatusCode(401);
                context.setResponseBody("there is no request parameter name");
    
    
                return null;
            }
            return null;
        }
    }
    

    简单看一下代码知道这个过滤器继承了ZuulFilter,在run()方法里日志输出了请求的URL路径和对参数是否有name做了简单的拦截过滤,请求http://localhost:8768/service-feign/hello?

    可以看到这个Zuul的路由过滤器成功的对这个请求拦截过滤了,最后看一下控制台信息也可以看到成功输出打印了请求的URL

    二、路由熔断

    package com.xu.serviceconsumer.component;
    
    import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
    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.IOException;
    import java.io.InputStream;
    
    @Component
    public class MyFallbackProvider implements FallbackProvider {
    
        @Override
        public String getRoute() {
            return "service-feign";
        }
    
        @Override
        public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
            System.out.println("route:"+route);
            System.out.println("exception:"+cause.getMessage());
            return new ClientHttpResponse() {
                @Override
                public HttpStatus getStatusCode() throws IOException {
                    return HttpStatus.OK;
                }
    
                @Override
                public int getRawStatusCode() throws IOException {
                    return 200;
                }
    
                @Override
                public String getStatusText() throws IOException {
                    return "ok";
                }
    
                @Override
                public void close() {
    
                }
    
                @Override
                public InputStream getBody() throws IOException {
                    return new ByteArrayInputStream(("Sorry, the service "+getRoute()+" is unavailable now.").getBytes());
                }
    
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_JSON);
                    return headers;
                }
            };
        }
    }
    

    注意这里的一段代码

    我们的熔断路由器配置的是service-feign(就是我们zuul请求路由的这个这个服务),我们关闭这个服务,保留其他之前开启的服务,请求http://localhost:8768/service-feign/hi?name=111

    自此,我们可以

    自此,说明我们自定义的熔断器已经起作用了,这课的内容到此为止,下回再见!

    ===============================================================================

    如果您觉得此文有帮助,可以打赏点钱给我支付宝或扫描二维码

  • 相关阅读:
    let 及const
    ES6与ES2015、ES2016以及ECMAScript的区别
    AMD CMD commonJS es6
    千里之行,始于足下
    学习随笔 pyspark JDBC 操作oracle数据库
    学习随笔--pyspark RDD常用操作
    学习随笔--Spark java开发入门
    学习随笔--flask框架基本搭建
    学习随笔--scrapy爬虫简单实例
    学习随笔-python动态爬取空气质量网数据的实现
  • 原文地址:https://www.cnblogs.com/xulijun137/p/12209745.html
Copyright © 2020-2023  润新知