zuul-开篇

一、简要介绍

zuul wiki链接

Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.

这里有几个关键词

  • front door
    我们这里可以理解为请求网关,在底层服务和客户端/网页之间的通讯桥梁。我个人对zuul的理解更偏向于业务总线,服务编排,整合底层的各种基础服务。
  • dynamic routing
    动态路由
  • resiliency
    可伸缩
  • security
    安全性
    wiki里面提到的3个使用场景
  • Surgical Routing
  • Stress Testing
  • Multi-Region Resiliency
    其实我个人认为有点夸大的成分,我理解这些都是动态路由的应用场景而已,而zuul的动态路由也只是说把拦截器的逻辑用groovy来写,通过这种方式来实现热部署,并不是什么稀奇的东西。

二、源码下载&启动

git地址
官方安装wiki

  1. 源码下载并编译
  2. 修改配置文件
    application.properties
zuul.filters.root=zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters

filter的根目录修改一下

  1. 进程启动配置jvm的系统参数
-DTZ=GMT -Darchaius.deployment.environment=test -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Deureka.validateInstanceId=false -Deureka.mt.num_retries=1
  1. 运行com.netflix.zuul.sample.Bootstrap启动类
Zuul Sample: starting up.
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Zuul Sample: finished startup. Duration = 26856 ms
2019-02-15 00:38:59,487 WARN  io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'SO_KEEPALIVE' for channel '[id: 0x283bb17c]'
2019-02-15 00:38:59,487 WARN  io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'SO_LINGER' for channel '[id: 0x283bb17c]'
2019-02-15 00:38:59,487 WARN  io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'TCP_NODELAY' for channel '[id: 0x283bb17c]'

三、核心组件分析

这里对zuul分析的版本是v1.1.0。为什么选择zuul1的版本来分析,主要是考虑到第一版的特性会比较少,方便我了解zuul的核心功能。另外zuul1用的是BIO,代码看起来会简单不少。zuul2增加了很多新的特性和优化,后面会抽时间慢慢看完。


zuul的请求流程

接下来分析下zuul-simple-webapp

web.xml configures a few things:

  • StartServer as a ServletContextListener that initializes the app.
  • ZuulServlet is a servlet that matches all requests. It performs the core Zuul Filter flow of executing pre, routing, and post Filters.
  • ContextLifecycleFilter is a servlet filter matching all requests. It cleans up the RequestContextafter each request, ensuring isolation.
<listener>
    <listener-class>com.netflix.zuul.StartServer</listener-class>
</listener>

<servlet>
    <servlet-name>Zuul</servlet-name>
    <servlet-class>com.netflix.zuul.http.ZuulServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>Zuul</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>ContextLifecycleFilter</filter-name>
    <filter-class>com.netflix.zuul.context.ContextLifecycleFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ContextLifecycleFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
1. StartServer.java
private void initGroovyFilterManager() {
        FilterLoader.getInstance().setCompiler(new GroovyCompiler());

        String scriptRoot = System.getProperty("zuul.filter.root", "");
        if (scriptRoot.length() > 0) scriptRoot = scriptRoot + File.separator;
        try {
            FilterFileManager.setFilenameFilter(new GroovyFileFilter());
            FilterFileManager.init(5, scriptRoot + "pre", scriptRoot + "route", scriptRoot + "post");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

这里定义了groovy的编译器和Zuul的Filter的加载路径。

2. ZuulFilter的定义

zuulFilter即为zuul的过滤器实现。我们后续需要实现的各种过滤器都必须基于这个类来实现。接口比较简单,注释也写得很好,这里就不展开来讲。

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {

    private final DynamicBooleanProperty filterDisabled =
            DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);

    /**
     * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
     * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
     * We also support a "static" type for static responses see  StaticResponseFilter.
     * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
     *
     * @return A String representing that type
     */
    abstract public String filterType();

    /**
     * filterOrder() must also be defined for a filter. Filters may have the same  filterOrder if precedence is not
     * important for a filter. filterOrders do not need to be sequential.
     *
     * @return the int order of a filter
     */
    abstract public int filterOrder();

    /**
     * By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
     *
     * @return true by default
     */
    public boolean isStaticFilter() {
        return true;
    }

    /**
     * The name of the Archaius property to disable this filter. by default it is zuul.[classname].[filtertype].disable
     *
     * @return
     */
    public String disablePropertyName() {
        return "zuul." + this.getClass().getSimpleName() + "." + filterType() + ".disable";
    }

    /**
     * If true, the filter has been disabled by archaius and will not be run
     *
     * @return
     */
    public boolean isFilterDisabled() {
        return filterDisabled.get();
    }
3. ZuulServlet.java

接下来看ZuulServlet,这部分是zuul最核心的逻辑。

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

首先会初始化ZuulRunner,其中bufferReqs这个属性其实就是控制Request是否使用bufferReader,允许多次从request里面读取内容。

ZuulRunner.java

/**
     * sets HttpServlet request and HttpResponse
     *
     * @param servletRequest
     * @param servletResponse
     */
    public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }

接下里我们看zuulSerlvet的核心逻辑。

@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();
        }
    }
  • preRoute和postRoute不管发生任何异常的情况下都会执行。如果在执行
    preRoute的时候就发生异常,会跳过route的逻辑,直接到postRoute。
  • 不管在哪一步发生异常都会执行error的过滤器。

另外RequestContext的作用为了保存请求和执行结果的上下文,方便在过滤器中传递。包括过滤器之间如果需要做传输传递的话也是依赖RequestContext来实现。

5.FilterProcessor.java

最后我们再看下执行过滤器的逻辑,实时上就是根据过滤器的类型拿到过滤器的链表,遍历执行

/**
     * runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
     *
     * @param sType the filterType.
     * @return
     * @throws Throwable throws up an arbitrary exception
     */
    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }

三、总结

zuul的代码不多,整体的解决思路有点类似spring的拦截器实现,只是spring拦截器一般面向的是方法,zuul建议面向的是服务(当然这个看个人的使用方式)。另外zuul使用允许过滤器使用groovy进行动态编译注入,不需要发版。我认为在解决问题的思路上并不是一个新的思路,只是说在分布式的场景下的一个应用场景罢了。当然zuul2增加了很多新的特性,这个是需要我这边去深入了解的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352