Zuul服务网关✧

Zuul服务网关☣



前提知识点总结(不属于正文)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


一、什么是网关?

API Gateway(APIGW / API 网关),顾名思义,是出现在系统边界上的一个面向 API 的、串行集中式的强管控服务,这里的边界是企业 IT 系统的边界,可以理解为企业级应用防火墙,主要起到隔离外部访问与内部系统的作用。在微服务概念的流行之前,API 网关就已经诞生了,例如银行、证券等领域常见的前置机系统,它也是解决访问认证、报文转换、访问统计等问题的。(网关是用户访问系统的第一关,网关里记录者注册中心中的所有的服务地址,通过网关进行认证,请求分发)

在这里插入图片描述

  • Nginx 适合做门户网关,是作为整个全局的网关,对外的处于最外层的那种;而 Zuul 属于业务网关,主要用来对应不同的客户端提供服务,用于聚合业务。各个微服务独立部署,职责单一,对外提供服务的时候需要有一个东西把业务聚合起来。
  • Zuul 可以实现熔断、重试等功能,这是 Nginx 不具备的。

二、Zuul实现API网关

官网文档:
准备一个聚合项目,一个父项目内两个注册中心,一个消费者,一个服务提供者,这里不惜介绍前面文章有提,启动项目测试看一下服务是否注册到注册中心:


在这里插入图片描述


① 添加子模块zuul-server

创建一个java项目,引入依赖和配置即可

<?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>

    <groupId>com.yjxxt</groupId>
    <artifactId>zuul-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 继承父依赖 -->
    <parent>
        <groupId>com.yjxxt</groupId>
        <artifactId>zuul-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- 项目依赖 -->
    <dependencies>
        <!-- spring cloud netflix zuul 依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

</project>

application.yml配置

server:
  port: 9000 # 端口

spring:
  application:
    name: zuul-server # 应用名称

启动类

@SpringBootApplication
// 开启 Zuul 注解
@EnableZuulProxy
public class ZuulServerApplication {

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

这里zuul模块建立完毕

② 配置路由

配置路由后平时的服务请求假设:http://localhost:7070/order/10,配置路由后:http://localhost:7070/product-service/order/10,这样指定某服务下的接口

application.yml配置

# 路由规则
zuul:
  routes:
    product-service:              # 路由 id 自定义
      path: /product-service/**   # 配置请求 url 的映射路径
      url: http://localhost:7070/ # 映射路径对应的微服务地址

在这里插入图片描述

启动测试:
在这里插入图片描述

在这里插入图片描述

弊端就是每次更换服务都需要手动修改


③ 服务名称路由
  • 添加 Eureka Client 依赖
<!-- netflix eureka client 依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 配置注册中心和路由规则
# 路由规则
zuul:
  routes:
    product-service:              # 路由 id 自定义
      path: /product-service/**   # 配置请求 url 的映射路径
      serviceId: product-service  # 根据 serviceId 自动从注册中心获取服务地址并转发请求

# 配置 Eureka Server 注册中心
eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # 设置服务注册中心地址
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
  • 启动类
package com.yjxxt;

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

@SpringBootApplication
// 开启 Zuul 注解
@EnableZuulProxy
// 开启 EurekaClient 注解,目前版本如果配置了 Eureka 注册中心,默认会开启该注解
//@EnableEurekaClient
public class ZuulServerApplication {

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

}

在这里插入图片描述


④ 简化路由

Zuul 为了方便大家使用,提供了默认路由配置:路由 id 和 微服务名称 一致,path 默认对应 /微服务名称/**,所以以下配置就没必要再写了。

# 路由规则
zuul:
  routes:
    product-service:              # 路由 id 自定义
      path: /product-service/**   # 配置请求 url 的映射路径
      serviceId: product-service  # 根据 serviceId 自动从注册中心获取服务地址并转发请求

在这里插入图片描述

还可以路由地址排除和路由名称排除以及路由前缀等,此处不做解析


三、网关过滤器

四个过滤器,pre过滤器主要进行请求的映射与转发服务,还有一部分有routing过滤器进行转发,post过滤器则在routing 和 error 过滤器之后被调用是对结果的返回,error过滤器只有在出现错误时才会触发
在这里插入图片描述

① 入门Demo

创建类Filter/CustomFilter .java过滤器
Spring Cloud Netflix Zuul 中实现过滤器必须包含 4 个基本特征:过滤器类型,执行顺序,执行条件,动作(具体操作)。这些步骤都是 ZuulFilter 接口中定义的 4 个抽象方法:

/**
 * 网关过滤器
 */
@Component
public class CustomFilter extends ZuulFilter {

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

    /**
     * 过滤器类型
     *      pre
     *      routing
     *      post
     *      error
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 执行顺序
     *      数值越小,优先级越高
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 执行条件
     *      true 开启
     *      false 关闭
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 动作(具体操作)
     *      具体逻辑
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        // 获取请求上下文
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        logger.warn("CustomFilter...method={}, url={}",
                request.getMethod(),
                request.getRequestURL().toString());
        return null;
    }

}

测试http://localhost:9000/product-service/product/521

在这里插入图片描述

请求日志的打印


② 统一鉴权

类似于认证,即token,否则无权限访问服务

修改过滤器:

 /*通过过滤器进行认证*/
    @Override
    public Object run() throws ZuulException {
        // 获取请求上下文
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        // 获取表单中的 token
        String token = request.getParameter("token");
        // 业务逻辑处理
        if (null == token) {
            logger.warn("token is null...");
            // 请求结束,不在继续向下请求。
            rc.setSendZuulResponse(false);
            // 响应状态码,HTTP 401 错误代表用户没有访问权限
            rc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            // 响应类型
            rc.getResponse().setContentType("application/json; charset=utf-8");
            PrintWriter writer = null;
            try {
                writer = rc.getResponse().getWriter();
                // 响应内容
                writer.print("{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != writer)
                    writer.close();
            }
        } else {
            // 使用 token 进行身份验证
            logger.info("token is OK!");
        }
        return null;
    }

测试http://localhost:9000/product-service/product/521
在这里插入图片描述

在这里插入图片描述


③ 网关过滤器异常统一处理

修改过滤器:

/*通过过滤器统一管理异常*/
    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        Throwable throwable = rc.getThrowable();
        logger.error("ErrorFilter..." + throwable.getCause().getMessage(), throwable);
        // 响应状态码,HTTP 500 服务器错误
        rc.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        // 响应类型
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // 响应内容
            writer.print("{\"message\":\"" + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

制造一个异常,添加一个类AccessFilter.java(把认证过滤器提出一个类出来)

@Component
public class AccessFilter extends ZuulFilter {

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

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

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

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

    @Override
    public Object run() throws ZuulException {
        // 模拟异常
         Integer.parseInt("zuul");

        // 获取请求上下文
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        // 获取表单中的 token
        String token = request.getParameter("token");
        // 业务逻辑处理
        if (null == token) {
            logger.warn("token is null...");
            // 请求结束,不在继续向下请求。
            rc.setSendZuulResponse(false);
            // 响应状态码,HTTP 401 错误代表用户没有访问权限
            rc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            // 响应类型
            rc.getResponse().setContentType("application/json; charset=utf-8");
            PrintWriter writer = null;
            try {
                writer = rc.getResponse().getWriter();
                // 响应内容
                writer.print("{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != writer)
                    writer.close();
            }
        } else {
            // 使用 token 进行身份验证
            logger.info("token is OK!");
        }
        return null;
    }
}

添加异常处理过滤器

/**
 * 异常过滤器
 */
@Component
public class ErrorFilter extends ZuulFilter {

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

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

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

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


    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        ZuulException exception = this.findZuulException(rc.getThrowable());
        logger.error("ErrorFilter..." + exception.errorCause, exception);

        HttpStatus httpStatus = null;
        if (429 == exception.nStatusCode)
            httpStatus = HttpStatus.TOO_MANY_REQUESTS;

        if (500 == exception.nStatusCode)
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

        // 响应状态码
        rc.setResponseStatusCode(httpStatus.value());
        // 响应类型
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // 响应内容
            writer.print("{\"message\":\"" + httpStatus.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

    private ZuulException findZuulException(Throwable throwable) {
        if (throwable.getCause() instanceof ZuulRuntimeException)
            return (ZuulException) throwable.getCause().getCause();

        if (throwable.getCause() instanceof ZuulException)
            return (ZuulException) throwable.getCause();

        if (throwable instanceof ZuulException)
            return (ZuulException) throwable;
        return new ZuulException(throwable, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
    }

}

关闭自带的异常处理

zuul:
  # 禁用 Zuul 默认的异常处理 filter
	SendErrorFilter:
     error:
       disable: true

结果测试:
在这里插入图片描述


④ Zuul请求的声明周期
  1. HTTP 发送请求到 Zuul 网关
  2. Zuul 网关首先经过 pre filter
  3. 验证通过后进入 routing filter,接着将请求转发给远程服务,远程服务执行完返回结果,如果出错,则执行 error filter
  4. 继续往下执行 post filter
  5. 最后返回响应给 HTTP 客户端

在这里插入图片描述


四、Zuul 和 Hystrix 无缝结合

想要实现网络监控,就要配置hystrix,Zuul 的依赖中包含了 Hystrix 的相关 jar 包,所以我们不需要在项目中额外添加 Hystrix 的依赖。但是需要开启数据监控的项目中要添加 dashboard 依赖。

① 准备环境
# 引入依赖
<!-- spring cloud netflix hystrix dashboard 依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
# 配置
# 度量指标监控与健康检查
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

启动类// 开启 Zuul 注解@EnableZuulProxy

启动项目测试访问:http://localhost:9000/hystrix

在这里插入图片描述
监听服务地址http://localhost:9000/actuator/hystrix.stream
在这里插入图片描述


② 网关熔断

在 Edgware 版本之前,Zuul 提供了接口 ZuulFallbackProvider 用于实现 fallback 处理。从 Edgware 版本开始,Zuul 提供了接口 FallbackProvider 来提供 fallback 处理。编写实现类:ProductProviderFallback(熔断只有在服务宕机的时候没有任何反馈才会熔断,所有测试时挂掉服务即可)

/**
 * 对商品服务做服务容错处理
 */
@Component
public class ProductProviderFallback implements FallbackProvider {

    /**
     * return - 返回 fallback 处理哪一个服务。返回的是服务的名称。
     * 推荐 - 为指定的服务定义特性化的 fallback 逻辑。
     * 推荐 - 提供一个处理所有服务的 fallback 逻辑。
     * 好处 - 某个服务发生超时,那么指定的 fallback 逻辑执行。如果有新服务上线,未提供 fallback 逻辑,有一个通用的。
     */
    @Override
    public String getRoute() {
        return "product-service";
    }

    /**
     * 对商品服务做服务容错处理
     *
     * @param route 容错服务名称
     * @param cause 服务异常信息
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            /**
             * 设置响应的头信息
             * @return
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                header.setContentType(new MediaType("application", "json", Charset.forName("utf-8")));
                return header;
            }

            /**
             * 设置响应体
             * Zuul 会将本方法返回的输入流数据读取,并通过 HttpServletResponse 的输出流输出到客户端。
             * @return
             */
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("{\"message\":\"商品服务不可用,请稍后再试。\"}".getBytes());
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 HttpStatus
             * @return
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 int
             * @return
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 String
             * @return
             */
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            /**
             * 回收资源方法
             * 用于回收当前 fallback 逻辑开启的资源对象。
             */
            @Override
            public void close() {
            }
        };
    }

}

一定是服务宕机后刷新才会出现:
在这里插入图片描述


③ 网关限流

就是在指定的时间内规定多少请求,一旦超标,则不接受请求,多于请求直接返回,限流算法:三种

  • 计数器算法
  • 漏桶(Leaky Bucket)算法
  • 令牌桶(Token Bucket)算法

计数器

设置一个计数器,一分钟比如允许100个请求,所以计数器一分钟之内没满一百则重新计数。缺点就是恶意在59s100个请求,1分钟又100个导致压垮服务,这是一个漏洞,而且容易闲置

在这里插入图片描述


漏桶算法

就是用一个水桶下面一个洞,水桶去存放大量请求,再通过小洞一个一个处理请求,一旦请求大于水桶,则直接fallback,类似消息队列

在这里插入图片描述


令牌桶算法

一个桶里存放令牌,请求会去拿令牌,令牌会一直生成,多余令牌直接舍弃,请求拿到令牌才能去访问资源,拿到令牌的请求会被执行,否则fallback或者缓存

在这里插入图片描述


代码实现:

// Zuul 的限流保护需要额外依赖 spring-cloud-zuul-ratelimit 组件,限流数据采用 Redis 存储所以还要添加 Redis 组件。
//[RateLimit 官网文档]:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
<!-- spring cloud zuul ratelimit 依赖 -->
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.3.0.RELEASE</version>
</dependency>
<!-- spring boot data redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

全局限流配置
使用全局限流配置,Zuul 会对代理的所有服务提供限流保护(记得开启redis缓存数据库)。

server:
  port: 9000 # 端口

spring:
  application:
    name: zuul-server # 应用名称
  # redis 缓存
  redis:
    host: 192.168.10.106  # Redis服务器地址

# 配置 Eureka Server 注册中心
eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # 设置服务注册中心地址
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

zuul:
  # 服务限流
  ratelimit:
    # 开启限流保护
    enabled: true
    # 限流数据存储方式
    repository: REDIS
    # default-policy-list 默认配置,全局生效
    default-policy-list:
      - limit: 3
        refresh-interval: 60    # 60s 内请求超过 3 次,服务端就抛出异常,60s 后可以恢复正常请求
        type:
          - origin
          - url
          - user

启动测试
在这里插入图片描述

请求数超过我们设定的阈值,所以返回请求太多,后面还有局部限流,自定义限流,网关调优,Zuul 和 Sentinel 整合等,内容很多这里就先到这。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每日小新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值