Zuul

Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器,是开源的微服务网关。在 SpringCloud 体系中,Zuul 担任着网关的角色,对发送到服务端的请求进行一些预处理,比如安全验证、动态路由、负载分配等。

Zuul 的核心是 Filters,根据执行时期分为以下几类:

  • PRE:这种过滤器在请求被路由之前调用
  • ROUTING:这种过滤器将请求路由到微服务
  • POST:这种过滤器在路由到微服务以后执行
  • ERROR:在其他阶段发生错误时执行该过滤器

一、zuul 1.x

1、工作原理

  • Zuul Servlet:zuul 的入口,所有的请求都从 servlet 进来,经过处理后,在从 servlet 返回;
  • ZuulFilter Runner:zuul 在 runner 中对一个 http 请求执行不同类型的 filter,filter 共有三种类型:pre、route、post;
  • Filter:filter 提供具体的过滤逻辑,zuul 提供 java 和 groovy 两种类型的 filter;
  • Filter Loader:用来管理 filter,提供 filter 添加和移除的操作,ZuulFilter Runner 通过 Filter Loader 来获取 filter 列表;
  • Request Context:context 中维护一个 ThreadLocal 的对象,该对象中存储这 http 请求的各种信息以供 filter 获取;

Zuul 的主体运行流程:

  1. 先通过 StartServer 初始化,启动 ZuulServlet;
  2. 在 ZuulServlet 中初始化 ZuulRunner,并调用 ZuulRunner 的方法来处理具体的 http 请求;
  3. ZuulRunner 调用 FilterProcessor 来进行具体的逻辑处理;
  4. FilterProcessor 通过 FilterLoader 来获取 Filter 列表并循环调用,通过 processZuulFilter 方法来调用每个 filter,processZuulFilter 中调用了每个 filter 的 runFilter 方法;
  5. Filter 列表中每个 Filter 都是 ZuulFilter 类型,并且实现了 run() 方法,run() 方法在 ZuulFilter 抽象类的 runFilter 方法中被调用;

2、源码

1. 代码目录

2. web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
   
    <listener>
        <!--1、初始化 mock moniter;2、初始化 filter file manager;3、初始化 filter -->
        <listener-class>com.netflix.zuul.StartServer</listener-class>
    </listener>
 
    <servlet>
        <!-- 用来处理 http 请求的核心模块,后面详细说明 -->
        <servlet-name>ZuulServlet</servlet-name>
        <servlet-class>com.netflix.zuul.http.ZuulServlet</servlet-class>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>ZuulServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
 
    <filter>
        <!-- 用来清理 context 中的变量,后面详细说明 -->
        <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>
</web-app>

3. 核心逻辑:zuul-core 

  • ZuulServlet

ZuulServlet 继承于 HttpServlet,重写了 HttpServlet 的 init 和 service 方法,并提供了 init、preRoute、route、postRoute、error 等处理方法;

  • init ():初始化了 zuulRunner,ZuulServlet 中对 http 请求的处理逻辑均由 zuulRunner 完成;
  • service ():通过对 init、preRoute、route、postRoute、error 等方法的调用,实现了处理 http 请求的具体流程;

init、preRoute、route、postRoute、error 方法均调用了 zuulRunner 的方法;

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();
            } 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();
        }
    }
 
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }
 
    void route() throws ZuulException {
        zuulRunner.route();
    }
 
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }
 
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
    }
 
    void error(ZuulException e) {
        RequestContext.getCurrentContext().setThrowable(e);
        zuulRunner.error();
    }
}
  • ZuulRunner

ZuulRunner 为 ZuulServlet 提供处理 http 请求的具体操作,ZuulRunner 的 init 方法中将 http 请求的上下文信息存放到 RequestContext 中,preRoute、route、postRoute 方法中调用了 FilterProcessor 的逻辑;

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));
}
 
public void postRoute() throws ZuulException {
    FilterProcessor.getInstance().postRoute();
}
 
public void route() throws ZuulException {
    FilterProcessor.getInstance().route();
}
 
public void preRoute() throws ZuulException {
    FilterProcessor.getInstance().preRoute();
}
 
public void error() {
    FilterProcessor.getInstance().error();
}
  • FilterProcessor

FilterProcessor 从 FilterLoader 中获取 Filter 列表,依次执行,列表类型有四种:pre、route、post、error;

public void postRoute() throws ZuulException {
    try {
        runFilters("post");
    } catch (ZuulException e) {
        throw e;
    } catch (Throwable e) {
        throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
    }
}
 
public void error() {
    try {
        runFilters("error");
    } catch (Throwable e) {
        logger.error(e.getMessage(), e);
    }
}
 
public void route() throws ZuulException {
    try {
        runFilters("route");
    } catch (ZuulException e) {
        throw e;
    } catch (Throwable e) {
        throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
    }
}
 
public void preRoute() throws ZuulException {
    try {
        runFilters("pre");
    } catch (ZuulException e) {
        throw e;
    } catch (Throwable e) {
        throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
    }
}
 
/**
 * 根据 type 获取 filter 列表,在 for 循环中依次执行,调用 processZuulFilter 方法
 */
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;
}

调用 filter 的具体逻辑在 processZuulFilter 中实现,主要调用了 ZuulFilter 的 runFilter 方法,runFilter 方法中执行某个 filter 的 run() 方法,这个方法在自定义 filter 的时候需要用户自己实现;

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();
        }
         
        // 这里调用 filter 的 runFilter 方法
        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

ZuulFilter 是一个抽象类,实现了 IZuulFilter 接口,IZuulFilter 接口中声明了 run() 方法,所有的 filter 实例必须实现这个方法,自定义的 filter 继承于 ZuulFilter;

