盘点 Cloud : Zuul 主流程

946 阅读5分钟

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一. 前言

这一篇来学习一下Zuul 主处理流程 , 以下先来了主流程图 :

GateWayAll-main.jpg

二. 使用案例

通过配置文件就可以实现基本的路由规则

zuul:
  routes:
    test:
      url: http://127.0.0.1:8086/

三. 源码梳理

3.1 配置的加载与初始化

这里来看一下配置是通过什么方式加载到系统中的 , 首先看一下自动装配类 :

Zuul 的自动装配类是 ZuulServerAutoConfiguration , 它存在如下属性 :

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)


// 注入的类包括如下Bean :
- HasFeatures : 
- CompositeRouteLocator :
- SimpleRouteLocator : 
- ZuulController : 
- ZuulHandlerMapping : 
- ApplicationListener :
- ServletRegistrationBean : 

// Filter Bean 列表 :
- ServletDetectionFilter :
- FormBodyWrapperFilter :
- DebugFilter :
- Servlet30WrapperFilter : 
- SendResponseFilter :
- SendErrorFilter : 
- SendForwardFilter : 


// 其中包括三个 Configuration :
- ZuulFilterConfiguration :
- ZuulCounterFactoryConfiguration :
- ZuulMetricsConfiguration

其中核心的处理类就是 ZuulRefreshListener , 它继承于 ApplicationListener , 在 Application 创建时调用 , 其中主要会调用 zuulHandlerMapping

// Step 1 : reset 调用入口
private void reset() {
   // ((RefreshableRouteLocator) this.routeLocator).refresh();
   this.zuulHandlerMapping.setDirty(true);
}

// Step 2 : 刷新 routeLocator
for (RouteLocator locator : routeLocators) {
   if (locator instanceof RefreshableRouteLocator) {
      ((RefreshableRouteLocator) locator).refresh();
   }
}

// Step 3 : 添加 routes
protected void doRefresh() {
   this.routes.set(locateRoutes());
}

// Step 4 : 创建 routes
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
   LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
   
   // 调用 super local , 从 Properties 中获取配置
   routesMap.putAll(super.locateRoutes());
   
   if (this.discovery != null) {
      Map<String, ZuulRoute> staticServices = new LinkedHashMap<>();
      
      
      for (ZuulRoute route : routesMap.values()) {
          // 核心逻辑 , 加载 Properties 文件的配置到 Map 中
         String serviceId = route.getServiceId();
         staticServices.put(serviceId, route);
      }
      // 通过 DiscoveryClient从注册中心拉取关联
      List<String> services = this.discovery.getServices();
      
      // 从 Proerties 中获取 ignore 列表
      String[] ignored = this.properties.getIgnoredServices().toArray(new String[0]);
      for (String serviceId : services) {
      
         // 循环所有 services , 生成对应 key
          String key = "/" + mapRouteToService(serviceId) + "/**";
         
         // 这里会出现2种情况 : 
         // 一 : 如果已存在且不存在 url ,则为其设置 location
         if (staticServices.containsKey(serviceId)&& staticServices.get(serviceId).getUrl() == null) {
            ZuulRoute staticRoute = staticServices.get(serviceId);
            if (!StringUtils.hasText(staticRoute.getLocation())) {
               staticRoute.setLocation(serviceId);
            }
         }
         
         // 二 : 如果不在 ignore 队列中且之前不存在
         if (!PatternMatchUtils.simpleMatch(ignored, serviceId)&& !routesMap.containsKey(key)) {
               routesMap.put(key, new ZuulRoute(key, serviceId));
         }
      }
   }
   
   
   // 附加流程 : 如果存在  DEFAULT_ROUTE , 则将 routesMap 覆盖 DEFAULT_ROUTE
   if (routesMap.get(DEFAULT_ROUTE) != null) {
      ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
      routesMap.remove(DEFAULT_ROUTE);
      routesMap.put(DEFAULT_ROUTE, defaultRoute);
   }
   

   LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
   for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
      String path = entry.getKey();
       // 主要逻辑为构建 url , 为其添加前缀 / 和 prefix
      values.put(path, entry.getValue());
   }
   // 最终返回对象
   return values;
}

// Step 4-1 L
protected Map<String, ZuulRoute> locateRoutes() {
   LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
   
   // 从 properties 中获取 Route 列表 , 构建 routeMap
   for (ZuulRoute route : this.properties.getRoutes().values()) {
      routesMap.put(route.getPath(), route);
   }
   return routesMap;
}


至此 , Route 全部扫描完成 , 开始进入处理逻辑

3.2 请求的拦截

