天天看点

springcloud (Finchley版本)简易入门 | 第五课: Zuul路由网关一、Zuul简介二、实战

在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简答的微服务系统如下图:

springcloud (Finchley版本)简易入门 | 第五课: Zuul路由网关一、Zuul简介二、实战

在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。

一、Zuul简介

Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如api/user转发到到user服务,/api/order转发到到order服务。zuul默认和Ribbon结合实现了负载均衡的功能。

  • SpringCloud Zuul通过与SpringCloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。外层调用都必须通过API网关,使得将维护服务实例的工作交给了服务治理框架自动完成。
  • 在API网关服务上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。

Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。也就是说:Zuul也是支持Hystrix和Ribbon。

zuul有以下功能:

  • Authentication
  • Insights
  • Stress Testing
  • Canary Testing
  • Dynamic Routing
  • Service Migration
  • Load Shedding
  • Security
  • Static Response handling
  • Active/Active traffic management

1.1、可能对Zuul的疑问

Zuul支持Ribbon和Hystrix,也能够实现客户端的负载均衡。我们的Feign不也是实现客户端的负载均衡和Hystrix的吗?既然Zuul已经能够实现了,那我们的Feign还有必要吗?

或者可以这样理解:

  • zuul是对外暴露的唯一接口相当于路由的是controller的请求,而Ribbonhe和Fegin路由了service的请求
  • zuul做最外层请求的负载均衡 ,而Ribbon和Fegin做的是系统内部各个微服务的service的调用的负载均衡

有了Zuul,还需要Nginx吗?他俩可以一起使用吗?

  • 我的理解:Zuul和Nginx是可以一起使用的(毕竟我们的Zuul也是可以搭成集群来实现高可用的),要不要一起使用得看架构的复杂度了(业务)~~~

二、实战

2.1、创建zuul工程

其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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-test</artifactId>
        <groupId>com.dukun.study</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>zuul</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

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

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


</project>
           

在其入口applicaton类加上注解@EnableZuulProxy,开启zuul的功能:

package com.dukun.study.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

/**
 * zuul网关
 *
 * @Author: dukun0210
 * @Date: 2021/1/12 14:17
 */
@SpringBootApplication
@EnableZuulProxy
public class AppZuul {

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

yml文件:

server:
  port: 9000
eureka:
  client:
    serviceUrl:
        defaultZone: http://localhost:3000/eureka/  #eureka服务端提供的注册地址 参考服务端配置的这个路径
  instance:
    instance-id: zuul-0 #此实例注册到eureka服务端的唯一的实例ID
    prefer-ip-address: true #是否显示IP地址
    leaseRenewalIntervalInSeconds: 20 #eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
    leaseExpirationDurationInSeconds: 60 #Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒

spring:
  application:
    name: zuul #此实例注册到eureka服务端的name

zuul:
  prefix: /api
  ignored-services: "*"
#  stripPrefix: false
  routes:
    order:
      serviceId: server-order
      path: /order/**
    user:
      serviceId: client-user
      path: /user/**
    power:
      serviceId: server-power
      path: /power/**
           
注意/ **代表是所有层级 / * 是代表一层。 如果是/ * 的话 /power/admin/getUser.do 就不会被路由 。

这样 简单的zuul就搭建好了, 启动项目 我们就可以通过zuul然后加上对应的访问微服务:

以/api/order/** 开头的请求都转发给server-order服务;以/api/user/**开头的请求都转发给client-user服务;

http://localhost:9000/api/user/getOrderAndPowerrFeign.do?name=8848 就可以访问到 user服务中的 /getOrderAndPowerrFeign.do 方法。

2.2、相关配置

2.2.1、统一前缀

这个很简单,就是我们可以在前面加一个统一的前缀,这个时候我们在

yaml

配置文件中添加如下。

zuul:

  prefix: /zuul

2.2.2、路由策略配置

你会发现前面的访问方式(直接使用服务名),需要将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略。

zuul:
  prefix: /api
  routes:
    order:
      serviceId: server-order
      path: /order/**
           

2.2.3、服务名屏蔽

这个时候你别以为你好了,你可以试试,在你配置完路由策略之后使用微服务名称还是可以访问的,这个时候你需要将服务名屏蔽。

zuul:

  ignore-services: "*"

2.2.4、路径屏蔽

Zuul

还可以指定屏蔽掉的路径 URI,即只要用户请求中包含指定的 URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。

zuul:

  ignore-patterns: **/auto/**

2.3、Zuul 的过滤功能

如果说,路由功能是

Zuul

的基操的话,那么过滤器就是

Zuul

的利器了。毕竟所有请求都经过网关(Zuul),那么我们可以进行各种过滤,这样我们就能实现限流,灰度发布,权限控制等等。

先解释一下关于过滤器的一些注意点:

springcloud (Finchley版本)简易入门 | 第五课: Zuul路由网关一、Zuul简介二、实战

zuul中定义了4种标准过滤器类型,这 些过滤器类型对应于请求的典型生命周期。

过滤器类型:

  1. PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份 验证、在 集群中选择请求的微服务、记录调试信息等。
  2. ROUTING:这种过滤器将请求路由到微服务。这种过滤器 用于构建发送给微服 务的请求,并使用 Apache HttpCIient或 Netfilx Ribbon请求微服务
  3. POST:这种过滤器在路由 到微服务以后执行。这种过滤器可用来为响应添加标准 的 HTTP Header、收集统计信息和指标、将响应从微服务 发送给客户端等。
  4. ERROR:在其他阶段发生错误时执行该过滤器。

2.3.1、简单实现一个请求访问记录打印

/**
 * ZUUL 网关的过滤器
 * 添加日志功能
 *
 * @Author: dukun0210
 * @Date: 2021/1/13 10:24
 */
@Component
public class LogFilter extends ZuulFilter {
    //Pre、Routing、Post。
    // 前置Pre就是在请求之前进行过滤,
    // Routing路由过滤器就是我们上面所讲的路由策略,
    // 而Post后置过滤器就是在Response之前进行过滤的过滤器。
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }

    // 指定过滤顺序 越小越先执行,这里第一个执行
    // 当然不是只真正第一个 在Zuul内置中有其他过滤器会先执行
    // 那是写死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER;
    }

    // 什么时候该进行过滤
    // 这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等
    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 如果过滤器允许通过则怎么进行处理
    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String remoteAddr = request.getRemoteAddr();
        System.out.println("访问者IP:"+remoteAddr+"访问地址:"+request.getRequestURI());
        return null;
    }
}
           

由代码可知,自定义的 zuul Filter需实现以下几个方法。

filterType:返回过滤器的类型。有 pre、 route、 post、 error等几种取值,分别对应上文的几种过滤器。 详细可以参考 com.netflix.zuul.ZuulFilter.filterType()中的注释。

filter0rder:返回一个 int值来指定过滤器的执行顺序,不同的过滤器允许返回相同的数字。

shouldFilter:返回一个 boolean值来判断该过滤器是否要执行, true表示执行, false表示不执行。

run:过滤器的具体逻辑。 禁用zuul过滤器 Spring Cloud默认为Zuul编写并启用了一些过滤器,例如DebugFilter、 FormBodyWrapperFilter 等,这些过滤器都存放在spring-cloud-netflix-core这个jar包 里,一些场景下,想要禁用掉部分过滤器,该怎么办 呢? 只需在application.yml里设置zuul...disable=true 例如,要禁用上面我们写的过滤器,这样配置就行了: zuul.LogFilter.pre.disable=true

2.3.2、令牌桶限流

当然不仅仅是令牌桶限流方式,

Zuul

只要是限流的活它都能干,这里我只是简单举个例子。

springcloud (Finchley版本)简易入门 | 第五课: Zuul路由网关一、Zuul简介二、实战

我先来解释一下什么是令牌桶限流吧。

首先我们会有个桶,如果里面没有满那么就会以一定固定的速率会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。很简单吧,啊哈哈、

下面我们就通过

Zuul

的前置过滤器来实现一下令牌桶限流。

@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
    // 定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return -5;
    }

    @Override
    public Object run() throws ZuulException {
        log.info("放行");
        return null;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        if(!RATE_LIMITER.tryAcquire()) {
            log.warn("访问量超载");
            // 指定当前请求未通过过滤
            context.setSendZuulResponse(false);
            // 向客户端返回响应码429,请求数量过多
            context.setResponseStatusCode(429);
            return false;
        }
        return true;
    }
}
           

这样我们就能将请求数量控制在一秒两个,有没有觉得很酷?

2.3.3、zuul容错与回退

zuul默认是整合了hystrix和ribbon的, 提供降级回退,那如何来使用hystrix呢?

我们自行写一个类,继承FallbackProvider 类 然后重写里面的方法。

package com.dukun.study.zuul.provider;

import com.netflix.hystrix.exception.HystrixTimeoutException;
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;

/**
 * zuul网关的 容错
 *
 * @Author: dukun0210
 * @Date: 2021/1/13 11:10
 */
@Component
public class FallBackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        //制定为哪个微服务提供回退(这里写微服务名 写*代表所有微服务)
        return "*";
    }

    //此方法需要返回一个ClientHttpResponse对象 ClientHttpResponse是一个接口,具体的回退逻辑要实 现此接口
    //route:出错的微服务名 cause:出错的异常对象
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {

        //这里可以判断根据不同的异常来做不同的处理, 也可以不判断
        //完了之后调用response方法并根据异常类型传入HttpStatus
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ClientHttpResponse response(final HttpStatus status) {
        //这里返回一个ClientHttpResponse对象 并实现其中的方法,关于回退逻辑的详细,便在下面的方法中
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                返回一个HttpStatus对象 这个对象是个枚举对象, 里面包含了一个status code 和
                //reasonPhrase信息
                return status;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                //返回status的code 比如 404,500等
                return status.value();
            }

            @Override
            public String getStatusText() throws IOException {
                //返回一个HttpStatus对象的reasonPhrase信息
                return status.getReasonPhrase();
            }

            @Override
            public void close() {
                //close的时候调用的方法, 讲白了就是当降级信息全部响应完了之后调用的方法
            }

            @Override
            public InputStream getBody() throws IOException {
                //吧降级信息响应回前端
                return new ByteArrayInputStream("系统繁忙,稍后再试".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                //需要对响应报头设置的话可以在此设置
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }


}
           

关于 Zuul  的其他

Zuul

的过滤器的功能肯定不止上面我所实现的两种,它还可以实现权限校验,包括我上面提到的灰度发布等等。

当然,

Zuul

作为网关肯定也存在单点问题,如果我们要保证

Zuul

的高可用,我们就需要进行

Zuul

的集群配置,这个时候可以借助额外的一些负载均衡器比如

Nginx

继续阅读