public ZuulFilterResult runFilter() {
    ZuulFilterResult zr = new ZuulFilterResult();
    if (!isFilterDisabled()) {
        if (shouldFilter()) {
            Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
            try {
                Object res = run();
                zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } catch (Throwable e) {
                t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                zr.setException(e);
            } finally {
                t.stopAndLog();
            }
        } else {
            zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
        }
    }
    return zr;
}

二、zuul 2.x

工作目录

  • Bootstrap.start() 启动 netty 工程
public class Bootstrap {
    public static void main(String[] args) {
        new Bootstrap().start();
    }
 
    public void start() {
        System.out.println("Zuul Sample: starting up.");
        long startTime = System.currentTimeMillis();
        int exitCode = 0;
 
        Server server = null;
 
        try {
            ConfigurationManager.loadCascadedPropertiesFromResources("application");
            Injector injector = InjectorBuilder.fromModule(new ZuulSampleModule()).createInjector();
 
 
            /**
             * 获取 serverStartup 对象,demo 中实现了一个 SampleServerStartup,继承于 BaseServerStartup;
             * SampleServerStartup 重写了 choosePortsAndChannels 方法,该方法返回一个 map,key 为 port,value 为 ChannelInitializer;
             * 在构建 Server 对象的时候将这个 map 以参数的形式传入;
             */
            BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class);
             
            // 从 serverStartup 获取 server
            server = serverStartup.server();
 
            long startupDuration = System.currentTimeMillis() - startTime;
            System.out.println("Zuul Sample: finished startup. Duration = " + startupDuration + " ms");
 
            // 启动
            server.start(true);
        }
        catch (Throwable t) {
            t.printStackTrace();
            System.err.println("###############");
            System.err.println("Zuul Sample: initialization failed. Forcing shutdown now.");
            System.err.println("###############");
            exitCode = 1;
        }
        finally {
            // server shutdown
            if (server != null) server.stop();
 
            System.exit(exitCode);
        }
    }
}
  •  Server通过 start 方法启动
public class Server
{
    ......
 
    public Server(Map<Integer, ChannelInitializer> portsToChannelInitializers, ServerStatusManager serverStatusManager, ClientConnectionsShutdown clientConnectionsShutdown, EventLoopGroupMetrics eventLoopGroupMetrics, EventLoopConfig eventLoopConfig)
    {
        // 由 SampleServerStartup 创建 Server 对象时传入
        this.portsToChannelInitializers = portsToChannelInitializers;
         
        ......
    }
 
    ......
 
    public void start(boolean sync)
    {
        serverGroup = new ServerGroup("Salamander", eventLoopConfig.acceptorCount(), eventLoopConfig.eventLoopCount(), eventLoopGroupMetrics);
        serverGroup.initializeTransport();
        try {
            List<ChannelFuture> allBindFutures = new ArrayList<>();
 
            // Setup each of the channel initializers on requested ports.
            for (Map.Entry<Integer, ChannelInitializer> entry : portsToChannelInitializers.entrySet())
            {
                // setupServerBootstrap 方法将 ServerBootstrap 与 channelInitializer 绑定;
                // 参数中:entry.getKey 为 port,将与 ServerBootstrap 绑定,entry.getValue 为 channelInitializer,将作为 ServerBootstrap 的 handler;
                // netty 中通过 handler 处理数据,这里是 zuul 的处理逻辑;
                allBindFutures.add(setupServerBootstrap(entry.getKey(), entry.getValue()));
            }
 
            // Once all server bootstraps are successfully initialized, then bind to each port.
            for (ChannelFuture f: allBindFutures) {
                // Wait until the server socket is closed.
                ChannelFuture cf = f.channel().closeFuture();
                if (sync) {
                    cf.sync();
                }
            }
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
 
    private ChannelFuture setupServerBootstrap(int port, ChannelInitializer channelInitializer)
            throws InterruptedException
    {
        ServerBootstrap serverBootstrap = new ServerBootstrap().group(
                serverGroup.clientToProxyBossPool,
                serverGroup.clientToProxyWorkerPool);
 
        // Choose socket options.
        Map<ChannelOption, Object> channelOptions = new HashMap<>();
        channelOptions.put(ChannelOption.SO_BACKLOG, 128);
        //channelOptions.put(ChannelOption.SO_TIMEOUT, SERVER_SOCKET_TIMEOUT.get());
        channelOptions.put(ChannelOption.SO_LINGER, -1);
        channelOptions.put(ChannelOption.TCP_NODELAY, true);
        channelOptions.put(ChannelOption.SO_KEEPALIVE, true);
 
        // Choose EPoll or NIO.
        if (USE_EPOLL.get()) {
            LOG.warn("Proxy listening with TCP transport using EPOLL");
            serverBootstrap = serverBootstrap.channel(EpollServerSocketChannel.class);
            channelOptions.put(EpollChannelOption.TCP_DEFER_ACCEPT, Integer.valueOf(-1));
        }
        else {
            LOG.warn("Proxy listening with TCP transport using NIO");
            serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);
        }
 
        // Apply socket options.
        for (Map.Entry<ChannelOption, Object> optionEntry : channelOptions.entrySet()) {
            serverBootstrap = serverBootstrap.option(optionEntry.getKey(), optionEntry.getValue());
        }
 
        serverBootstrap.childHandler(channelInitializer);
        serverBootstrap.validate();
 
        LOG.info("Binding to port: " + port);
 
        // Flag status as UP just before binding to the port.
        serverStatusManager.localStatus(InstanceInfo.InstanceStatus.UP);
 
        // Bind and start to accept incoming connections.
        return serverBootstrap.bind(port).sync();
    }
 
    ......
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值