• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

6.服务网关Zuul

武飞扬头像
Maybe !
帮助1

注:本人所有的spring-cloud系列的文章均为黑马的《Spring Cloud微服务架构开发》的个人笔记。

服务网关Zuul

学习目标:

  • 认识服务网关Zuul
  • 掌握Zuul路由的映射规则配置
  • 掌握Zuul与Hystrix结合实现熔断
  • 掌握Zuul中的Eagerload配置
  • 掌握Zuul的4个核心过滤器及请求生命周期
  • 了解Zuul自定义过滤器的使用

目录:

  1. 认识Zuul
  2. 快速入门Zuul
  3. Zuul路由的映射规则配置
  4. Zuul和Hystrix结合实现熔断
  5. Zuul中是Eager Load配置
  6. Zuul的过滤器

1.认识Zuul

客户端与各个微服务通信,存在许多问题:

  1. 客户端会多次请求不同的微服务,使客户端变得更为复杂。
  2. 存在跨域请求,在一定场景下处理相对复杂。例如,在重定向或js.发起的ajax请求时,会因为域名不同、二级域名不同、子域名不同或端口号不同等因素,处理变得相对复杂。
  3. 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个,这种情况下,如果是客户端与微服务直接通信的架构,那么用户访问每一个接口的请求路径都需要改变,这就使得重构变得难以实现。
  4. 某些微服务可能会设置防火墙等不友好的协议,难以做到直接访问。

针对上述问题,可以使用服务网关解决。服务网关相当于介于客户端和服务端之间的中间层,所有的外部请求都会先经过服务网关进行调度和过滤。

Zuul是Netflix的一个开源组件,它是通过Servlet实现的。Zuul作为Spring Cloud中的服务网关组件,它能够通过与Spring Cloud Eureka进行整合,将自身注册到EurekaServer中,与Eureka、Ribbon、Hystrix等整合,同时从Eureka中获得其他微服务实例信息。这样的设计通过把网关和服务治理整合到一起,Spring Cloud Zuul可以获取到服务注册信息,结合Ribbon,Hystrix等更好地实现路由转发、负载均衡等功能。
学新通

2.快速入门Zuul

案例:

2.1 创建eureka-server,可用之前的模块

2.1.1 pom.xml

<?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 https://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.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.li</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-server</name>
    <description>eureka-server</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
</project>
学新通

2.1.2 application.yml

spring:
  application:
    name:eureka-server
server:
  port: 7000
eureka:
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
  instance:
    hostname: server1

2.1.3 启动类添加注解@EnableEurekaServer

2.2 创建服务提供者eureka-provider

2.2.1 pom.xml

添加eureka-client 、test 、web依赖

2.2.2 application.yml

spring:
  application:
    name: eureka-provider
server:
  port: 7009
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka
  instance:
    hostname: localhost

2.2.3 启动类添加注解@EnableEurekaClient

2.2.4 创建controller.providerController类

package com.li.eurekaprovider.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderController {

    @GetMapping("/hi")
    public String hi(String id){
        return "访问服务提供者hi()接口成功,当前id:" id;
    }
}

2.3 创建服务消费者eureka-comsumer

2.3.1 pom.xml

添加web、 test 、eureka-client依赖

2.3.2 application.yml

spring:
  application:
    name: eureka-consumer
server:
  port: 8764
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka

2.3.3 启动类添加注解@EnableEurekaClient

2.3.4 创建config.RestTemplateConfig类

package com.li.eurekaconsumer.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
学新通

2.3.5 创建service.ConsumerService类

package com.li.eurekaconsumer.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

@Service
public class ConsumerService {

    @Autowired
    RestTemplate restTemplate;
    public String hi(@RequestParam("id") String id){
        return restTemplate.getForObject(
        "http://eureka-provider/hi?id=" id,String.class);
    }
}

学新通

2.3.6 创建controller.ConsumerController类

package com.li.eurekaconsumer.controller;

import com.li.eurekaconsumer.service.ConsumerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConsumerController {
    @Autowired
    ConsumerService consumerService;
    @GetMapping("/hi")
    public String hi(String id){
        return consumerService.hi(id);
    }
}

学新通

2.4 创建服务网关geteway-zuul模块

2.4.1 pom.xml

添加web 、test、 eureka-client、 zuul依赖

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

2.4.2 appliation.yml

spring:
  application:
    name: gateway-zuul
