Zuul总结

目录

为什么要有Zuul

Zuul简介

路由访问映射规则

简单配置

统一前缀

路由策略配置

服务名忽略

路径屏蔽

敏感请求头屏蔽

设置统一的公共前缀

最后的yml

Zuul 的过滤功能

令牌桶限流

Zuul过滤原理

关于 Zuul 的其他

Zuul和Feign和Nginx


注意:本文参考   

docs/system-design/framework/springcloud/springcloud-intro.md · SnailClimb/JavaGuide - Gitee.com

20000 字的 Spring Cloud 总结,从此任何问题也难不住你

外行人都能看懂的SpringCloud,错过了血亏!

Zuul工作原理_吴声子夜歌的博客-CSDN博客_zuul原理

为什么要有Zuul

基于上面的学习,我们现在的架构很可能会设计成这样:

这样的架构会有两个比较麻烦的问题:

1 路由规则与服务实例的维护间题:外层的负载均衡(nginx)需要维护所有的服务实例清单(图上的OpenService)

2 签名校验、 登录校验冗余问题:为了保证对外服务的安全性, 我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,但我们的服务是独立的,我们不得不在这些应用中都实现这样一套校验逻辑,这就会造成校验逻辑的冗余。

还是画个图来理解一下吧:

 

每个服务都有自己的IP地址,Nginx想要正确请求转发到服务上,就必须维护着每个服务实例的地址

更是灾难的是:这些服务实例的IP地址还有可能会变,服务之间的划分也很可能会变。

http://123.123.123.123

http://123.123.123.124

http://123.123.123.125

http://123.123.123.126

http://123.123.123.127

购物车和订单模块都需要用户登录了才可以正常访问,基于现在的架构,只能在购物车和订单模块都编写校验逻辑,这无疑是冗余的代码。

为了解决上面这些常见的架构问题,API网关的概念应运而生。在SpringCloud中了提供了基于Netfl ix Zuul实现的API网关组件Spring Cloud Zuul

Spring Cloud Zuul是这样解决上述两个问题的:

SpringCloud Zuul通过与SpringCloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。外层调用都必须通过API网关,使得将维护服务实例的工作交给了服务治理框架自动完成

在API网关服务上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验

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

Zuul简介

Zuul包含了对请求的路由和过滤两个最主要的功能:

其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.
 
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。

注意:Zuul服务最终还是会注册进Eureka

Zuul提供了代理+路由+过滤三大功能

Zuul是Netflix开源的微服务网关,核心是一系列过滤器。这些过滤器可以完成以下功能。

1. 是所有微服务入口,进行分发。
2. 身份认证与安全。识别合法的请求,拦截不合法的请求。
3. 监控。在入口处监控,更全面。
4. 动态路由。动态将请求分发到不同的后端集群。
5. 压力测试。可以逐渐增加对后端服务的流量,进行测试。
6. 负载均衡。也是用ribbon。
7. 限流。比如我每秒只要1000次,10001次就不让访问了。
8. 服务熔断

网关和服务的关系:演员和剧场检票人员的关系。

路由访问映射规则

简单配置

本来想给你们复制一些代码,但是想了想,因为各个代码配置比较零散,看起来也比较零散,我决定还是给你们画个图来解释吧。

比如这个时候我们已经向 Eureka Server 注册了两个 Consumer 、三个 Provicer ,这个时候我们再加个 Zuul 网关应该变成这样子了。

emmm,信息量有点大,我来解释一下。关于前面的知识我就不解释了

首先,Zuul 需要向 Eureka 进行注册,注册有啥好处呢?

你傻呀,Consumer 都向 Eureka Server 进行注册了,我网关是不是只要注册就能拿到所有 Consumer 的信息了?

拿到信息有什么好处呢?

我拿到信息我是不是可以获取所有的 Consumer 的元数据(名称,ip,端口)?

拿到这些元数据有什么好处呢?拿到了我们是不是直接可以做路由映射?比如原来用户调用 Consumer1 的接口 localhost:8001/studentInfo/update 这个请求,我们是不是可以这样进行调用了呢?localhost:9000/consumer1/studentInfo/update 呢?你这样是不是恍然大悟了?

这里的 url 为了让更多人看懂所以没有使用 restful 风格。

上面的你理解了,那么就能理解关于 Zuul 最基本的配置了,看下面。

server:
  port: 9000
eureka:
  client:
    service-url:
      # 这里只要注册 Eureka 就行了
      defaultZone: http://localhost:9997/eureka

然后在启动类上加入 @EnableZuulProxy 注解就行了。没错,就是那么简单

统一前缀