Zuul 请求的处理同样是通过 Servlet 进行的 , 前面自动装配中生成了 , 其主要入口为 : ZuulController , 它由 MVC 发起流程 , 主要流程如下 :

Step 1 : 拦截的入口

public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {
   try {
      // 实际调用父类  , 父类会继续调用 service , 到 ZuulServlet
      return super.handleRequestInternal(request, response);
   }
   finally {
      RequestContext.getCurrentContext().unset();
   }
}

Step 2 : ZuulServlet 处理请求

屏蔽相关的 try - catch 后 , 留下主要的方法如下 :


public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {

    //  Step 1 : 初始化请求 > zuulRunner.init(servletRequest, servletResponse)
    init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

    // Step 2 : 为容器设置标识符为 zuul 引擎  ->  put("zuulEngineRan", true);
    RequestContext context = RequestContext.getCurrentContext();
    context.setZuulEngineRan();

    // Step 3 : route 前置处理 , 主要是 filter 处理 
    preRoute();
    
    // Step 4 :route 路由
    route();

    // Step 5 : 后置处理 ,返回 Response
    postRoute();

}

Step 3 :通用 filter 进行处理

以上的三四五最终都是对 Filter 的执行 , 也是 Zuul 的核心逻辑 , 通过 Filter 进行处理 , 整体的核心代码为 : FilterProcessor.getInstance().route() , 不同的是三种不同的入口会传入三种不同的 type 进行处理 -> pre / route / post

// C- FilterProcessor
public Object runFilters(String sType) throws Throwable {

    boolean bResult = false;
    
    // 获取当前类型所有的 ZuulFilter
    // type 有多种 : pre / route / post , 这也意味着我们有三个切入点
    List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    
    // Step 2 : 循环 list 所有的 filter , 并且发起 ZuulFilter Process 进行处理
    Object result = processZuulFilter(zuulFilter);
    
    return bResult;
}


// 发起 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 {
        // Time 1 : 记录开始时间
        long ltime = System.currentTimeMillis();
        filterName = filter.getClass().getSimpleName();
        
        RequestContext copy = null;
        
        // 准备返回对象 ,分别承接异常和正常结果
        Object o = null;
        Throwable t = null;

        // 核心 !! 调用 Filter 进行处理 
        // 注意 , 由于forward 等都是 filter 进行的处理 , 所以存在状态和耗时
        ZuulFilterResult result = filter.runFilter();
        ExecutionStatus s = result.getStatus();
        
        // Time 2 : 记录结束时间
        execTime = System.currentTimeMillis() - ltime;
        
        switch (s) {
            case FAILED:
                // 调用异常 , 则获取其中 Exception 进行处理
                t = result.getException();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                break;
            case SUCCESS:
                // 调用成功 , 则获取 result
                o = result.getResult();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                break;
            default:
                break;
        }
        
        if (t != null) throw t;

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

    } catch (Throwable e) {
        // 异常处理会构建 ZuulException 进行返回 , 意味着可以捕捉该 Exception 进行处理
    }
}

Step 4 : 执行 FIlter

