ZUUL-API网关[通俗易懂]

ZUUL-API网关[通俗易懂]更多干货 分布式实战(干货) springcloud实战(干货) mybatis实战(干货) springboot实战(干货) React入门实战(干货) 构建中小型互联网企业架构(干货) python学习持续更新 ElasticSearch笔记 kafkastorm实战(干货) …

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

更多干货

一、内容概览

本篇将介绍API网关的基本概念、Zuul网关的功能和工作机制。结合代码介绍如何使用Zuul构建一个简单的网关、介绍Zuul的路由配置方式、了解Filter工作原理并实现一些扩展功能。

二、什么是API网关

在微服务架构中,通常会有多个服务提供者。设想一个电商系统,可能会有商品、订单、支付、用户等多个类型的服务,而每个类型的服务数量也会随着整个系统体量的增大也会随之增长和变更。作为UI端,在展示页面时可能需要从多个微服务中聚合数据,而且服务的划分位置结构可能会有所改变。网关就可以对外暴露聚合API,屏蔽内部微服务的微小变动,保持整个系统的稳定性。

当然这只是网关众多功能中的一部分,它还可以做负载均衡,统一鉴权,协议转换,监控监测等一系列功能。

下图是一张典型的Backend for Front-End网关:

ZUUL-API网关[通俗易懂]

三、什么是Zuul

Zuul是Spring Cloud全家桶中的微服务API网关。

所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul底层利用各种filter实现如下功能:

  • 认证和安全 识别每个需要认证的资源,拒绝不符合要求的请求。
  • 性能监测 在服务边界追踪并统计数据,提供精确的生产视图。
  • 动态路由 根据需要将请求动态路由到后端集群。
  • 压力测试 逐渐增加对集群的流量以了解其性能。
  • 负载卸载 预先为每种类型的请求分配容量,当请求超过容量时自动丢弃。
  • 静态资源处理 直接在边界返回某些响应。

四、编写一个Zuul网关

1、新建一个gateway-zuul-demo模块,在依赖项处添加【Cloud Discovery->Eureka Discovery和Cloud Rouing->Zuul】。

2、修改入口类,增加EnableZuulProxy注解

@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulDemoApplication {

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

 

3、修改appliation.yml

server:
  port: 9006
spring:
  application:
    name: gateway-zuul-demo
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
  instance:
    prefer-ip-address: true

 

4、启动Eureka Server、Rest-Demo和Gateway-Zuul-Demo,在浏览器中输入http://localhost:9006/rest-demo/user/xdlysk获取返回结果。

从上面的例子中的地址可以看出来默认Zuul的路由方式是:http://ZUULHOST:ZUULPORT/serviceId/**。

如果启动多个Rest-Demo可以发现Zuul里面还内置了Ribbon的负载均衡功能。

五、路由配置

Zuul提供了一套简单且强大路由配置策略,利用路由配置我们可以完成对微服务和URL更精确的控制。

1、重写指定微服务的访问路径:

zuul:
  routes:
    rest-demo: /rest/**

 

这表示将rest-demo微服务的地址映射到/rest/**路径。

2、忽略指定微服务:

zuul:
  ignored-services: rest-demo,xxx-service

 

使用“*”可忽略所有微服务,多个指定微服务以半角逗号分隔。

3、忽略所有微服务,只路由指定微服务:

zuul:
  ignored-services: *
  routes:
    rest-demo: /rest/**

 

4、路由别名:

zuul:
  routes:
    route-name: #路由别名,无其他意义,与例1效果一致
      service-id: rest-demo
      path: /rest/**

 

5、指定path和URL

zuul:
  routes:
    route-name:
      url: http://localhost:8000/
      path: /rest/**

 

此例将http://ZUULHOST:ZUULPORT/rest/映射到http://localhost:8000/。同时由于并非用service-id定位服务,所以也无法使用负载均衡功能。

6、即指定path和URL,又保留Zuul的Hystrix、Ribbon特性

zuul:
  routes:
    route-name: #路由别名,无其他意义,与例1效果一致
      service-id: rest-demo
      path: /rest/**
ribbon:
  eureka:
    enable: false #为Ribbon禁用Eureka
rest-demo:
  ribbon:
    listOfServers: localhost:9000,localhost:9001

 

7、借助PatternServiceRouteMapper实现路由的正则匹配

@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
    /**
     * A RegExp Pattern that extract needed information from a service ID. Ex :
     * "(?<name>.*)-(?<version>v.*$)"
     */
    //private Pattern servicePattern;
    /**
     * A RegExp that refer to named groups define in servicePattern. Ex :
     * "${version}/${name}"
     */
    //private String routePattern;
    return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
}

 

