Zuul API网关

Zuul API网关

解决问题: 服务器集群的集成调用, 集成了ribbon, hystrix, actuator等

zuul API 网关,为微服务应用提供统一的对外访问接口。
zuul 还提供过滤器,对所有微服务提供统一的请求校验。

快速开始

前提

例如我们有三中业务服务器, 服务器id名为: item-service, order-service, user-service

并且配置了eureka注册中心

1. 创建项目导入依赖

创建springboot项目, 并导入依赖Zuul, Eureka Discovery Client

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

2. yml配置文件

spring:
  application:
    name: zuul  # 服务器id/名称
    
server:
  port: 3001  # 服务器端口
  
# 配置eureka
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka

# 配置zuul
# 默认规则:
# 如果不配置,zuul会根据注册表的注册信息,完成自动配置
# 最好自己手动配置,避免注册表信息不全,自动配置不完整
zuul:
  routes:
    item-service: /item-service/**
    user-service: /user-service/**
    order-service: /order-service/**

3. 主程序注解

添加 @EnableZuulProxy@EnableDiscoveryClient (高版本springboot可省略此注解) 注解

@EnableZuulProxy
// @EnableDiscoveryClient // springboot高版本可省略
@SpringBootApplication
public class Sp11ZuulApplication {

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

}

4. 访问测试

例如我们item-service有个/getItem请求, 我们可以访问我们的zuul服务器来发送请求, 如下

http://localhost:3001/item-service/getItem // 3001 为zuul的端口, 即访问的zuul服务器

Zuul 请求过滤

请求过滤的本质就是对接口的访问权限进行限制,实现的思路大致有以下几种:

  1. 为每个服务写一套校验签名和鉴别权限的过滤器和拦截器。(代码是真的冗余)。
  2. 将校验权限剥离,构成一个独立的鉴权服务。(换汤不换药,还是代码冗余)。
  3. 在API网关中实现对客户端请求的校验。(请求过滤)。

过滤方式

  • PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR:在其他阶段发生错误时执行该过滤器。
    除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

1. 定义过滤器

过滤器需要继承ZuulFilter 抽象类, 并重写其4个方法, 使用@Component注解注入spring容器

package cn.tedu.sp11.filter;

import cn.tedu.web.util.JsonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class AccessFilter extends ZuulFilter {

    /**
     * 指定过滤器的类型: pre(前置), post(发布), routing(路由), error(错误) <br>
     * 多数情况下用pre
     * @return 可以直接返回字符串, 例如"pre", 或者使用FilterConstants中的常量
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * 顺序号 <br>
     *  在默认过滤器中, 第5个过滤器在上下文对象中添加了 service-id(服务id) <br>
     *  所以在第5个过滤器之后, 才能从上下文对象访问 service-id(服务id) <br>
     *  如果需要用到service-id(服务id), 建议使用5之后的数字
     * @return 6
     */
    @Override
    public int filterOrder() {
        return 6;
    }

    /**
     * 对当前请求, 是否要进行过滤 <br>
     * @return
     *      如果返回true, 要进行过滤, 会执行下面的过滤方法run() <br>
     *      如果返回false, 不需要过滤, 跳过run()方法, 继续执行后面的流程
     */
    @Override
    public boolean shouldFilter() {
        // 判断用户调用的是否是商品服务
        // 如果是商品服务进行过滤, 其他服务不过滤

        // 获取上下文请求对象
        RequestContext ctx = RequestContext.getCurrentContext();

        // 从上下文对象获取客户端调用的service-id
        String serviceId = (String) ctx.get(FilterConstants.SERVICE_ID_KEY);

        // 如果是商品服务, 返回true, 如果不是则返回false
        return "item-service".equals(serviceId);
    }

    /**
     * 过滤代码, 如果有token参数, 则继续执行, 如果为空, 则返回对应内容
     * @return 返回值再当前zuul版本中没有启动, 返回任何数据都无效, 作者考虑了返回值后续可能会有用
     * @throws ZuulException 异常
     */
    @Override
    public Object run() throws ZuulException {
        // http://localhost:3001/item-service/123?token=ds54f6af
		// 获取上下文请求对象
        RequestContext ctx = RequestContext.getCurrentContext();
		// 从上下文对象获request对象
        HttpServletRequest request = ctx.getRequest();
		// 获取请求参数
        String token = request.getParameter("token");

        if (StringUtils.isBlank(token)) { // 判断是否为空串
            // 如果为空, 没有token参数, 阻止这次调用继续执行
            ctx.setSendZuulResponse(false);

            // 在这里, 直接向客户端发送响应
            // JsonResult: {code: 400, msg: not log in, data: null}
            ctx.setResponseStatusCode(JsonResult.NOT_LOGIN);
            ctx.setResponseBody(
                    JsonResult.err()
                    .code(JsonResult.NOT_LOGIN)
                    .msg("not log in")
                    .toString());
        }
        return null;
    }

}

