前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Zuul详解

Zuul详解

作者头像
良辰美景TT
发布2018-09-11 14:30:24
1.7K0
发布2018-09-11 14:30:24
举报
文章被收录于专栏:java、Spring、技术分享

简介

  微服务具有系统小(一个程序员可以独立完成开发),可以独立部署,能快速进行迭代等优点。因为系统切分的小,必然也就意味着会有更多的系统需要进行维护。在实际应用中,相关的系统一般部署在同一个机房,内部之间通过Eureka的服务发现机制与Ribbon客户端负载便可以很好的实现系统间的调用。而外部的应用如何来访问公司内部各种各样的微服务呢?在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。(功能上应该和Nginx差不多,Zuul基于Eureka的服务发现功能动态实现路由的功能)

简单实例

  Zuul属于Netflix下的开源项目,我们在项目中可以单独使用,官方地址:https://github.com/Netflix/zuul。由于博文属于Spring Cloud系列,我们的简单实例还是在Spring Cloud下面来实现一下Zuul版的Hello world。

  • 还是万年不变的导入依赖的包,pom.xml文件的配置如下:
代码语言:javascript
复制
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
    </dependencies>
  • 编写启动类App.java,这里用到了一个新的注解EnableZuulProxy。代码如下:
代码语言:javascript
复制
package com.ivan.zuul;

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

/**
 * 
 * 功能描述: 
 * 
 * @version 2.0.0
 * @author zhiminchen
 */
@SpringBootApplication
@EnableZuulProxy
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}
  • 编写配置文件,代码如下
代码语言:javascript
复制
server.port=10000

zuul.routes.provider.path=/provider/**
zuul.routes.provider.url=http://localhost:8000/

上面配置zuul.routes 的 provider可以是任何值,其中的path指的是访问Zuul这个系统时的路径,而下面的url指的就是在访问上面的path的时候会路由的路径,是不是很简单。Zuul对访问路径做了一层代理,我们可以基于Zuul做数据的裁剪以及聚合功能。权限过滤也可以放在这一层。

Zuul与Eureka整合,实现服务自动发现与负载均衡

  • 首先还是在pom.xml里加入Eureka的依赖。代码如下:
代码语言:javascript
复制
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
  • 修改application.properties文件
代码语言:javascript
复制
server.port=10000
spring.application.name=zuul
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

ribbon.ReadTimeout=5000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

zuul.routes.provider.path=/provider/**
zuul.routes.provider.serviceId=PROVIDER

可以看出上面的属性path对应了相应的serviceId。而这个serviceId就是我们微服务配置的spring.application.name属性。上面的配置自动集成了Ribbon的负载均衡。

Zuul源码解读

参考文章:https://blog.csdn.net/forezp/article/details/76211680

zuul.png

  在zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。Zuul过滤器的运行机制如下图:

image.png

上面的解释来源于网络,我们还以从EnableZuulProxy这个注解看看他帮我们做了些什么事情吧。EnableZuulProxy 的代码如下:

代码语言:javascript
复制
package org.springframework.cloud.netflix.zuul;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.context.annotation.Import;

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

可以看出这里还是通过Spring 的Import机制加载了ZuulProxyMarkerConfiguration这个类。现在看看ZuulProxyMarkerConfiguration这个类都做了些什么。源代码如下:

代码语言:javascript
复制
package org.springframework.cloud.netflix.zuul;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ZuulProxyMarkerConfiguration {
    @Bean
    public Marker zuulProxyMarkerBean() {
        return new Marker();
    }

    class Marker {
    }
}

可以看到这个类首先是由Configuration注解类修饰,这样这个类里申明的Bean会被加载到Spring IOC容器里,从这个类里看,并没有加载我们希望看到的zuulservlet对象,而且这个Marker类没有任何代码,只是做了一下申明。在相同的包下,我们找到了ZuulProxyAutoConfiguration这个类,以下是这个类的截图:

image.png

这个类继承ZuulServerAutoConfiguration,在这个类里加载了zuulServlet,代码如下:

代码语言:javascript
复制
    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
                this.zuulProperties.getServletPattern());
        // The whole point of exposing this servlet is to provide a route that doesn't
        // buffer requests.
        servlet.addInitParameter("buffer-requests", "false");
        return servlet;
    }

debug看下这个ZuulServlet对应的url是什么。效果如下:

image.png

这里配置的url为/zuul/*,但很明显我们在测试的时候并没有用在访问url上面加上zuul。那这个zuulServlet是如何做到拦截的呢。我们找到了ZuulController类,这个类持有zullServlet的代理。而zullController的handlerMapping类拦截的是/,代码截图如下:

image.png

请求URL映射就分析到这里了,下面我们来看看 zullServlet里是如何处理请求的。 service方法如下:

代码语言:javascript
复制
    @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();
            } catch (ZuulException e) {
                error(e);
                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();
        }
    }

这里的处理逻辑结合下面的图是不是很清晰了。

image.png

我们捡最重要的route()方法看看是如何处理逻辑的吧。代码如下:

代码语言:javascript
复制
    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }

    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        zuulRunner.route();
    }

    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }

最终处理的还是zuulRunner对象。而在zuulRunner里的处理逻辑就更简单了,代码如下:

代码语言:javascript
复制
    public void route() throws ZuulException {
        FilterProcessor.getInstance().route();
    }

直接交给了FilterProcessor处理,FilterProcessor的代码如下,我只截取部分代友,并做相应的注释:

代码语言:javascript
复制
public class FilterProcessor {
//zuulRunner的route方法会调到这个方法里
    public void route() throws ZuulException {
        try {
//传入的route代表的是一种filter类型,目前有"pre", "route", "post" 三种类型
            runFilters("route");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
        }
    }

/**
这个方法里会根据传入的filter类型,执行相应的filter逻辑
**/
    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