这个很简单,就是我们可以在前面加一个统一的前缀,比如我们刚刚调用的是 localhost:9000/consumer1/studentInfo/update,这个时候我们在 yaml 配置文件中添加如下。

zuul:
  prefix: /zuul

这样我们就需要通过 localhost:9000/zuul/consumer1/studentInfo/update 来进行访问了。

相当于   路由ip:路由端口/具体微服务名字/该微服务请求路径

原来是  具体微服务ip:具体微服务端口/该微服务请求路径

路由策略配置

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

将地址里的微服务 具体名称替换,防止别人发现名称

zuul:
  routes:
    consumer1: /FrancisQ1/**
    consumer2: /FrancisQ2/**

这个时候你就可以使用 ``localhost:9000/zuul/FrancisQ1/studentInfo/update` 进行访问了。

服务名忽略

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

zuul:
  ignore-services: "*"

路径屏蔽

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

zuul:
  ignore-patterns: **/auto/**

这样关于 auto 的请求我们就可以过滤掉了。

** 代表匹配多级任意路径

*代表匹配一级任意路径

敏感请求头屏蔽

默认情况下,像 CookieSet-Cookie 等敏感请求头信息会被 zuul 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。
 

设置统一的公共前缀

访问地址:http://myzuul.com:9527/atguigu/mydept/dept/get/1
zuul: 
  prefix: /atguigu
  ignored-services: "*"
  routes: 
    mydept.serviceId: microservicecloud-dept
    mydept.path: /mydept/**

最后的yml

server: 
  port: 9527
 
spring: 
  application:
    name: microservicecloud-zuul-gateway
 
zuul: 
  prefix: /atguigu
  ignored-services: "*"
  routes: 
    mydept.serviceId: microservicecloud-dept
    mydept.path: /mydept/**
 
eureka: 
  client: 
    service-url: 
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka  
  instance:
    instance-id: gateway-9527.com
    prefer-ip-address: true 
 
info:
  app.name: atguigu-microcloud
  company.name: www.atguigu.com
  build.artifactId: $project.artifactId$
  build.version: $project.version$
 

Zuul 的过滤功能

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

简单实现一个请求时间日志打印

要实现自己定义的 Filter 我们只需要继承 ZuulFilter 然后将这个过滤器类以 @Component 注解加入 Spring 容器中就行了。

在给你们看代码之前我先给你们解释一下关于过滤器的一些注意点。

过滤器类型:PreRoutingPost。前置Pre就是在请求之前进行过滤,Routing路由过滤器就是我们上面所讲的路由策略,而Post后置过滤器就是在 Response 之前进行过滤的过滤器。你可以观察上图结合着理解,并且下面我会给出相应的注释。

// 加入Spring容器
@Component
public class PreRequestFilter extends ZuulFilter {
    // 返回过滤器类型 这里是前置过滤器
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
    // 指定过滤顺序 越小越先执行,这里第一个执行
    // 当然不是只真正第一个 在Zuul内置中有其他过滤器会先执行
    // 那是写死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
    @Override
    public int filterOrder() {
        return 0;
    }
    // 什么时候该进行过滤
    // 这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等
    @Override
    public boolean shouldFilter() {
        return true;
    }
    // 如果过滤器允许通过则怎么进行处理
    @Override
    public Object run() throws ZuulException {
        // 这里我设置了全局的RequestContext并记录了请求开始时间
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.set("startTime", System.currentTimeMillis());
        return null;
    }
}
// lombok的日志
@Slf4j
// 加入 Spring 容器
@Component
public class AccessLogFilter extends ZuulFilter {
    // 指定该过滤器的过滤类型
    // 此时是后置过滤器
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }
    // SEND_RESPONSE_FILTER_ORDER 是最后一个过滤器
    // 我们此过滤器在它之前执行
    @Override
    public int filterOrder() {
        return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
    }
    @Override
    public boolean shouldFilter() {
        return true;
    }
    // 过滤时执行的策略
    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        // 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔
        Long startTime = (Long) context.get("startTime");
        // 这里我可以获取HttpServletRequest来获取URI并且打印出来
        String uri = request.getRequestURI();
        long duration = System.currentTimeMillis() - startTime;
        log.info("uri: " + uri + ", duration: " + duration / 100 + "ms");
        return null;
    }
}

上面就简单实现了请求时间日志打印功能,你有没有感受到 Zuul 过滤功能的强大了呢?

没有?好的、那我们再来。

令牌桶限流

当然不仅仅是令牌桶限流方式,Zuul 只要是限流的活它都能干,这里我只是简单举个例子

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

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

下面我们就通过 Zuul 的前置过滤器来实现一下令牌桶限流。

package com.lgq.zuul.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

@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;
    }
}

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

Zuul过滤原理

zuul的核心逻辑都是由一系列filter过滤器链实现的

Zuul的核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列的过滤器。Zuul包括以下4中过滤器:

PRE过滤器:它是在请求路由道具体的服务之前执行的,这种类型的过滤器可以做安全验证,例如身份验证、参数验证、认证鉴权,限流等。

ROUTING过滤器:它用于将请求路由到具体的微服务实例。在默认情况下,它使用Http Client进行网络请求,现在也支持OKHTTP。

POST过滤器:它是请求已被路由到微服务后执行的。一般情况下,用作收集统计信息、指标,以及将响应传送到客户端。如果需要对返回的结果进行再次处理,可以在这种过滤中处理逻辑。

ERROR过滤器:它是在其他过滤器发生错误时执行的。如果发生异常,就执行该filter,可以做全局异常处理。

Zuul采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接相互通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。

但是filter的类型不同,执行的时机也不同,效果自然也不一样,主要特点如下:

1 filter的类型:filter的类型,决定了它在整个filter链中的执行顺序,可能在端点路由前执行,也可能在端点路由时执行,还有可能在端点路由后执行,甚至是端点路由发生异常时执行。

2 filter的执行顺序:同一种类型的filter,可以通过filterOrder()方法设置执行顺序,一般都是根据业务场景自定义filter执行顺序。

3 filter执行条件:filter运行所需的标准,或条件。

4 filter执行效果:符合某个filter执行条件,产生执行效果。

zuul内部有一套完整的机制,可以动态读取编译运行filter机制,filter与filter之间不直接通信,在请求线程中会通过RequestContext来共享状态,它内部是用ThreadLocal实现的,例如HttpServletRequest、HttpServletResponse、异常信息等。部分源码如下:

public class RequestContext extends ConcurrentHashMap<String, Object> {

    private static final Logger LOG = LoggerFactory.getLogger(RequestContext.class);

    protected static Class<? extends RequestContext> contextClass = RequestContext.class;

    private static RequestContext testContext = null;

    protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
        @Override
        protected RequestContext initialValue() {
            try {
                return contextClass.newInstance();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
    };  //.......}

当一个客户端Request请求进入Zuul网关服务时,网关先进入“pre filter”, 进行一系列的验证、操作或者判断。然后交给“routing filter” 进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完后,最后由“post filter”进行处理,该类型的处理器处理完之后,将Response信息返回给客户端。

ZuulServlet是Zuul的核心Servlet。 ZuulServlet 的作用是初始化ZuulFilter,并编排这些ZuulFilter的执行顺序。该类中有一个service()方法,执行了过滤器执行的逻辑。

从上面的代码可知,首先执行preRoute()方法,这个方法执行的是PRE类型的过滤器的逻辑。如果执行这个方法时出错了,那么会执行error(e)和postRoute()。然后执行route()方法,该方法是执行ROUTING类型过滤器的逻辑。最后执行postRoute(),该方法执行了POST类型过滤器的逻辑。

public class ZuulServlet extends HttpServlet {

    private static final long serialVersionUID = -3374242278843351500L;
    private ZuulRunner zuulRunner;


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;

        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                preRoute(); //如果preRoute方法在执行的时候出现异常,直接就抛出500异常,不会走catch中的error方法,见下图FilterProcessor类中的preRoute方法。
            } catch (ZuulException e) {
                error(e); //如果preRoute在执行过程中,抛出Zuul异常,这里被捕捉到以后,会执行error方法,打印堆栈信息,见下图FilterProcessor类中的error方法。
                postRoute();
                return;
            }
            try {
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }  //.......
}

关于 Zuul 的其他

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

当然,Zuul 作为网关肯定也存在 单点问题 ,如果我们要保证 Zuul 的高可用,我们就需要进行 Zuul 的集群配置,这个时候可以借助额外的一些负载均衡器比如 Nginx 。

Zuul和Feign和Nginx

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

或者可以这样理解:

zuul是对外暴露的唯一接口相当于路由的是controller的请求,而Ribbon和Fegin路由了service的请求

zuul做最外层请求的负载均衡 ,而Ribbon和Fegin做的是系统内部各个微服务的service的调用的负载均衡

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

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

zuul是一个微服务的网关,所有的web请求都需要进过它进行路由。简单来说,A请求路由给A微服务,B请求交给B微服务处理。它仅支持http请求。不支持tcp协议。

nginx是一个网关,同样它可以路由web请求到对应的服务节点(有独立IP的服务器)。它一般做负载均衡使用。

说白点,两者设计的维度不同。

zuul路由的业务,对业务进行了归类,并交给了对应的微服务。

nginx路由请求的压力,对请求进行平均后,交给了服务器处理。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值