2. 访问测试即可

测试访问: http://localhost:3001/item-service/35

http://localhost:3001/item-service/35?token=1234

一个有token参数, 一个没有

zuul集成ribbon

负载均衡和重试

zuul已经集成了ribbon, 默认已经实现了负载均衡, 但是没有默认开启重试功能(zuul框架不推荐开启重试)

1. zuul + ribbon 重试

需要 spring-retry 依赖 springboot依赖里没有, 从这里复制即可

<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>

2. 配置重试 yml

需要配置zuul.retryable=true来开启重试功能, 可配置重试参数

zuul:
  routes:
    item-service: /item-service/**
    user-service: /user-service/**
    order-service: /order-service/**

  retryable: true # 开启重试功能

# zuul:
#   retryable: true

# 配置重试参数 (可选)
# ribbon:
#   ConnectTimeout: 1000
#   ReadTimeout: 1000
#   MaxAutoRetriesNextServer: 1
#   MaxAutoRetries: 1

zuul集成hystrix

1. 创建降级类

创建降级类, 需要实现FallbackProvider接口, 重写其两个方法, 记得加@Component注解, 注入ioc容器

package cn.tedu.sp11.fallback;

import cn.tedu.web.util.JsonResult;
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;

@Component
public class ItemFallback implements FallbackProvider {

    /**
     * 返回服务id, 即service-id <br>
     * 针对哪个服务应用当前降级类
     * @return 如果返回"*"或null, 表示对所有服务都执行当前降级类 <br>
     *     如果返回"item-service" 只对item-service服务降级
     */
    @Override
    public String getRoute() {
        return "item-service";
    }

    /**
     * 降级执行的代码
     * ClientHttpResponse 是封装降级响应的对象
     */
    @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 HttpStatus.OK.value();
            }

            // 状态文本
            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            // 返回响应体
            @Override
            public InputStream getBody() throws IOException {
                // JsonResult:  {code: 200, msg: "调用后台服务失败", data: null}
                String json = JsonResult.err().msg("调用商品服务失败").toString();
                ByteArrayInputStream in = new ByteArrayInputStream(json.getBytes());
                return in;
            }

            // 返回响应头
            @Override
            public HttpHeaders getHeaders() {
                // Content-Type: application/json
                HttpHeaders h = new HttpHeaders();
                h.setContentType(MediaType.APPLICATION_JSON_UTF8);
                return h;
            }
        };
    }
}

2. hystrix 超时时间

为了方便测试, 这里设置为500, 一般设置比ribbon重试时间长即可

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

3. 测试

自行想办法测试, 相信大家都会

zuul + hystrix 数据监控

暴露 hystrix.stream 监控端点

  • zuul 已经包含 actuator 依赖
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

启动服务,查看暴露端点

所有暴露的监控点: http://localhost:3001/actuator
hystrix监控点: http://localhost:3001/actuator/hystrix.stream

启动仪表盘项目

关于dashboard 断路器仪表盘项目的创建: dashboard 断路器仪表盘

启动仪表盘项目后, 填入上面的hystrix的监控点链接即可

注意: 必须通过zuul网关访问后台服务才会长生监控数据

zuul + turbine 聚合监控

我们可以在turbin的项目中把zuul添加进去即可 (使用逗号隔开)

turbine中配置

turbine:
  app-config: order-service, zuul
  cluster-name-expression: new String("default")

熔断测试

并发工具下载地址: http://httpd.apache.org/docs/current/platform/windows.html#down

熔断测试,ab -n 20000 -c 50 http://localhost:3001/order-service/123abc

zuul Cookie过滤

zuul 会过滤敏感 http 协议头,默认过滤以下协议头:

  • Cookie
  • Set-Cookie
  • Authorization

可以设置 zuul 不过滤这些协议头

zuul:
  sensitive-headers: 
posted @ 2020-09-02 07:57  zpk-aaron  阅读(351)  评论(0编辑  收藏  举报