一、zuul如何路由到上游服务器

所有文章

https://www.cnblogs.com/lay2017/p/11908715.html

正文

zuul在分布式项目中充当着一个网关的角色,而它最主要的功能像nginx一样针对上游服务器做反向代理。我们可以将它理解为一个服务的门面,作为客户端来说不需要再面向各式各样的服务,只需要面向zuul即可,简化了客户端与服务端的交互关系。

既然,zuul成为了客户端与服务端的中间层,那么zuul显然可以进行拦截、记录、安全管理、路由...等等各种处理。本文,将从路由这个点切入,看看路由的过程。

ZuulServlet

首先,客户端和服务端的交互显然少不了的http,所以先找到zuul针对Servlet的实现

可以看到,ZuulServlet直接继承了HttpServlet。所以,ZuulServlet依然走的是http通信协议,我们跟进ZuulServlet的service方法。

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

        // 初始化一个上下文
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();
        // 路由前置处理-------------------
        try {
            // pre类型的ZuulFilter
            preRoute();
        } catch (ZuulException e) {
            // error类型的ZuulFilter
            error(e);
            // post类型的ZuulFilter
            postRoute();
            return;
        }
        // 路由处理-----------------------
        try {
            // route类型的ZuulFilter
            route();
        } catch (ZuulException e) {
            // error类型的ZuulFilter
            error(e);
            // post类型的ZuulFilter
            postRoute();
            return;
        }
        // 路由后置处理--------------------
        try {
            // post类型的ZuulFilter
            postRoute();
        } catch (ZuulException e) {
            // error类型的ZuulFilter
            error(e);
            return;
        }

    } catch (Throwable e) {
        // ...
    } finally {
        RequestContext.getCurrentContext().unset();
    }
}

显然,service方法很清晰地描绘了一个这样的路由过程:

浏览器发起响应 -> preFilter -> routeFilter -> postFilter -> 浏览器接受响应

                    |---------|-----------|-------> errorFilter -> 浏览器接受响应

PreDecorationFilter

preFilter无非就是对Servlet的请求信息进行处理,为routeFilter做准备。默认的preFilter有这么5个

这里我们以PreDecorationFilter为例,看看它的处理过程。

public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
    // 获取路由信息
    Route route = this.routeLocator.getMatchingRoute(requestURI);
    if (route != null) {
        // ... 处理路由信息,添加到context当中
    } else {
        // ...
    }
    return null;
}

PreDecorationFilter主要是做了一个路由准备。例如:http://localhost:8080/consumer/user/get?userId=1

这里的route信息将会是

经过PreDecorationFilter以后,我们已经知道了一个请求该路由到哪里去。

RibbonRoutingFilter

routeFilter默认有以下三种,这里以RibbonRoutingFilter为例

跟进RibbonRoutingFilter的run方法

@Override
public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    this.helper.addIgnoredHeaders();
    try {
        RibbonCommandContext commandContext = buildCommandContext(context);
        // 转发请求
        ClientHttpResponse response = forward(commandContext);
        // 设置响应结果到上下文
        setResponse(response);
        return response;
    }
    catch (ZuulException ex) {
        throw new ZuulRuntimeException(ex);
    }
    catch (Exception ex) {
        throw new ZuulRuntimeException(ex);
    }
}

run方法中做了一次请求转发,我们跟进forward看看

protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
    Map<String, Object> info = this.helper.debug(context.getMethod(),
            context.getUri(), context.getHeaders(), context.getParams(),
            context.getRequestEntity());
    // 构造RibbonCommand
    RibbonCommand command = this.ribbonCommandFactory.create(context);
    try {
        // 执行RibbonCommand
        ClientHttpResponse response = command.execute();

        return response;
    }
    catch (HystrixRuntimeException ex) {
        return handleException(info, ex);
    }

}

这里构造并执行了一个RibbonComand,具体的实例对象是HttpClientRibbonCommand,我们看看它的类图

HttpClientRibbonCommand主要是包含了三种实现

1、ClientRequest:实现了请求响应

2、RibbonCommand表示了一个负载均衡的实现

3、HystrixCommand表示了一个熔断的实现

到这里我们基本可以知道HttpClientRibbonCommand的请求过程

Hystrix熔断前置判断 -> Ribbon负载均衡处理 -> http请求到上游服务 -> 返回响应结果 -> 设置到上下文当中

SendResponseFilter

经过routeFilter以后,我们已经获得了上游服务器的response结果。然后就是postFilter,默认的postFilter只有一个SendResponseFilter,顾名思义其实就是发送响应结果返回到客户端。

打开SendResponseFilter的run方法

@Override
public Object run() {
    try {
        addResponseHeaders();
        writeResponse();
    }
    catch (Exception ex) {
        ReflectionUtils.rethrowRuntimeException(ex);
    }
    return null;
}

只做了一件事,写入响应数据,跟进writeResponse方法

private void writeResponse() throws Exception {
    RequestContext context = RequestContext.getCurrentContext();

    // ...
    HttpServletResponse servletResponse = context.getResponse();

    // ...

    OutputStream outStream = servletResponse.getOutputStream();
    // 获取输入流
    InputStream is = null;
    try {
        if (context.getResponseBody() != null) {
            String body = context.getResponseBody();
            // 响应内容转化为字节流
            is = new ByteArrayInputStream(body.getBytes(servletResponse.getCharacterEncoding()));
        } else {
            // ...
        }

        // ...

        if (is != null) {
            // 写入响应流
            writeResponse(is, outStream);
        }
    } finally {
        // 清理...
    }
}

