一、Zuul简介
- zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
- Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
- Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
- zuul的例子可以参考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文档说明来进行使用。
- Spring Cloud Zuul路由是微服务架构的不可或缺的一部分,提供动态路由,监控,弹性,安全等的边缘服务。
- Spring Cloud Zuu默认和Ribbon结合实现了负载均衡的功能。
二、zuul的工作原理
zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
zuul把Request route到 用户处理逻辑 的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等。
com.netflix.zuul.http.ZuulServlet是ZuulFilter链执行的入口,通过service方法,提取请求到RequestContext,然后通过调用ZuulRunner,依次按顺序执行每种类型的Filter,完成整个Filter的生命周期,架构图如下所示。
在zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。
执行流程图
Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
(1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
(2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
(3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
(4) ERROR:在其他阶段发生错误时执行该过滤器。
功能:
- 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
- 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
- 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
- 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
- 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
- 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
- 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
ZuulServlet - 处理请求(调度不同阶段的filters,处理异常等)
ZuulServlet
类似SpringMvc的DispatcherServlet
,所有的Request都要经过ZuulServlet
的处理- 三个核心的方法
preRoute()
,route()
,postRoute()
,zuul对request处理逻辑都在这三个方法里 ZuulServlet
交给ZuulRunner
去执行。- 由于
ZuulServlet
是单例,因此ZuulRunner
也仅有一个实例。 ZuulRunner
直接将执行逻辑交由FilterProcessor
处理,FilterProcessor
也是单例,其功能就是依据filterType执行filter的处理逻辑FilterProcessor
对filter的处理逻辑。
- 首先根据Type获取所有输入该Type的filter,
List<ZuulFilter> list
。 - 遍历该list,执行每个filter的处理逻辑,
processZuulFilter(ZuulFilter filter)
RequestContext
对每个filter的执行状况进行记录,应该留意,此处的执行状态主要包括其执行时间、以及执行成功或者失败,如果执行失败则对异常封装后抛出。- 到目前为止,zuul框架对每个filter的执行结果都没有太多的处理,它没有把上一filter的执行结果交由下一个将要执行的filter,仅仅是记录执行状态,如果执行失败抛出异常并终止执行。
zuul的原理及生命周期深入解析:https://www.cnblogs.com/lexiaofei/p/7080257.html
源码解析:
https://blog.csdn.net/forezp/article/details/76211680(可以增加做日志处理)
http://xujin.org/sc/sc-zuul-s2/
三、zuul入门使用
1、添加依赖
引入spring-cloud-starter-zuul
包
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
2、配置文件
应用名、服务端口等
spring.application.name=gateway-service-zuul
server.port=8888
#路由
#这里的配置表示,访问/it/** 直接重定向到http://www.baidu.com/**
zuul.routes.api-a-url.path=/it/**
zuul.routes.api-a-url.url=http://www.baidu.com/
服务路由:
在Zuul中提供了两种映射方式:
方式一:通过url直接映射,上面配置就是
api-a-url部分为路由的名字,可以任意定义,但是一组映射关系的path和url要相同
方式二:服务化的方式(使用serviceId的映射方式)(推荐)
通过url映射的方式对于Zuul来说,并不是特别友好,Zuul需要知道我们所有为服务的地址,才能完成所有的映射配置。而实际上,我们在实现微服务架构时,服务名与服务实例地址的关系在eureka server中已经存在了,所以只需要将Zuul注册到eureka server上去发现其他服务,我们就可以实现对serviceId的映射
需要添加spring-cloud-starter-eureka
依赖
修改配置:
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
针对我们在准备工作中实现的两个微服务service-A和service-B,定义了两个路由api-a和api-b来分别映射。另外为了让Zuul能发现service-A和service-B,也加入了eureka的配置。
这两种方式推荐使用serviceId的映射方式,除了对Zuul维护上更加友好之外,serviceId映射方式还支持了断路器,对于服务故障的情况下,可以有效的防止故障蔓延到服务网关上而影响整个系统的对外服务
3、启动类
启动类添加@EnableZuulProxy
,支持网关路由。
@SpringCloudApplication @EnableZuulProxy public class GatewayServiceZuulApplication { public static void main(String[] args) { SpringApplication.run(GatewayServiceZuulApplication.class, args); } }
注:1、这里用了@SpringCloudApplication
注解,之前没有提过,通过源码我们看到,它整合了@SpringBootApplication
、@EnableDiscoveryClient
、@EnableCircuitBreaker
,主要目的还是简化配置。
@EnableCircuitBreaker:
启动断路器
2、@EnableZuulProxy简单理解为@EnableZuulServer的增强版,当Zuul与Eureka、Ribbon等组件配合使用时,我们使用@EnableZuulProxy。
4、服务过滤
一、在服务网关中定义过滤器只需要继承ZuulFilter
抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。
public class AccessFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(AccessFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("accessToken"); if(accessToken == null) { log.warn("access token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); return null; } log.info("access token ok"); return null; } }
filterType
:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:pre
:可以在请求被路由之前调用routing
:在路由请求时候被调用post
:在routing和error过滤器之后被调用error
:处理请求时发生错误时被调用
filterOrder
:通过int值来定义过滤器的执行顺序shouldFilter
:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。run
:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)
令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)
设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)
对返回body内容进行编辑等。
Zuul中默认实现的Filter
类型 | 顺序 | 过滤器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
pre | -1 | FormBodyWrapperFilter | 包装请求体 |
route | 1 | DebugFilter | 标记调试标志 |
route | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
route | 10 | RibbonRoutingFilter | serviceId请求转发 |
route | 100 | SimpleHostRoutingFilter | url请求转发 |
route | 500 | SendForwardFilter | forward请求转发 |
post | 0 | SendErrorFilter | 处理有错误的请求响应 |
post | 1000 | SendResponseFilter | 处理正常的请求响应 |
禁用指定的Filter
可以在application.yml中配置需要禁用的filter,格式:
zuul: FormBodyWrapperFilter: pre: disable: true
二、在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,我们只需要在应用主类中或者在配置类中配置:
@Configuration public class MyZuulFilterConfig { @Bean public AccessFilter accessFilter() { return new AccessFilter(); } }
启动该服务网关后,访问:
http://localhost:5555/api-a/add?a=1&b=2
:返回401错误http://localhost:5555/api-a/add?a=1&b=2&accessToken=token
:正确路由到server-A,并返回计算内容
四、服务网关的重要性
- 不仅仅实现了路由功能来屏蔽诸多服务细节,更实现了服务级别、均衡负载的路由。
- 实现了接口权限校验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
- 实现了断路器,不会因为具体微服务的故障而导致服务网关的阻塞,依然可以对外服务。
参考:http://blog.didispace.com/springcloud5/
http://www.ityouknow.com/springcloud/2017/06/01/gateway-service-zuul.html