SpringCloud学习(八)Zuul的介绍和进阶使用(Zuul自定义过滤器和整合Hystrix)

一. 综述
Spring Cloud 集群提供了多个组件,用于进行集群内部的通信,例如服务管理组件Eureka,负载均衡组件Ribbon。如果集群提供了API或者Web服务,需要与外部进行通信,比较好的方式是添加一个网关,将集群的服务都隐藏到网关后面。这种做法对于外部客户端来说,无须关心集群的内部结构,只需关心网关的配置等信息。对于Spring Cloud集群来说,不必过多暴露服务,提升了集群的安全性。Zuul是Ntlix的一个子项目,Spring Cloud将Zuul进行了进一步的实现与封装,将其整合到spring-netflix项目中,为微服务集群提供代理、过滤、路由等功能。

二. 运行机制
当我们使用了@EnableZuulProxy注解。开启该注解之后,在Spring容器初始化的时候,会将Zuul的相关配置初始化,其中包含一个Spring Boot的Bean:ServletRegistrationBean,该类主要用于注册Servlet。Zuul提供了一个ZuulServlet类,在Servlet的service 方法中,执行各种Zuul过滤器(ZuulFilter)。如图所示是Http请求在ZuulServlet中的生命周期。
在这里插入图片描述
ZuulServlet的service方法接收到请求后,会执行pre阶段的过滤器,再执行routing阶段的过滤器,最后执行post阶段的过滤器。其中routing阶段的过滤器会将请求转发到“源服务”,源服务可以是第三方的Web服务,也可以是Spring Cloud的集群服务。在执行pre和routing阶段的过滤器时,如果出现异常,则会执行error过滤器。整个过程的HTTP请求、HTTP响应、状态等数据,都会被封装到一个RequestionContext对象中。如图是各阶段的一些具体过滤器。
在这里插入图片描述
旁边的数字表示他们的执行顺序,越小优先级越高。

三. 代码实例
SpringCloud版本:Greenwich.RELEASE
SpringBoot版本:2.1.4.RELEASE

  1. 新建zuul模块引入依赖

     		<!-- zuul -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
    
            <!-- Eureka客户端 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
                <!-- 这里会自动引入版本,类似parent标签继承 -->
            </dependency>
        <dependencyManagement>
        <dependencies>
            <!-- <scope>import</scope>解决单继承问题,类似parent标签, -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  2. 在主启动类上标记@EnableZuulProxy注解,前面已经讲了该注解的作用

  3. 如果网关作为Eureka客户端注册到Eureka服务器时,可以通过ServiceId将请求转发到集群的服务中。那就需要在配置文件中配置Eureka的地址(这个@EnableEurekaClient可加可不加,@EnableZuulProxy默认开启了)

    # 注册到Eureka中
    eureka:
      instance:
        instance-id: ${info.application.name}
      client:
        service-url:
          defaultZone: http://localhost:7000/eureka/,http://feiyu:7001/eureka/
    
  4. 配置文件设置路由转发规则

    # 网关转发配置
    zuul:
      routes: # 提供映射规则
        fei: # map的key值,任意即可
          path: /fei/** # 映射路径
          serviceId: springcloud-employee-client-feign  # 服务名
        yu:
          path: /yu/** # 映射路径
          serviceId: springcloud-employee-provider-feign  # 服务名
      ignored-services: "*"  # 忽略所有的服务,即不能使用服务id访问
    

    也可以简写成:(serviceId省略,使用routeId代替)

    # 网关转发配置
    zuul:
      routes:
        springcloud-employee-client-feign: # serviceId省略,使用routeId代替
          path: /fei/** # 映射路径	  
        springcloud-employee-provider-feign: # serviceId省略,使用routeId代替
          path: /yu/** # 映射路径	    
      ignored-services: "*"  # 忽略所有的服务,即不能使用服务id访问
    
  5. 配置完成,启动项目,使用http://localhost:5000/fei/dept/list地址可以访问到springcloud-employee-client-feign这个服务的dept/list接口
    注意:默认集成了Ribbon,实现了服务访问的负载均衡。

四. Zuul进阶

4.1 Zuul与Hystrix

当我们对网关进行配置让其调用集群的服务时,将会执行Ribbon路由过滤器(RibbonRoutingFilter)。该过滤器在进行转发时会封装成一个Hystrix命令予以执行。换言之,它具有容错的功能。如果 “源服务” 出现问题(例如超时),那么所执行的Hystrix命令将会触发回退。
我们可以在配置文件中设置Ribbon调用的超时时间。

ribbon:
  readTimeout: 2000
  connectTimeout: 2000

还可以自定义一个回退处理类,只需实现FallbackProvider接口即可(代码上都有详细注释)

package com.fei.springcloudzuul5000.hystrix;

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;

/**
 * @Author: xiaoshijiu
 * @Date: 2019/7/13
 * @Description: Zuul中使用Hystrix处理回退
 * @Component需要将其加到IOC容器中
 */