//FilterLoader 里持用所有的filter对象
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
//执行具体的filter
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }

}

/**
这个方法会处理具体的ZuulFilter
**/
    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
//处理的上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        boolean bDebug = ctx.debugRouting();
        final String metricPrefix = "zuul.filter-";
        long execTime = 0;
        String filterName = "";
        try {
            long ltime = System.currentTimeMillis();
            filterName = filter.getClass().getSimpleName();
            
            RequestContext copy = null;
            Object o = null;
            Throwable t = null;

            if (bDebug) {
                Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
                copy = ctx.copy();
            }
            //在这里会执行run方法啦
            ZuulFilterResult result = filter.runFilter();
            ExecutionStatus s = result.getStatus();
            execTime = System.currentTimeMillis() - ltime;

            switch (s) {
                case FAILED:
                    t = result.getException();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                    break;
                case SUCCESS:
                    o = result.getResult();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                    if (bDebug) {
                        Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                        Debug.compareContextState(filterName, copy);
                    }
                    break;
                default:
                    break;
            }
            
            if (t != null) throw t;

            usageNotifier.notify(filter, s);
            return o;

        } catch (Throwable e) {
            if (bDebug) {
                Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
            }
            usageNotifier.notify(filter, ExecutionStatus.FAILED);
            if (e instanceof ZuulException) {
                throw (ZuulException) e;
            } else {
                ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                throw ex;
            }
        }
    }

代码分析到这里,整个流程也就走的差不多了,但还是有个疑问,我们自定义的ZuulFilter系统是如何发现的呢?FilterLoader又是如何根据类型取到ZuulFilter?带着这样的疑问我们来看一下FilterLoader类,关键代码有做相应的注释,代码如下:

代码语言:javascript
复制
public class FilterLoader {
    final static FilterLoader INSTANCE = new FilterLoader();

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

    private final ConcurrentHashMap<String, Long> filterClassLastModified = new ConcurrentHashMap<String, Long>();
    private final ConcurrentHashMap<String, String> filterClassCode = new ConcurrentHashMap<String, String>();
    private final ConcurrentHashMap<String, String> filterCheck = new ConcurrentHashMap<String, String>();
    private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();

//最终所有的filter都是注册到这个类上的,类的名字已经可以看出它的作用了
    private FilterRegistry filterRegistry = FilterRegistry.instance();

    static DynamicCodeCompiler COMPILER;
    
    static FilterFactory FILTER_FACTORY = new DefaultFilterFactory();

    // overidden by tests
    public void setFilterRegistry(FilterRegistry r) {
        this.filterRegistry = r;
    }

    
    /**
     * @return Singleton FilterLoader
     */
    public static FilterLoader getInstance() {
        return INSTANCE;
    }
}

    public List<ZuulFilter> getFiltersByType(String filterType) {
//hashFiltersByType 相当于FilterLoader内部的缓存
        List<ZuulFilter> list = hashFiltersByType.get(filterType);
        if (list != null) return list;

        list = new ArrayList<ZuulFilter>();

//从这里可以看出 filterRegistry 一定集成了一个集合对象
        Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
        for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
            ZuulFilter filter = iterator.next();
            if (filter.filterType().equals(filterType)) {
                list.add(filter);
            }
        }
        Collections.sort(list); // sort by priority

        hashFiltersByType.putIfAbsent(filterType, list);
        return list;
    }

FilterRegistry 类的代码就很简单啦,内部持有一个ConcurrentHashMap。代码如下:

代码语言:javascript
复制
public class FilterRegistry {

    private static final FilterRegistry INSTANCE = new FilterRegistry();

    public static final FilterRegistry instance() {
        return INSTANCE;
    }

    private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();

    private FilterRegistry() {
    }

    public ZuulFilter remove(String key) {
        return this.filters.remove(key);
    }

    public ZuulFilter get(String key) {
        return this.filters.get(key);
    }

    public void put(String key, ZuulFilter filter) {
        this.filters.putIfAbsent(key, filter);
    }

    public int size() {
        return this.filters.size();
    }

    public Collection<ZuulFilter> getAllFilters() {
        return this.filters.values();
    }

}

到现在我们已经知道如何根据类型取ZuulFilter对象了,但FilterRegistry 里的ZuulFilter是如何put进去的,FilterRegistry 类的ConcurrentHashMap里的key又是什么呢?必竟我们上面在写实例的时候并没有调用这个方法。带着这样的问题,我在ZuulServerAutoConfiguration类发现初使化的地方,下图是debug状态。

image.png

到这里我们就分析完了Zuul的关键代码。下面自定义一个ZuulFilter看看是否能起作用

自定义ZuulFilter

有了上面的源码解析,要自定义ZuulFilter就很容易了,直接上代码吧:

代码语言:javascript
复制
package com.ivan.zuul.filter;

import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;

@Component
public class ZuulFiterTest extends ZuulFilter{

    //返回为true才会执行
    public boolean shouldFilter() {
        return true;
    }

    public Object run() throws ZuulException {
        System.out.println("执行了里面的逻辑啦");
        return "true";
    }

    //ZuulFilter的类型
    @Override
    public String filterType() {
        return FilterConstants.ROUTE_TYPE;
    }

    /**
     * 优先级
     */
    @Override
    public int filterOrder() {
        return 0;
    }
    

}

效果如下图:

image.png

可以看到我们自定义的Filter已经起作用了,对于特殊的路由规则,我们可以定义自已的ZuulFilter。比如可以实现基于Zuul做数据的裁剪以及聚合功能。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.07.12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 简单实例
  • Zuul与Eureka整合,实现服务自动发现与负载均衡
  • Zuul源码解读
  • 自定义ZuulFilter
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档