这里生成了字节流并写入outStream,继续跟进writeResponse

private void writeResponse(InputStream zin, OutputStream out) throws Exception {
    byte[] bytes = buffers.get();
    int bytesRead = -1;
    while ((bytesRead = zin.read(bytes)) != -1) {
        out.write(bytes, 0, bytesRead);
    }
}

单纯地写入输出流

总结

Zuul作为网关,主要实现都包含在了ZuulFilter的实现当中。以一个ThreadLocal实现的RequestContext来传递节点数据。如果想做一些自定义的处理可以通过实现ZuulFilter。

原文地址:https://www.cnblogs.com/lay2017/p/12045151.html

时间: 2024-10-17 10:46:52

一、zuul如何路由到上游服务器的相关文章

Spring Cloud(Dalston.SR5)--Zuul 网关-路由配置

Spring Cloud 在 Zuul 的 routing 阶段实现了几个过滤器,这些过滤器决定如何进行路由工作. 简单路由(SimpleHostRoutingFilter) 该过滤器运行后,会将 HTTP 请求全部转发到"源服务器",简单路由的配置如下: #zuul路由配置 zuul: routes: #表示http://localhost:9100/person/speaks地址,路由到http://localhost:8080/speaks person: path:/perso

Nginx自定义模块编写:根据post参数路由到不同服务器

Nginx自定义模块编写:根据post参数路由到不同服务器 2014-05-05 15:27 blogread IT技术博客 字号:T | T Nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器,然而当我们需要根据http包体来进行请求路由时,Nginx默认的配置规则就捉襟见肘了,但是没关系,Nginx提供了强大的自定义模块功能,我们只要进行需要的扩展就行了. 我们来理一下思路,我们的需求是: Nginx根据http包体的参数,来选择合适的路由 在这之前,我们先来考虑另

Nginx之Memcached上游服务器

Memcached Memcached是高性能的分布式内存缓存服务器.一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度.提高可扩展性. Nginx中Memcached模块 在Nginx中, Memcached模块默认是开启的,它负责与memcached守护进程通信.因此,客户端和memcached守护进程之间没有直接通信,也就是说,在这种情况下,Nginx不再是充当反向代理.memcached模块使得Nginx可以使用memcached协议会话,因此,ke

Nginx之优雅处理上游服务器错误码

当上游服务器无法响应请求时,便会返回50x状态码,这里我们可以进行错误优化处理,会显示的更加友好. 示例如下: # 方法1:从本地磁盘提供错误页面 server { error_page 500 502 503 504 /50x.html; location =/50x.html { root html; } # 方法2:从外部网站提供错误页面 server { error_page 500 http://www.test.com/50x.html; } 若这里为代理到一组上游服务器的话,也可以

Nginx 反向代理如何连接上游服务器

92 想上游服务器先建立TCP连接 如三次握手 下面指令可以控制握手时间 proxy_next_upstream  指令当出现502可以换个上游服务器 Tcp keepalive 一般都是由进程在操作的  主要降低性能消耗 默认是off proxy_bind 应用场景 一般有两个 当Nginx上有多个IP地址时,不要使用系统默认IP地址 而主动使用某个IP地址 透传IP地址 原文地址:https://www.cnblogs.com/jackey2015/p/10431020.html

Nginx 当上游服务器返回失败时的处理办法

95 Syntax: proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 |http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...; Default: proxy_next_upstream error timeout; Context: http, server, location 生效前提:没

Spring Cloud之搭建动态Zuul网关路由转发

传统方式将路由规则配置在配置文件中,如果路由规则发生了改变,需要重启服务器.这时候我们结合上节课内容整合SpringCloud Config分布式配置中心,实现动态路由规则. 将yml的内容粘贴到码云上: ###注册 中心 eureka: client: serviceUrl: defaultZone: http://localhost:8100/eureka/ server: ##api网关端口号 port: 80 ###网关名称 spring: ##网关服务名称 application: n

nginx自定义模块编写-根据post参数路由到不同服务器

nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器,然而当我们需要根据http包体来进行请求路由时,nginx默认的配置规则就捉襟见肘了,但是没关系,nginx提供了强大的自定义模块功能,我们只要进行需要的扩展就行了. 我们来理一下思路,我们的需求是: nginx根据http包体的参数,来选择合适的路由 在这之前,我们先来考虑另一个问题: 在nginx默认配置的支持下,能否实现服务器间的跳转呢?即类似于状态机,从一个服务器执行OK后,跳转到另一台服务器,按照规则依次传递

Spring Cloud Zuul 实现路由,获取参数

Spring Cloud Zuul作为网关,可实现请求验证过滤,路由到具体服务的作用. 举例: 比如请求订单接口,报文包括验证信息及具体请求参数. 通过配置路由到具体接口,通过参数调用接口,返回具体报文. 参数传递: 1.post请求,String类型json 2.接口获取@ResponseBody,将获取到String转json,再获取详细参数信息 验证:继承ZuulFilter类,重写run方法,调用具体验证服务,验证通过返回true.