栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Zuul服务网关✧

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Zuul服务网关✧

Zuul服务网关☣

目录
      • Zuul服务网关☣
        • 一、什么是网关?
        • 二、Zuul实现API网关
          • ① 添加子模块zuul-server
          • ② 配置路由
          • ③ 服务名称路由
          • ④ 简化路由
        • 三、网关过滤器
          • ① 入门Demo
          • ② 统一鉴权
          • ③ 网关过滤器异常统一处理
          • ④ Zuul请求的声明周期
        • 四、Zuul 和 Hystrix 无缝结合
          • ① 准备环境
          • ② 网关熔断
          • ③ 网关限流


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


一、什么是网关?

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

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

二、Zuul实现API网关

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



① 添加子模块zuul-server

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




    4.0.0

    com.yjxxt
    zuul-server
    1.0-SNAPSHOT

    
    
        com.yjxxt
        zuul-demo
        1.0-SNAPSHOT
    

    
    
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-zuul
        
    


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 依赖

    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client

  • 配置注册中心和路由规则
# 路由规则
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 默认对应 /微服务名称 @Component public class CustomFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(CustomFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @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 依赖。

① 准备环境
# 引入依赖


    org.springframework.cloud
    spring-cloud-starter-netflix-hystrix-dashboard

# 配置
# 度量指标监控与健康检查
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 {

    
    @Override
    public String getRoute() {
        return "product-service";
    }

    
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                header.setContentType(new MediaType("application", "json", Charset.forName("utf-8")));
                return header;
            }

            
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("{"message":"商品服务不可用,请稍后再试。"}".getBytes());
            }

            
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }

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

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

            
            @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


    com.marcosbarbero.cloud
    spring-cloud-zuul-ratelimit
    2.3.0.RELEASE



    org.springframework.boot
    spring-boot-starter-data-redis



    org.apache.commons
    commons-pool2


全局限流配置
使用全局限流配置,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 整合等,内容很多这里就先到这。


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/657484.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号