1.3 微服务网关层的功能
请求鉴权
发布商品,登录鉴权
数据完整性检查
数据包定长 Header+变成Body
协议转换
JSON -> HashMap(String, Object)
解析 app 的request参数时,在需要 rpc 调用服务接口时,需要将文本 request 参数转为 map 参数使用 rpc。
路由转发
根据 CMD 转发到不同业务逻辑层。对于 HTTP 请求,cmd 就是 url。
服务治理
限流、降级、熔断等。
2 常用网关方案
3 Zuul 的特点
Zuul 网关是具体核心业务服务的看门神,相比具体实现业务的系统服务来说它是一个边缘服务,主要提供动态路由,监控,弹性,安全性等功能。在分布式的微服务系统中,系统被拆为了多套系统,通过zuul网关来对用户的请求进行路由,转发到具体的后台服务系统中。
路由+过滤器= Zuul
核心是一系列的过滤器
3.1 四种过滤器
在zuul中过滤器分为四种:
- PRE Filters(前置过滤器)
- 当请求会路由转发到具体后端服务器前执行的过滤器,比如鉴权过滤器,日志过滤器,还有路由选择过滤器
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服务器会开启一个线程定时去轮询被修改或者新增的过滤器,然后动态编译,加载到内存,然后等后续有请求进来,新增或者修改后的过滤器就会生效了。
3.3 请求生命周期
Zuul接收到请求后:
Pre事前
请求被路由之前调用首先由前置过滤器处理
身份验证
Routing事中
由路由过滤器具体地把请求转发到后端应用微服务
Apache HttpClient 或 Netflix Ribbon 请求微服务
Post事后
远程调用后执行
再执行后置过滤器把执行结果写回请求方
HTTP Header、收集统计信息和指标、Response
Error错误时
当上面任何一个类型过滤器出错时执行
3.4 核心处理流程 - ZuulServlet类
在Zuul1.0中最核心的是ZuulServlet类,该类是个servlet,用来对匹配条件的请求执行核心的 pre, routing, post过滤器。
- 该类核心时序图
上图可知当请求过来后,先后执行了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看如何执行过滤器的: