现在还需要一个网关,也可以叫路由器,上连Nginx,下连各个注册中心,将前端发送到Nginx的请求分发到各个注册中心,这就是Zuul
Spring Cloud Zuul 是一套边缘服务,它能实现动态路由、监控、负载和流量管理等功能。
简单来说,Zuul就是服务应用端的一套负载均衡器,它由一个核心ZuulServlet和一系列的过滤器组成。
pre:前置过滤器。在请求被路由之前调用
routing: 路由过滤器。在前置调用完成之后,路由请求时被调用
error:处理请求时发生错误时被调用
post:在routing和error过滤器之后被调用
Origin Server :当前调用的服务
Zuul跟OpenFeign、Eureka一样,也是Spring Cloud Netflix的一员,版本与Netflix其他套件一致。
Zuul是单独的一个服务,需要重新创建一个新项目,除了加入Zuul相关依赖以外,还需要加入Config客户端和Eureka客户端依赖。下面为了便于学习,仅仅加入了注册中心,实际开发时应该需要把配置中心也加上
Zuul可以通过Config拉取配置文件,并注册至Eureka直接完成高可用,这个过程不需要任何配置信息。
将zuul注册到注册中心上
在下面的知识点中并没有使用配置中心,但是如果需要使用配置中心,只需要在springboot骨架创建项目时加入配置中心,然后按照下面的步骤调取配置文件
首先在yml调用配置中心
与配置中心客户端连接一样,连接配置中心,起名为api-config,同时在版本控制工具中上传对应的配置文件。配置文件可以参考Config示例创建同样的三个配置文件,并命名为api-config.yml、api-config-dev.yml、api-config-pro.yml。具体内容如下图:
在zuul的springboot启动类上添加@EnableZuulProxy注解,开启zuul代理
通过zuul服务地址+服务实例+接口地址 就可以完成请求转发的功能
服务地址就是zuul的url
服务实例就是业务模块在注册中心的名字
接口地址就是业务模块的controller方法设定的请求路径
测试如图:
流程如图:
当用户访问网关时,Zuul网关会将Eureka上所有可用服务列表拉取下来,根据用户访问路径先匹配应用ID,然后将请求路径转发给对应的应用服务
只需要在Nginx中配置多台zuul服务器,就可以实现zuul的高可用
Zuul启动后可以通过Zuul的url +业务模块在注册中心的名称+请求路径,对注册中心的业务模块进行访问,但这会暴露业务模块的名称,所以zuul提供了自定义映射的功能。
通过自定义映射实现路径转发,间接实现对服务实例名的隐藏,当然原始的实例名称仍然可以访问
属性说明:
routes 本质是一个map,key是一个string,value是一个zuulroute,包含id、path、serviceId,id默认为routes保存的key
user可以随便指定,只为区别节点,但不能重复,因为user就是routes 的key
path 自定义的路径
serviceId 希望通过自定义的路径访问的业务模块在注册中心的名称
实例:
配置如图
访问如图
通过实例名:映射路径 可以简洁的声明实例名和映射路径
其实就是重定向,用的比较少
path 请求路径,与上述其他定义一致
url 请求路径需要转发的连接
上面写的path可使用的通配符如下,常用**
通配符 | 说明 | 通配符示例 | 通配符示例说明 |
---|---|---|---|
? | 匹配任意单个字符 | /user/? | 可以匹配/user/后面拼接的任意一个字符的路径。如:/user/a |
* | 匹配任意数量的字符 | /user/* | 可以匹配/user/后面拼接的任意多个字符的路径。如:/user/123 |
** | 匹配任意数量的字符,支持多级目录 | /user/** | 可以匹配/user/后面拼接的任意多级目录路径。如:/user/photo/123 |
为了防止用户直接通过业务模块在注册中心的名字访问接口,可以通过zuul.ignored-services来禁用服务名访问。
如果需要单独限制,可以使用数组的方式声明业务模块在注册中心的名字。
首先建立自定义映射,然后使用“*”,这可以把所有默认映射都给禁止
此时访问如图
如图,写上业务模块在注册中心的名字即可,多个业务模块就写成数组的形式
Zuul的连接数,超时等常见配置在ZuulProperties的内部类Host类中,超时配置有两种情况
1. 当zuul使用了服务注册中心时,会有ribbon组件负责连接。开发者需要设置ribbon.ReadTimeout 和ribbon.SocketTimeout两项配置属性
2. 当使用了zuul节点和url映射时,需要配置zuul.host.connect-timeout-millis和zuul.host.socket-timeout-milli属性
连接超时配置
ribbon超时配置
注意:如果配置了ribbon的超时时间,那么zuul的超时时间一定要大于ribbon的超时时间,否则远程调用组件的内部调用时间还没超过,外部zuul反倒先超时返回无响应,就不对了
在Zuul的架构图中可以看到,zuul有四层核心过滤器,分别是pre(前置)、routing(路由)、post(后置)和error(异常)过滤器。
开发者一般可以通过前置过滤器完成用户权限校验,后置过滤器完成响应结果过滤等功能。
主要的过滤器一共有10个,但是对应的过滤器都有自己的执行前置条件,所以,并不会因为过滤器过多而影响到性能。
可以不需要太关注这些过滤器,这些过滤器都是自动执行和使用的,我们只需要从这些过滤器的源码中学会如何写自定义过滤器
Filter名称 | Filter 的作用 |
---|---|
Pre 前置过滤器 | |
ServletDetectionFilter | 判定当前请求是通过SpringMVC还是ZuulServlet来执行的 |
Servlet30WrapperFilter | 将request对象进行二次包装 |
FormBodyWrapperFilter | 文件上传时,对文件对象封装的过滤器 |
DebugFilter | 调试过滤器,激活调试日志 |
PreDecorationFilter | 查找路由 |
Routing过滤器 | |
RibbonRoutingFilter | 对通过serviceId路由访问的请求进行负载访问 |
SimpleHostRoutingFilter | 对通过url路由访问的请求进行直接访问 |
SendForwardFilter | 对请求上下文中的forward.do参数进行处理请求 |
Post过滤器 | |
SendErrorFilter | 在上下文中出现错误时处理 |
SendResponseFilter | 使用当前上下文中的参数对访问端进行响应 |
基于Servlet30WrapperFilter过滤器,通过查看源码,可以看到过滤器要继承ZuulFilter这个抽象类
接着这个过滤器要实现四个方法,这四个方法如图
这四个方法从上往下分别是
如果我们希望自定义一个过滤器,只需要照着内置过滤器抄一个,模板代码如下
@Component public class SimpleFilter extends ZuulFilter { @Override public String filterType() { //定义当前过滤器类型。具体过滤器类型详见org.springframework.cloud.netflix.zuul.filters.support.FilterConstants return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { //当前过滤器的顺序,值越小越靠前 return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { //true/false表示是否执行run方法 return true; } @Override public Object run() throws ZuulException { //过滤器的具体业务 return null; } }
说明:
自定义的Filter需要继承ZuulFilter父类,并添加@Component注解
ZuulFilter中定义了四个方法需要重写:
- filterType方法 : 返回当前过滤器类型。包含有pre、routing、post、error类型过滤器。具体可以参考FilterConstants类中的 *_type常量定义
- filterOrder : 返回当前过滤器的运行顺序,值越小,运行优先级越高。
值定义在FilterConstants 类中的*_order常量。
前置过滤器官方demo给的建议是运行在PreDecorationFilter过滤器之前。
后置过滤器官方demo给的建议是运行在SendResponseFilter之前。demo地址
- shouldFilter : 返回当前过滤器是否要执行run方法。无特殊情况时,一般返回true即可。
- run:过滤器的主要逻辑业务方法。该方法可直接返回null,不需要返回其他值。
RequestContext是什么
假如我们希望自定义一个过滤器用于判断前端传递的参数加密是否正确,或者用户是否登录,可以写一个pre前置过滤器
代码如下
@Component public class SessionFilter extends ZuulFilter { @Override public String filterType() { //定义当前过滤器类型。具体过滤器类型详见org.springframework.cloud.netflix.zuul.filters.support.FilterConstants return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { //当前过滤器的顺序,值越小越靠前 return FilterConstants.PRE_DECORATION_FILTER_ORDER -1; } @Override public boolean shouldFilter() { //true/false表示是否执行run方法 return true; } @Override public Object run() throws ZuulException { //zuul内置RequestContext对象,可获取当前请求的上下文 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String uid = request.getParameter("uid"); if(StringUtils.isEmpty(uid)){ //终止后续过滤器的执行 requestContext.setSendZuulResponse(false); //设置响应码 requestContext.setResponseStatusCode(HttpStatus.SC_BAD_REQUEST); requestContext.getResponse().setContentType("text/html;charset=utf-8"); requestContext.setResponseBody("无权限访问~"); }else{ //redis中查找用户id对应的token是否存在,判定当前用户是否有权限。 } return null; } }
filterType() 返回值定义为FilterConstants.****PRE_TYPE类型,将当前过滤器定义为前置过滤器
filterOrder()返回值按照官方的建议,filterOrder取值为 FilterConstants.PRE_DECORATION_FILTER_ORDER - 1
run() 主要负责过滤器的主业务逻辑
- RequestContext
Zuul内置的对象,通过该对象可以获取当前请求的Request和Response对象,就像servlet那样,获得了Request和Response就可以做任何事,比如获取所有请求参数。
- requestContext.setSendZuulResponse(false);
当设置为false时将会中断后续的过滤器执行
- requestContext.setResponseStatusCode(HttpStatus.SC_BAD_REQUEST );
当设置了SendZuulResponse为false时,需要设置当前响应的状态码,状态码来自于HttpStatus这个常量类
- requestContext.getResponse().setContentType("text/html;charset=utf-8");
设置文本类型,常用的就两个,记住就行
text/html和application/json
- requestContext.setResponseBody("error context");
设置当前错误信息的响应文本
除了创建pre前置过滤器以外,还可以按照FilterConstants类中的 *_type常量定义filterType方法的返回值来定义具体过滤器类型。
同时定义filterOrder的返回值来定义过滤器执行顺序。