网关服务---Zuul

Zuul的简介

什么是Zuul?

概述: ZuulNetflix的一个开源组件,它是通过Servlet实现的

作用:通过把网关和服务治理整合到一起,Spring Cloud Zuul可以获取到服务注册信息,结合RibbonHystrix更好地实现路由转发、负载均衡等功能。

为什么用Hystrix?

        我们可以搭建简单的微服务架构系统实现各服务之间的调用,但是不同的微服务一般会有不同的网络地址,而外部客户端(例如手机APP)可能需要调用多个服务的接口才能完成一个业务需求。而客户端直接与各个微服务通信,这样会有许多问题出现

客户端直接与微服务通信

例如,一个电商的手机APP,可能会调用多个微服务的接口,才能完成一次购票的业务流程。如图所示:

客户端与微服务直接通信,就会产生许多问题,具体如下:

1.客户端会多次请求不同的微服务,使客户端变得更为复杂

2. 存在跨域请求,在一定场景下处理相对复杂。例如,在重定向或js发起的   ajax请求时,会因为域名不同、二级域名不同、子域名不同或端口号不同等因素,处理变得相对复杂。

3. 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个,这种情况下,如果是客户端与微服务直接通信的架构,那么用户访问每一个接口的请求路径都需要改变,这就使得重构变得难以实现。

4. 某些微服务可能会设置防火墙等不友好的协议,难以做到直接访问。

客户通过Zuul网关与微服务通信

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

        服务网关除了要实现请求路由负载均衡过滤等功能之外,还要实现更多功能,例如与服务相关框架整合、服务请求的熔断等。图描述了加入服务网关的微服务调度过程。

快速搭建Zuul工程

简单搭建Zuul工程

1.创建eureka-server项目(之前创建过,可看我之前写的)

Hystrix——服务容错保护库-CSDN博客

2.创建服务提供者eureka-provider项目(之前创建过,可看我之前写的,同上)
3.创建服务消费者eureka-consumer项目

依赖(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.xiaofeng</groupId>
    <artifactId>eureka-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-consumer</name>
    <description>Demo project for Spring Boot</description>
    <url/>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.7.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.7.RELEASE</version>
        </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>

 application.yml

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

 开启Eureka Client功能:在启动类@EnableEurekaClient

创建config,使 RestTemplate实例对象处理请求时拥有客户端负载均衡的能力

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

创建一个service包,并添加@Service注解,service类中注入RestTemplate实例对象,并添加一个hi()方法,在hi()方法中对eureka-provider提供者服务进行调用 

@Service
public class ConsumerService {
    @Autowired
    private RestTemplate restTemplate;

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

创建一个controller包,并添加@RestController注解,注入我们写的service对象,并创建一个hi()方法

@RestController
public class ConsumerController {
    @Autowired
    private ConsumerService consumerService;

    @GetMapping("/hi")
    public String hi(String id){
        return consumerService.hi(id);
    }
}
4.创建网关服务gateway-zuul项目

依赖:

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

application.yml:

server:
  port: 8835
spring:
  application:
    name: gateway-zuul
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka/
  instance:
    hostname: localhost
zuul:
  routes:
    eureka-consumer:
      path: /eureka-consumer/**

在启动类,添加@EnableZuulProxy注解开启服务网关Zuul功能 

5.项目测试

查看服务注册是否成功:

不用网关进行访问:

用Zuul网关进行访问:

 Zuul的路由映射规则配置

1.服务路由配置

        服务路由是Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,我们在使用服务路由配置的时候,无需通过serviceId指定具体服务实例地址,只需要通过zuul.routes.<路由名>.pathzuul.routes.<路由名>. serviceId的方式成对配置即可

代码的作用是将符合/eureka-consumer/**规则的请求路径转发到名为eureka-consumer的服务实例上 

        对于面向服务的路由配置,除了使用pathserviceId映射的配置方式之外,还有一种更简洁的配置方式,即zuul.routes.<serviceId>=<path>,其中<serviceId>用来指定路由的具体服务名<path>用来配置匹配的请求映射地址

2.服务路由默认规则

        默认情况下,Zuul会自动为Eureka服务注册中心的所有服务创建映射关系进行路由,这会使得一些我们不希望对外开放的服务也可能被外部访问到。当使用服务名称作为前缀路径时,实际上会匹配类似下面的默认路由配置

        使用关闭默认的路由配置后,此时我们需要在配置文件中逐个为需要路由的服务添加映射规则。当然可以使用pathserviceId 组合的配置方式,也可以使用更简洁的zuul.routes.<serviceId>=<path>配置方式,只有在配置文件中出现的映射规则会被创建路由,而从Eureka中获取的其他服务,Zuul将不会再为它们创建路由规则。 

3.自定义路由映射规则

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

4.路径匹配

        在使用服务路由的配置方式时,我们需要为每个路由规则定义路由表达式,也就是path参数。在Zuul中,路由表达式采用了Ant风格定义。Ant风格的路由表达式共有三种通配符

        为了更灵活的使用路由配置规则,Zuul还提供了一个忽略表达式参数zuul.ignored-patterns,该参数用来设置不被网关进行路由的URL表达式。例如,不希望/hi接口被路由,配置信息如下。

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

5.路由前缀

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

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

Zuul和Hystrix结合实现熔断

实现网关处理类

        Zuul和Hystrix结合使用实现熔断功能时,需要实现FallbackProvider接口在网关项目gateway-zuul中创建网关处理类MyFallbackProvider类,用于处理回退逻辑并实现 FallbackProvider的两个抽象方法,包括getRoute () fallbackResponse () 

ZuulHystrix结合使用实现熔断功能时,实现FallbackProvider接口,实现getRoute()方法

@Component
public class MyFallbackProvider implements FallbackProvider {
    /*
    * 指定当前服务哪些是需要回退的
    * */
    @Override
    public String getRoute() {
        return "eureka-consumer";
    }

实现fallbackResponse()方法,重写getStatusCode()getRawStatusCode() getStatusText()方法

/*
    * 执行回退操作的具体逻辑
    * */
    @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 mt = new MediaType("application","json", Charset.forName("UTF-8"));
                httpHeaders.setContentType(mt);
                return httpHeaders;
            }
        };
    }

 测试