此例可以将rest-demo-v1映射为/v1/rest-demo/**。

8、路由前缀

zuul:
  prefix: /api
  strip-prefix: true
  routes:
    rest-demo: /rest/**

 

此时访问Zuul的/api/rest/user/xdlysk会被转发到/rest-demo/user/xdlysk。

9、忽略某些微服务中的某些路径

zuul:
  ignoredPatterns: /**/user/* #忽略所有包含/user/的地址请求
  routes:
    route-demo:
      service-Id: rest-demo
      path: /rest/**

 

更多的配置项和配置方法可以参考

spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java并结合上述例子扩展。在ZuulProperties.java中每个字段都会有注释解释其作用。

六、Filter工作原理

6.1 Zuul中的Filter

Zuul是围绕一系列Filter展开的,这些Filter在整个HTTP请求过程中执行一连串的操作。

Zuul Filter有以下几个特征:

  • Type:用以表示路由过程中的阶段(内置包含PRE、ROUTING、POST和ERROR)
  • Execution Order:表示相同Type的Filter的执行顺序
  • Criteria:执行条件
  • Action:执行体

Zuul提供了动态读取、编译和执行Filter的框架。各个Filter间没有直接联系,但是都通过RequestContext共享一些状态数据。

尽管Zuul支持任何基于JVM的语言,但是过滤器目前是用Groovy编写的。 每个过滤器的源代码被写入到Zuul服务器上的一组指定的目录中,这些目录将被定期轮询检查是否更新。Zuul会读取已更新的过滤器,动态编译到正在运行的服务器中,并后续请求中调用。

6.2 Filter Types

以下提供四种标准的Filter类型及其在请求生命周期中所处的位置:

  • PRE Filter:在请求路由到目标之前执行。一般用于请求认证、负载均衡和日志记录。
  • ROUTING Filter:处理目标请求。这里使用Apache HttpClient或Netflix Ribbon构造对目标的HTTP请求。
  • POST Filter:在目标请求返回后执行。一般会在此步骤添加响应头、收集统计和性能数据等。
  • ERROR Filter:整个流程某块出错时执行。

除了上述默认的四种Filter类型外,Zuul还允许自定义Filter类型并显示执行。例如,我们定义一个STATIC类型的Filter,它直接在Zuul中生成一个响应,而非将请求在转发到目标。

6.3 Zuul请求生命周期

一图胜千言,下面通过官方的一张图来了解Zuul请求的生命周期。

ZUUL-API网关[通俗易懂]

6.4 自定义一个Filter

1、添加一个PreRequestLogFilter:

public class PreRequestLogFilter extends ZuulFilter{

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

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

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

    @Override
    public Object run() {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        System.out.print(String.format("send %s request to %s",request.getMethod(),request.getRequestURL()));
        return null;
    }
}

Java

Copy

2、修改启动类GatewayZuulDemoApplication,添加Filter注入:

@Bean
public PreRequestLogFilter preRequestLogFilter(){
    return new PreRequestLogFilter();
}

 

3、启动服务并请求服务,观察控制台输出日志信息。

6.4 Filter的启用与禁用

我们自己写的Filter可以通过修改shouldFilter()启用或禁用。如果第三方的Filter怎样控制其启用及禁用呢?

很简单,通过配置文件就可以做到:

zuul:
  PreRequestLogFilter: #自定义Filter类名
    pre: #Type
      disable: true

 

七、Zuul部分源码分析

在前面提到在ROUTING过滤器中会选择使用Apache HttpClient或Netflix Ribbon请求目标服务,那么什么时候会使用Ribbon是么时候用Apache HttpClient呢?

这里涉及到两个关键Filter:RibbonRoutingFilterSimpleHostRoutingFilter。从名字就可以看出来RibbonRoutingFilter是使用Ribbon请求目标服务,而SimpleHostRoutingFilter则是另一个。

通过filterOrder()可以看到RibbonRoutingFilter会在SimpleHostRoutingFilter之前执行。

//spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java

@Override
public boolean shouldFilter() {
    RequestContext ctx = RequestContext.getCurrentContext();
    return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null /*获取serviceId*/
            && ctx.sendZuulResponse());
}

 

//spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java

@Override
public boolean shouldFilter() {
    return RequestContext.getCurrentContext().getRouteHost() != null
            && RequestContext.getCurrentContext().sendZuulResponse();
}

 

对比两个Filter的生效条件可以看出当配置serviceId时RibbonRoutingFilter生效。

下面看看RibbonRoutingFilter是如何将Ribbon和Hystrix集成进来的:

//spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java

public RibbonRoutingFilter(ProxyRequestHelper helper,
                           RibbonCommandFactory<?> ribbonCommandFactory,
                           List<RibbonRequestCustomizer> requestCustomizers) {
    this.helper = helper;
    this.ribbonCommandFactory = ribbonCommandFactory;
    this.requestCustomizers = requestCustomizers;
    // To support Servlet API 3.1 we need to check if getContentLengthLong exists
    // Spring 5 minimum support is 3.0, so this stays
    try {
        HttpServletRequest.class.getMethod("getContentLengthLong");
    } catch(NoSuchMethodException e) {
        useServlet31 = false;
    }
}

@Override
public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    this.helper.addIgnoredHeaders();
    try {
        RibbonCommandContext commandContext = buildCommandContext(context);
        ClientHttpResponse response = forward(commandContext);
        setResponse(response);
        return response;
    }
    catch (ZuulException ex) {
        throw new ZuulRuntimeException(ex);
    }
    catch (Exception ex) {
        throw new ZuulRuntimeException(ex);
    }
}

protected RibbonCommandContext buildCommandContext(RequestContext context) {
    HttpServletRequest request = context.getRequest();

    MultiValueMap<String, String> headers = this.helper
            .buildZuulRequestHeaders(request);
    MultiValueMap<String, String> params = this.helper
            .buildZuulRequestQueryParams(request);
    String verb = getVerb(request);
    InputStream requestEntity = getRequestBody(request);
    if (request.getContentLength() < 0 && !verb.equalsIgnoreCase("GET")) {
        context.setChunkedRequestBody();
    }

    String serviceId = (String) context.get(SERVICE_ID_KEY);
    Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
    Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);

    String uri = this.helper.buildZuulRequestURI(request);

    // remove double slashes
    uri = uri.replace("//", "/");

    long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength();

    return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
            requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
}

protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
    Map<String, Object> info = this.helper.debug(context.getMethod(),
            context.getUri(), context.getHeaders(), context.getParams(),
            context.getRequestEntity());

    RibbonCommand command = this.ribbonCommandFactory.create(context);
    try {
        ClientHttpResponse response = command.execute();
        this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
        return response;
    }
    catch (HystrixRuntimeException ex) {
        return handleException(info, ex);
    }

}

 

上述代码在构造函数中注入了ribbonCommandFactory(实际类型为RestClientRibbonCommandFactory)。接着在run()方法中构造RibbonCommandContext,并通过RestClientRibbonCommandFactory创建一个RibbonCommand(实际类型为RestClientRibbonCommand)。RestClientRibbonCommand继承AbstractRibbonCommand时所带的泛型参数RestClient具备负载均衡能力。又由于在RestClientRibbonCommand的继承链上出现了HystrixCommand,所以通过该Filter发出的请求实际上就同时集成了Ribbon和Hystrix。

八、Zuul的容错与回退