server:
  port: 8835
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka/
zuul:
  routes:
    eureka-consumer:
      path: /eureka-consumer/**  #zuul路由的前缀

2.4.3在启动类添加注解@EnableZuulProxy

2.5 测试

依次启动eureka-server, eureka-provider,eureka-consumer,gatew-zuul

访问http://localhost:8764/hi?id=10
学新通

访问http://localhost:8835/eureka-consumer/hi?id=10
学新通
说明成功使用网关
.

3.路由的映射规则配置

3.1 服务路由配置

zuul:
  routes:
    eureka-consumer:
      path: /eureka-consumer/**
      serviceId: eureka-consumer

上述示例代码的作用是将符合/eureka-consumer/** 规则的请求路径转发到名为eureka-consumer的服务实例上,其中routes可以指定为任意的路由名称,这里我们指定的是eureka-consumer.
对于面向服务的路由配置,除了使用path与serviceId映射的配置方式之外,还有一种 简洁的配置方式,即zuul. routes. < serviceId> = < path>,其中用来指定路由的具体服务名, 用 来配置匹配的请求映射地址。示例代码如下:

zuul:
  routes:
 # <serviceId> = <path>
    eureka-consumer:/eureka-consumer/**

3. 2 服务路由的默认规则

在Zuul快速入门案例中,我们尝试使用浏览器访问
http://localhost : 8835/eureka-consumer/hi?id=12,虽然创建的gateway-zuul工程的配置文件中并没有相应的服务路由配置,但是却能够成功访问并且返回结果。原因就是Zuul有自己的默认服务路由规则。
默认情况下,Zuul会自动为Eureka服务注册中心的所有服务创建映射关系进行路由,但是这会导致我们不希望外界能访问到的接口被访问到,因此我们可以在配置中关闭默认服务路由规则

zuul:
	ignored-services:'*'

此时需要做到转发,就必须用到serviceId和path组合的配置方式或者简洁方式

zuul :
  Routes :
	eureka- consumer :
		path: /eureka-consumer/**
		serviceId: eureka-consumer
zuul:
  rotes:
   # <serviceId> = <path>
	eureka-consumer:/eureka-consumer/**

3.3 自定义路由映射规则

系统在迭代过程中,需要为一组相互配合的微服务定义一个版本标识来管理版本之间的关系,
比如userprovider-v1 userprovider-v2 默认情况下,Zuul自动为服务创建的路由表达式采用服务名作为前缀,比如前面两个版本产生/userprovider-v1和/userprovider-v2两个路由表达式来映射 ,不利于通过路由规则来管理 。通常的做法是为这些不同版本的微服务应用生成以版本号为路由前缀定义的路由规则,比如/v1/userprovider.这时候,通过这样具有版本号前缀的URL路径,我们就可以很容易地通过路由表达式来归类和管理这些具有版本信息的微服务了。

针对上面所述的需求,如果我们的各个微服务应用都遵循了类似userprovider-v1这样的命名规则,通过“-”分隔的规范来定义服务名和服务版本标识的话,那么,我们可以使用Zuul中自定义服务与路由映射关系的功能,实现符合上述规则的微服务自动化地创建类似/v1/userprovider/ ** 的路由匹配规则。实现步骤非常简单,引用官方文档,只需在网关项目中,增加如下Bean实例即可:

@Bean
public PatternServiceRouteMapper serviceRouteMapper () {
	return new PatternServiceRouteMapper{
	"(?<name>^. )-( ?<version>v. $) ",
	"${version}/${name}") ;
}

开发者可以借助PatternServiceRouteMapper实例中的正则表达式自定义服务与路由
映射的生成关系。其中,构造函数中的第一个参数“(?< name>. )- (?< version>v. $ )”
是用来匹配服务名称是否符合该自定义规则的正则表达式,如若服务名称符合该自定义规则,
就会按照第二个参数“$ {version}/$ {name}”的格式转换成对应的路由表达式规则。例如,
userprovider- v1会转换成/v1/userprovider格式。

3.4 路径匹配

在Zuul中,路由表达式采用了Ant风格定义。

通配符 说明 举例
匹配任意单个字符 /eureka-consumer/?
* 匹配任意数量字符 /eureka-consumer/*
** 匹配任意数量字符,支持多级目录 /eureka-consumer/**

随着版本迭代,可能需要对eureka-consumer服务做一些功能拆分,将原属于eureka-consumer服务的某些功能拆分到了另外一个全新的服务eureka-consumer-ext,而这些拆分的外部调用URL路径希望能够符合规则/eureka-consumer/ext/**,此时,我们就需要在配置文件中增加一个路由规则,配置信息具体如下:

zuul:
  routes:
    eureka-consumer:
		path: /eureka-consumer/**
		serviceId: eureka-consumer
	eureka-consumer-ext:
	    path: /eure ka-consumer/ext/**
        serviceId: eureka-consumer-ext

上述配置中,调用eureka consumer-ext服务的URL路径能同时被/eureka-consumer/ ** 和/ eureka-consumer/ext/** 两个表达式匹配。此时,匹配结果取决于路由规则的定义顺序。
为了更灵活的使用路由配置规则,Zuul 还提供了-一个忽略表达式参数zuul. ignored patterns,该参数用来设置不被网关进行路由的URL表达式。例如,不希望/hi接口被路由,配置信息可以写成下列方式:

zuul:
  ignored-patterns: /**/hi/**
  routes:
	eureka-consumer :
	 	path: /eureka-consumer/**
		serviceId: eureka-consumer

当配置了忽略表达式参数时,通过网关访问eureka-consumer的/hi接口时,会提示该接口不存在,在控制台可以看到没有匹配路由的输出信息

3.5 路由前缀

Zuul提供了zuul.prefix参数设置路由前缀:

zuul:
  prefix: /api
  routes:
    eureka-consumer:
        pah: /eureka-consumer/**
        strip-prefix: false

上述代码中,第2行代码prefix属性将路由前缀设置为/api,访问eureka-consumer服务的/api/eureka-consumer/1路径,请求将会被转发到eureka-consumer的/api/1。说明设置prefix参数后,Zuul_会把代理前缀从默认路径中移除掉,为避免这种情况,可以使用zuul.stripPrefix=false来关闭移除代理前缀的动作,也可以通过zuul. routes.<路由名>.strip-prefix=false来指定服务关闭移除代理前缀的动作,如第6行代码所示。
.

4. Zuul和hysrix结合实现熔断

需要实现FallbackProvider接口,该接口提供两个方法:
1.getRoute() 用于指定提供回退功能的服务
2.fallbackResponse() 用于执行回退操作的具体逻辑
案例:
在gateway-zuul中创建网关处理类fallback.MyFallbackProvider ,用于处理回退逻辑

package com.li.gatewayzuul.fallback;

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;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

@Component   //标注为bean类
public class MyFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        return "eureka-provider";
        //return "*"; //所有服务
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("连接异常,我是fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                MediaType mediaType = new MediaType("application","json",
                                                     Charset.forName("UTF-8"));
                httpHeaders.setContentType(mediaType);
                return httpHeaders;
            }
        };
    }
}
学新通

.

5.Zuul的Eager Load配置

Spring Cloud Zuul的路由转发也是通过Ribbon实现负载均衡的,默认情况下,客户端相关的 Bean会延迟加载,在第一次调用集群服务时,才会初始化这些对象。所以Zuul无法在第一时间加载到Ribbon的负载均衡,如果想提前加载Ribbon客户端,可以在配置文件中进行以下配置:

zuul:
  ribbon:
    eager-load:
    enabled: true

但是,你尝试一下之后会发现并没有起效,这是由于Spring Cloud Zuul_的Eager Load(饥饿加载)没有设计专门的参数来配置,而是直接采用了读取路由配置来进行Eager Load的做法。所以,如果我们使用默认路由,而没有通过配置的方式指定具体路由规则,那么zuul.ribbon.eager-load.enabled=true 的配置就没有什么作用了。
因此,在真正使用的时候,我们可以通过zuul.ignored-services=* ,来忽略所有的默认路由,让所有路由配置均在配置文件中维护,以达到网关zuul启动时就默认初始化好了各个路由所要转发的负载均衡对象。
.

6.Zuul过滤器

Spring Cloud Zuul作为网关组件将客户端请求路由到业务处理过中,大部分功能都是通过过滤器实现的。Zuul定义了四种标准的过滤器类型,具体介绍如下:

  • pre: 该过滤器会在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • route: 负责请求转发到服务。原始请求在此构建,并使用Apache HttpClient.或Netflix Ribbon发送原始请求。
  • post: 在route和error过滤器之后被调用,可以在响应消息中添加标准HTTPHeader、收集统计信息和指标,以及将响应发送给客户端等。
  • error: 处理请求发送错误时被调用。
  • 自定义过滤器

6.1 Zuul的生命周期

学新通

6.2 自定义过滤器

编写Zuul过滤器非常简单,只需要继承ZuulFilter,并实现 ZuulFilter中的四个抽象方法,包括filterlype()、filter0rder()、shouldFilter()和run()。具体步骤如下:

  1. 在gateway-zuul项目中定义表示zuul过滤器的PreRequestLogFilter类
package com.li.gatewayzuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import io.micrometer.core.instrument.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

@Component
public class PreRequestFilter extends ZuulFilter {

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

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run(){
        //获取当前请求的上下文对象
        RequestContext cxt = RequestContext.getCurrentContext() ;//获取当前请求的对象
        HttpServletRequest request = cxt.getRequest();
        logger.info("进入访问过滤器,访问的url:{}访问的方法:{}",request.getRequestURL(),request.getMethod());
        String accessToken = request.getHeader( "accessToken") ;
        if (StringUtils.isEmpty(accessToken)) {
            logger.info("当前的请求需要传递accessToken");
            cxt.setSendZuulResponse(false);
            cxt.setResponseStatusCode(401);
            return null;
        }
        logger.info("请求通过过滤器");
        return null;
    }

}
学新通

6.3 测试

依次启动eureka-server , eureka-consumer ,gatewa-zuul
访问http://localhost:8835/eureka-consumer/hi
学新通

6.4 禁用Zuul过滤器

springCloud默认为Zuul编写并启动一些过滤器,在一些情况下,需要禁用部分过滤器。只需要设置
zuul.< SimpleClassName>.< filterName>.disable=true 即可禁用SimpleClassName所对应的过滤器。一过滤器org.springframework.cloud.netflix. zuul. filters. post. SentResponseEilter为例,只需要设置zuul.SendResponseEilter. pre.disable=true 就可以了。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhiaihig
系列文章
更多 icon
同类精品
更多 icon
继续加载