自己制造异常,将eureka-provider服务停止,因为写了MyFallbackProvider类,使用会跳转这个错误:

这样就说明Zuul和Hystrix结合实现熔断整合成功!

Zuul中的Eager Load配置

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

        如果我们使用默认路由,而没有通过配置的方式指定具体路由规则,那么zuul.ribbon.eager-load.enabled=true的配置就没有什么作用了。

        因此,在真正使用的时候,我们可以通过zuul.ignored-services=*来忽略所有的默认路由,让所有路由配置均在配置文件中维护,以达到网关zuul启动时就默认初始化好了各个路由所要转发的负载均衡对象。

具体编写如下:

Zuul的过滤器

Zuul的过滤器介绍

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

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

2route:负责请求转发到服务。 原始请求在此构建,并使用ApacheHttpClient或Netflix Ribbon发送原始请求。

3post:routeerror过滤器之后被调用,可以在响应消息中添加标准HTTPHeader、收集统计信息和指标,以及将响应发送给客户端等。

4error:处理请求发送错误时被调用

Zuul请求的生命周期

Zuul请求的生命周期描述了各种类型过滤器的执行顺序

        HTTP请求到达Zuul最先来到pre过滤器,在这里会去映射url patern到目标地址上,然后将请求与找到的地址交给route类型routingfilterType方法返回的类型是“route”,不是routing)的过滤器进行求转发,请求服务实例获取响应,通过post类型过滤器对处理结果进行加工、转换等操作并返回。error类型的过滤器比较特殊,它在整个请求过程中,只在有异常的情况下才会触发,将异常结果交给post类型过滤器加工并返回。

自定义Zuul过滤器

手编写一个Zuul过滤器,来实现打印用户请求的Http方法以及请求地址。编写Zuul过滤器只需要继承ZuulFilter,并实现 ZuulFilter中的四个抽象方法,包括filterType() filterOrder()shouldFilter()run()

gateway-zuul项目中定义表示Zuul过滤器的PreRequestLogFilter,继承ZuulFilter。实现ZuulFilterfilterType抽象方法。

package com.xiaofeng.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

@Component
public class PreRequestLogFilter extends ZuulFilter {
    /*声明日志*/
    private static final Logger logger = LoggerFactory.getLogger(PreRequestLogFilter.class);
    /*当前编写的自定义类型是什么? 有四种:pre、route、post、error*/
    @Override
    public String filterType() {
        return "pre";
    }
    /*当前编写的自定义过滤器的执行顺序是什么*/
    @Override
    public int filterOrder() {
        return 1;
    }
    /*是否执行当前过滤器,默认为false,要改为true*/
    @Override
    public boolean shouldFilter() {
        return true;
    }
    /*写业务逻辑*/
    @Override
    public Object run() throws ZuulException {
        //获取当前请求的上下文对象
        RequestContext currentContext = RequestContext.getCurrentContext();
        //获取当前请求的对象
        HttpServletRequest request = currentContext.getRequest();
        logger.info("进入访问过滤器,访问的url:{},访问的方法:{}",request.getRequestURL(),request.getMethod());
        String accessToken = request.getHeader("accessToken");
        if (StringUtils.isEmpty(accessToken)){
            logger.info("当前的请求需要传递access Token");
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(401);
            return null;
        }
        logger.info("请求通过过滤器");
        return null;
    }
}

启动测试。依次启动eureka-servereureka-consumergateway-zuul。使用浏览器访问http://localhost:8835/eureka-consumer/hi,会在在控制台看到下列信息,如图所示。

在浏览器会返回401:

 在PostMan中需写accessToken和一个value值就可以请求到:

禁用Zuul过滤器

Spring Cloud默认为Zuul编写并启动了一些过滤器,例如DebugFilterFormBodyWrapperilterPreDecorationFilter等,这些过滤器都会存放在spring-  cloud-netflix-core这个Jar包的org.springframework.cloud.netflix.zuul.filters包中。

在一些情况下,我们需要禁用掉部分过滤器,只需设置zuul.<SimpleClassName>.<filterType>.disable=true,就可以禁用SimpleClassName所对应的过滤器。以过滤器org.springframework.cloud.netflix.zuul.filters.post.SentResponseFilter为例,只需要设置zuul.SendResponseFilter.pre.disable=true就可以了。

  • 23
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值