SpringCloud微服务实战(十一)-微服务网关及其实现原理(Zuul为例讲解)(中)

1.3 微服务网关层的功能

请求鉴权

发布商品,登录鉴权

数据完整性检查

数据包定长 Header+变成Body

协议转换

JSON -> HashMap(String, Object)

解析 app 的request参数时,在需要 rpc 调用服务接口时,需要将文本 request 参数转为 map 参数使用 rpc。

路由转发

根据 CMD 转发到不同业务逻辑层。对于 HTTP 请求,cmd 就是 url。

服务治理

限流、降级、熔断等。

2 常用网关方案

SpringCloud微服务实战(十一)-微服务网关及其实现原理(Zuul为例讲解)(中)

3 Zuul 的特点

SpringCloud微服务实战(十一)-微服务网关及其实现原理(Zuul为例讲解)(中)

Zuul 网关是具体核心业务服务的看门神,相比具体实现业务的系统服务来说它是一个边缘服务,主要提供动态路由,监控,弹性,安全性等功能。在分布式的微服务系统中,系统被拆为了多套系统,通过zuul网关来对用户的请求进行路由,转发到具体的后台服务系统中。


路由+过滤器= Zuul

核心是一系列的过滤器

3.1 四种过滤器

在zuul中过滤器分为四种:

  1. PRE Filters(前置过滤器)
  2. 当请求会路由转发到具体后端服务器前执行的过滤器,比如鉴权过滤器,日志过滤器,还有路由选择过滤器

ROUTING Filters (路由过滤器)

一般通过Apache HttpClient 或者 Netflix Ribbon把请求具体转发到后端服务器

POST Filters(后置过滤器)

当把请求路由到具体后端服务器后执行的过滤器;场景有添加标准http 响应头,收集一些统计数据(比如请求耗时等),写入请求结果到请求方等

ERROR Filters(错误过滤器)

当上面任何一个类型过滤器执行出错时候执行该过滤器

3.2 架构图

Zuul网关核心 Zuul Core本质上就是 Web Servlet,一系列过滤器,用于过滤请求或响应结果。

Zuul 提供一个框架可支持动态加载,编译,运行这些过滤器,这些过滤器通过责任链方式顺序处理请求或者响应结果。过滤器之间不会直接通信,但可通过责任链传递的RequestContext(ThreadLocal 线程级别缓存)参数共享一些信息。


虽然 Zuul 支持任何可以在JVM上跑的语言,但zuul的过滤器只能使用Groovy编写。编写好的过滤器脚本一般放在zuul服务器的固定目录,zuul服务器会开启一个线程定时去轮询被修改或者新增的过滤器,然后动态编译,加载到内存,然后等后续有请求进来,新增或者修改后的过滤器就会生效了。

SpringCloud微服务实战(十一)-微服务网关及其实现原理(Zuul为例讲解)(中)

3.3 请求生命周期

SpringCloud微服务实战(十一)-微服务网关及其实现原理(Zuul为例讲解)(中)

Zuul接收到请求后:


Pre事前

请求被路由之前调用首先由前置过滤器处理

身份验证

Routing事中

由路由过滤器具体地把请求转发到后端应用微服务

Apache HttpClient 或 Netflix Ribbon 请求微服务

Post事后

远程调用后执行

再执行后置过滤器把执行结果写回请求方

HTTP Header、收集统计信息和指标、Response

Error错误时

当上面任何一个类型过滤器出错时执行

3.4 核心处理流程 - ZuulServlet类

在Zuul1.0中最核心的是ZuulServlet类,该类是个servlet,用来对匹配条件的请求执行核心的 pre, routing, post过滤器。

  • 该类核心时序图

SpringCloud微服务实战(十一)-微服务网关及其实现原理(Zuul为例讲解)(中)

上图可知当请求过来后,先后执行了FilterProcessor管理的三种过滤器:

public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            ...
            try {
            // 1 前置过滤器
                preRoute();
            } catch (ZuulException e) {
                // 1.1 错误过滤器
                error(e);
                // 1.2 后置过滤器
                postRoute();
                return;
            }
            try {
                // 2 路由过滤器
                route();
            } catch (ZuulException e) {
                // 2.1 错误过滤器
                error(e);
                // 2.2 后置过滤器
                postRoute();
                return;
            }
            try {
                // 3 后置过滤器
                postRoute();
            } catch (ZuulException e) {
                // 3.1 错误过滤器
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
        }
    }

如果在三种过滤器执行过程中发生了错误,会执行error(e),该方法执行错误过滤器,注意如果在pre、route过滤器执行过程中出现错误,在执行错误过滤器后还需再执行后置过滤器。

FilterProcessor#runFilters

执行具体过滤器:

public Object runFilters(String sType) throws Throwable {
        ...
        boolean bResult = false;
        // 2 获取sType类型的过滤器
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                // 2.1具体执行过滤器
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }

代码2是具体获取不同类型的过滤器

代码2.1是具体执行过滤器

我们看下代码2看FilterLoader类的getFiltersByType方法如何获取不同类型的过滤器:

FilterLoader#getFiltersByType

 public List<ZuulFilter> getFiltersByType(String filterType) {

        // 3 分类缓存是否存在
        List<ZuulFilter> list = hashFiltersByType.get(filterType);
        if (list != null) return list;

        list = new ArrayList<ZuulFilter>();
        // 4 获取所有过滤器
        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
        // 5 保存到分类缓存
        hashFiltersByType.putIfAbsent(filterType, list);
        return list;
    }
    private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();

代码3首先看分类缓存里是否有该类型过滤器,有直接返回

否则执行代码4获取所有注册过滤器,然后从中过滤出当前需要类别

然后缓存到分类过滤器

看到这里想必大家知道FilterRegistry类是存放所有过滤器的类,FilterRegistry里面 :

private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();存放所有注册的过滤器,那么这些过滤器什么时候放入的那?这个我们后面讲解。


这里我们剖析了如何获取具体类型的过滤器的,下面回到代码2.1看如何执行过滤器的:

 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 {
            ...
            // 5 执行过滤器
            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);
                    
                    break;
                default:
                    break;
            }
            
            if (t != null) throw t;

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

        } catch (Throwable e) {
          ...
        }
    }
上一篇:Linux 独立启动方式安装 Archiva


下一篇:flowable 控制台打印出自带表的 sql 语句