public ZuulFilterResult runFilter() {

    ZuulFilterResult zr = new ZuulFilterResult();
    
    // 判断 filter 是否打开 且需要过滤
    if (!isFilterDisabled()) {
        if (shouldFilter()) {
            // 准备异常对象
            Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
            try {
                // run 执行 filter 逻辑 , 会由实际的 filter 实现 , 并且构建返回体
                Object res = run();
                zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } catch (Throwable e) {
            
                // 此处就是构建 FAILED 状态 , 并且被外部拦截
                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;
}

补充 :preRoute 相关 Filter

org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter com.gang.zuul.service.demo.config.DefaultZuulFilter org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter

3.3 请求的重定向

ZuulServlet 的 service 中核心第二步就是发起重定向 ,同样的 , 这里会有多个 filter 进行处理 , 包括 :

  • RibbonRoutingFilter : Ribbon 负载均衡
  • SimpleHostRoutingFilter : 基础 route 类
  • SendForwardFilter : 重定向转发
public Object run() {
   // Step 1 : 从 Context 中获取请求对象
   RequestContext context = RequestContext.getCurrentContext();
   HttpServletRequest request = context.getRequest();
   
   // Step 2 : 获取 Header 头 和 Param 属性
   MultiValueMap<String, String> headers = this.helper.buildZuulRequestHeaders(request);
   MultiValueMap<String, String> params = this.helper.buildZuulRequestQueryParams(request);
   String verb = getVerb(request);
   
   
   // Step 3 : 从 request 中获取输入流 , PS : 流这种对象通常只能获取一次
   InputStream requestEntity = getRequestBody(request);
   if (getContentLength(request) < 0) {
      context.setChunkedRequestBody();
   }

   // Step 4 : 构建请求 URL -> /demo/start/get
   String uri = this.helper.buildZuulRequestURI(request);
   this.helper.addIgnoredHeaders();

   try {
       // Step 5 : 发起远程调用
      CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
            headers, params, requestEntity);
            
      // Step 6 : 把 Response 设置到容器中
      // RequestContext.getCurrentContext().set("zuulResponse", response);
      // this.helper.setResponse
      setResponse(response);
   }
   catch (Exception ex) {
      throw new ZuulRuntimeException(handleException(ex));
   }
   return null;
}

补充请求环节 :

针对上一部分 forward 的处理

// 1. 构建 host
URL host = RequestContext.getCurrentContext().getRouteHost();
HttpHost httpHost = getHttpHost(host);

// 2. 构建 InputStreamEntity
new InputStreamEntity(requestEntity, contentLength,contentType);

// 3. 构建 HttpRequest
buildHttpRequest(verb, uri, entity, headers, params,request);

// 4. HttpClient 发起远程调用
httpclient.execute(httpHost, httpRequest)

3.4 返回路由结果

其主要通过类 SendResponseFilter 实现 , 核心属性为 private ThreadLocal<byte[]> buffers , 其中包括二个主要的步骤 :

  • addResponseHeaders () : 写 Response Header 头
  • writeResponse () : 写 ResponseBody
private void addResponseHeaders() {

   RequestContext context = RequestContext.getCurrentContext();
   HttpServletResponse servletResponse = context.getResponse();
   
   // 如果开启了 DebugHeader , 则会添加对应Header 头
   // public static final String X_ZUUL_DEBUG_HEADER = "X-Zuul-Debug-Header";
   // 获取 context.get(ROUTING_DEBUG_KEY) , for 循环后添加到下方
   // servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());


   // 遍历容器中的 ZuulResponseHeaders , 添加到 servletResponse 中
   List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
   if (zuulResponseHeaders != null) {
      for (Pair<String, String> it : zuulResponseHeaders) {
         if (!ZuulHeaders.CONTENT_ENCODING.equalsIgnoreCase(it.first())) {
            servletResponse.addHeader(it.first(), it.second());
         }
      }
   }
   
   
   // 如果需要添加 LengthHeader , context.getOriginContentLength() + servletResponse.setContentLength
   // TODO :省略详情   
}

写入 Response 主逻辑

private void writeResponse() throws Exception {

   RequestContext context = RequestContext.getCurrentContext();
   // there is no body to send
   if (context.getResponseBody() == null
         && context.getResponseDataStream() == null) {
      return;
   }
   
   // 从容器中获取 response , 并且设置 CharacterEncoding
   HttpServletResponse servletResponse = context.getResponse();
   if (servletResponse.getCharacterEncoding() == null) { // only set if not set
      servletResponse.setCharacterEncoding("UTF-8");
   }

   // 核心 : 总体还是通过流进行输出
   String servletResponseContentEncoding = getResponseContentEncoding(context);
   OutputStream outStream = servletResponse.getOutputStream();
   
   InputStream is = null;
   try {
      // 区别 byte 流和 ZIP 流 
      if (context.getResponseBody() != null) {
         String body = context.getResponseBody();
         is = new ByteArrayInputStream(
               body.getBytes(servletResponse.getCharacterEncoding()));
      }
      else {
         is = context.getResponseDataStream();
         if (is != null && context.getResponseGZipped()) {
            // 在发送到客户端之前解压流 , 将 gzip 流总结发到客户端
            if (isGzipRequested(context)) {
               servletResponseContentEncoding = "gzip";
            }
            else {
               servletResponseContentEncoding = null;
               is = handleGzipStream(is);
            }
         }
      }
      
      // 往 reponse 设置 header , encoding 类型
      if (servletResponseContentEncoding != null) {
         servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING,servletResponseContentEncoding);
      }

      if (is != null) {
         // 往返回写 response 
         writeResponse(is, outStream);
      }
   }
   finally {
       // 关闭流等操作 
       - is.close();
       - buffers.remove();
       - (Closeable) zuulResponse).close();
   }
}

总结

总得来说 , Zuul 的核心就是 Filter 拦截 ,从请求中取出 Request , 再通过 route 进行转发 , 在此期间进行各种复杂的处理 , 整体来说 ,结构非常清晰简单

其中部分 Filter 例如负载均衡这次都没有深入 , 后续再来详细看看