通过实现接口可以实现Zuul的容错与回退功能,下面这个例子来自Zuul的源码DefaultFallbackProvider,这里我稍微修改了下:

@Component
public class DefaultFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        //匹配微服务名字,*表示匹配所有
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return null;
    }

    @Override
    public ClientHttpResponse fallbackResponse(Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                //当fallback时返回给调用者的状态码
                return HttpStatus.OK;
            }

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

            @Override
            public String getStatusText() throws IOException {
                //状态码的文本形式
                return null;
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                //响应体
                return new ByteArrayInputStream("default fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                //设定headers
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.TEXT_HTML);
                return headers;
            }
        };
    }
}

 

停掉Rest-Demo后再请求,会发现返回了DefaultFallbackProvider中定义fallback的内容。

九、高可用

作为网关这么重要的角色,高可用是非常有必要的。但是通常来说网关所面对的请求应该的是来于外部,所以虽然说网关可以注册到Eureka Server上,但是外部的客户端数量众多,是不可能向Eureka Server注册的。那么要实现高可用的,要么在网关前面再架一个前置代理(如Nginx),要么让客户端从Eureka Server处获取Zuul网关地址实现客户端负载均衡。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170618.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • vue中显示静态图片怎么引用

    vue中显示静态图片怎么引用

    2022年4月2日
    34
  • CMS收集器和G1收集器的区别「建议收藏」

    CMS收集器和G1收集器的区别「建议收藏」目录CMS收集器和G1收集器的区别区别一:使用范围不一样区别二:STW的时间区别三:垃圾碎片区别四:垃圾回收的过程不一样对于CMS收集器和G1收集器的不同,目前简单写了一下4点,有不足的地方后面再不断的更新修改。CMS收集器和G1收集器的区别 区别一:使用范围不一样  CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集…

    2022年6月3日
    54
  • python dtype o_python – 什么是dtype(’O’)? – 堆栈内存溢出「建议收藏」

    python dtype o_python – 什么是dtype(’O’)? – 堆栈内存溢出「建议收藏」当你在数据帧中看到dtype(‘O’),这意味着Pandas字符串。什么是dtype?什么属于pandas或numpy,或两者,或其他什么?如果我们检查一下pandas代码:df=pd.DataFrame({‘float’:[1.0],’int’:[1],’datetime’:[pd.Timestamp(‘20180310′)],’string’:[‘foo’]})print…

    2022年5月17日
    99
  • 手机常见分辨率及代表机型有哪些_分辨率有哪几种

    手机常见分辨率及代表机型有哪些_分辨率有哪几种320×2404:31.33333HVGA/Half-sizeVGA640×4804:31.33333VGA/VideoGraphicArray/显示绘图阵列800×4805:31.66667WVGA/WideVGA854×48016:91.77916FWVGA/FullWideVGA960×54016:91.777

    2022年8月13日
    8
  • centos7怎么装系统_centos配置jdk环境变量

    centos7怎么装系统_centos配置jdk环境变量1.先查看是否安装了openjdk(两种方法)①在命令窗口键入:java -version②在命令窗口键入:rpm -qa | grep java2.在物理机下载的jdk安装包,后缀名是rpm3. 安装命令:rpm -ivh jdk-8u171-linux-x64.rpm4.配置JDK环境:vi /etc/profile#set java environmentJAVA_HOME=/usr/jav…

    2022年8月18日
    11
  • (二)提升树模型:Xgboost原理与实践

    (二)提升树模型:Xgboost原理与实践本篇博客是提升树模型博客的第二篇文章,第一篇介绍GBDT的博客可以参看这里。本篇博客是基于kingsam_的博客整理而来,在此表示感谢。在这篇文章的基础上,我加入了一些自己的理解,使得介绍Xgboost的内容更加详实易读。同介绍GBDT一样,我首先会介绍理论部分,然后举例说明模型训练过程,最后介绍一些细节问题。文章目录一、Xgboost简介二、监督学习的三要素2.1模型2.2参数2.3…

    2022年5月25日
    34

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号