@Component
public class MyFallBackProvider implements FallbackProvider {

    /**
     * 返回路由的名称,表示对哪个路由处理,”*“表示对所有服务处理
     */
    @Override
    public String getRoute() {
        return "*";
    }

    /**
     * 具体回退返回逻辑,一个Http响应
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {

            /**
             * 响应头
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.TEXT_PLAIN);
                return httpHeaders;
            }

            /**
             * 响应体
             */
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("当前服务不可用,请稍后再试".getBytes());
            }

            /**
             * 响应码,状态码
             */
            @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() {

            }
        };
    }
}

当 ”源服务“ 出现问题,就会执行这里定义的回退逻辑,返回已经定义好的结果。

4.2 自定义Zuul过滤器
自定义Zuul过滤器也比较容易,只需继承ZuulFilter类即可

package com.fei.springcloudzuul5000.filter;

import com.fei.common.log.Loggable;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.RequestDispatcher;

/**
 * @Author: xiaoshijiu
 * @Date: 2019/7/13
 * @Description: 自定义Zuul过滤器
 * @Component: 需要让Spring知道他的存在,即加入IOC容器中
 */
@Component
public class MyTestFilter extends ZuulFilter implements Loggable {

    /**
     * 判断是否需要执行error过滤器的条件常量
     */
    private static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";

    /**
     * 过滤器类型
     * @pre: 请求过来就执行,最先
     * @routh:路由阶段,决定如何进行路由
     * @post:路由之后执行的过滤器
     * @error:发生异常时执行的过滤器
     */
    @Override
    public String filterType() {
        return FilterConstants.ROUTE_TYPE;
    }

    /**
     * 执行顺序,数字越小表示执行顺序越靠前
     * @这里设置为1,在routh中是最小的了
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 是否执行
     */
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        String uri = ctx.getRequest().getRequestURI();
        // 这里指定一下具体执行的路径
        if (uri.indexOf("/static") != -1) {
            return true;
        }
        return false;
    }

    /**
     * 具体处理逻辑
     */
    @Override
    public Object run() throws ZuulException {
        getLog().warn("用于测试的zuul过滤器来了!!");
        RequestContext ctx = RequestContext.getCurrentContext();
        RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher("/static");
        if (dispatcher != null) {
            // 设置为true,表示不会再去执行error过滤器了
            ctx.set(SEND_ERROR_FILTER_RAN, true);
            // 设置为false,表示不会再去执行其他过滤器了
            ctx.setSendZuulResponse(false);
            try {
                // 转发
                dispatcher.forward(ctx.getRequest(), ctx.getResponse());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

每个方法代表什么意思,上面都有详细注解说明。
该过滤器的作用就是:拦截带有/static的请求,将其转发到已经定义好的/static的静态页面(写得可能比较粗糙!!)
多看看源码就都懂了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值