SpringBoot + Spring Cloud +Vue 管理系统后台搭建(十五、服务网关Zuul)

前面我们已经通过Ribbon或者Feign实现了负载均衡,那我们的各种微服务如何给外界调用呢?

就是通过我们的网关

使用网关的优点:

易于监控,可在网关上收集监控数据并将其推送到外部系统进行分析

易于认证,在网关上进行认证,然后转发到请求的微服务

客户端只跟服务网关打交道,减少客户端与各个微服务的交互次数

多渠道支持,可以根据不同的客户端(Web、移动端)提供不同的API

 

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

Zuul工作机制

zuul的核心是一系列的filters,作用类似Servlet中的Filter,这些filter在路由的特定时期参与一些过滤处理,比如实现鉴权、流量转发、请求统计等功能。

下面是机制图

生命周期

Filter生命周期有4个阶段分别是PRE、ROUTING、POST、ERROR

下面是生命周期图

 

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

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

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

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

除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。

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

格式 zull.<ClassName>.<filterType>.disable=true

例如禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter

zuul:

    SendResponseFilter:

          post:

               disable: true

 

自定义Filter

public class MyFilter extends ZuulFilter {

    final static Logger logger = LoggerFactory.getLogger(FirstFilter.class);

    // 表示是否需要执行改filter  true表示执行   false表示不执行
    public boolean shouldFilter() {

        return true;
    }

    public Object run() {
       //filter需要执行的具体操作

        return null; //直接返回null即可
    }

    @Override
    public String filterType() {
        // 定义filter类型
        //pre:可以在请求被路由之前调用
        //route:在路由请求时候被调用
        //post:在route和error过滤器之后被调用
        //error:处理请求时发生错误时被调用
        return "pre";
    }

    @Override
    public int filterOrder() {
        //定义filter的顺序 优先级为0,数字越大,优先级越低  
        return 0;
    }
}

下面我们写一个案例

新建一个mango-zuul作为服务网关

添加pom依赖

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.4.RELEASE</version>
      <relativePath /> <!-- lookup parent from repository -->
   </parent>

   <groupId>com.louis</groupId>
   <artifactId>mango-zuul</artifactId>
   <version>${project.version}</version>
   <packaging>jar</packaging>

   <name>mango-zuul</name>
   <description>mango-zuul</description>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <project.version>1.0.0</project.version>
      <java.version>1.8</java.version>
      <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-consul-discovery</artifactId>
      </dependency>
   </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

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

启动类

启动类添加@EnableZuulProxy注解开启服务网关支持

@EnableZuulProxy
@SpringBootApplication
public class MangoZuulApplication {

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

}

配置文件

这里配置在访问localhost:8010/feigin/call和ribbon/call时调用消费者对应的接口

server:
  port: 8010
spring:
  application:
    name: mango-zuul
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        serviceName: ${spring.application.name}    # 注册到consul的服务名称
zuul:
  routes:
    ribbon:
      path: /ribbon/**
      serviceId: mango-consumer  # 转发到消费者 /ribbon/
    feign:
      path: /feign/**
      serviceId: mango-consumer  # 转发到消费者 /feign/

到此我们配置完成,配置完依次启动注册中心、监控(monitor)、服务提供者(product)、服务消费者(consumer)、服务网关(zuul)

然后访问http://localhost:8010/ribbon/callhttp://localhost:8010/feign/call这俩地址

我们可以看到zuul已经成功转发请求,并成功调用后端微服务。

 

配置接口前缀

如果想给每一个服务的API接口加上一个前缀,可以使用zuul.prefix进行配置。

例如:http://localhost:8010/v1/feign/call

zuul:
  prefix: /v1   #配置接口前缀
  routes:
    ribbon:
      path: /ribbon/**
      serviceId: mango-consumer  # 转发到消费者 /ribbon/
    feign:
      path: /feign/**
      serviceId: mango-consumer  # 转发到消费者 /feign/

默认路由

上面我们是通过添加路由配置进行请求转发,但是如果微服务非常多的话,配置起来比较麻烦。

spring cloud zuul已经帮我们做了默认配置,默认情况下,Zuul会代理所有注册到注册中心的微服务,并且Zuul的默认路由规则

是:http://ZUUL_HOST:ZUUL_PORT/微服务在注册中心的serviceId/** 会被转发到serviceId对应的微服务。

遵循这个规则我们就不需要配置路由转发了

例如:http://localhost:8010/mango-consumer/feign/call

就可以直接访问到mango-consumer中的方法

路由熔断

Zuul作为Netflix组件,可以与Ribbon、Eureka、Hystrix等组件结合,实现负载均衡、熔断器的功能。

默认情况下Zuul与Ribbon相结合,实现了负载均衡。实现熔断器功能需要实现FallbackProvider接口。实现该接口有两个方法:

1、getRoute() 用于指定熔断器功能应用于哪些路由服务

2、fallbackResponse() 为进熔断器功能时执行的逻辑

 

创建FallbackProvider类

@Component
public class MyFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        return "mango-consumer";
    }

    @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 is unavailable now.".getBytes());
            }

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

这里getRoute()方法返回mango-consumer,只针对consumer服务进行熔断。

如果需要所有的路由服务都增加熔断,需要在getRoute() 方法返回“*”匹配符

getBody()方法返回给熔断时的反馈信息

到此,我们依次启动注册中心、监控(monitor)、服务提供者(product)、服务消费者(consumer)、服务网关(zuul)

正常访问没有问题

当我们停掉consumer再访问,可以看到我们的熔断已经起作用了

自定义Filter

创建一个MyFilter,继承ZuulFilter类,覆写run()方法逻辑,在转发请求前进行token认证,如果没有token,返回“there is norequest token”提示

@Component
public class MyFilter extends ZuulFilter {

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

    @Override
    public String filterType() {
        return "pre"; // 定义filter的类型,有pre、route、post、error四种
    }

    @Override
    public int filterOrder() {
        return 0; // 定义filter的顺序,数字越小表示顺序越高,越先执行
    }

    @Override
    public boolean shouldFilter() {
        return true; // 表示是否需要执行该filter,true表示执行,false表示不执行
    }

    @Override
    public Object run() throws ZuulException {
        // filter需要执行的具体操作
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("token");
        System.out.println(token);
        if(token==null){
            log.warn("there is no request token");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("there is no request token");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
        log.info("ok");
        return null;
    }
}

这样zuul就会自动加载Filter执行过滤了。重启我们的Zuul项目

访问:http://localhost:8010/mango-consumer/feign/call

可以看到已经返回我们自定义Filter的提示

下面我们加上token访问http://localhost:8010/mango-consumer/feign/call?token=1111

可以看到访问成功了

 

Zuul作为API网关服务,不同的客户端使用不同的负载将请求统一分发到后端的Zuul,

再由Zuul转发到后端服务。为了保证Zuul的可用性,前端可以同时开启多个Zuul实例进行负载均衡,

另外,在zuul的前端还可以使用Nginx或者F5再次进行负载转发,从而保证Zuul的高可用性。

 

好啦,Zuul我们就学到这里拉,看完记得点赞哦!!!

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值