一、什么是API网关
在微服务架构中,通常会有多个服务提供者。假设一个电商平台,可能会有商品、订单、支付、用户、物流等多个类型的微服务,而每个类型的微服务数量也会随着整个系统体量的增大也会随之增长和变更。作为UI端,在展示页面时可能需要从多个微服务中聚合数据,而且服务的划分位置结构可能会有所改变。网关就可以对外暴露聚合API,屏蔽内部微服务的微小变动,保持整个系统的稳定性。
当然这只是网关众多功能中的一部分,它还可以做负载均衡,统一鉴权,协议转换,监控监测等一系列功能。
二、Zuul网关简介(Zuul是做什么的)
Zuul是Spring Cloud全家桶中的微服务API网关。
在使用网关之前,动态的路由是通过Nginx的配置来做的,一旦发生改变,比如IP地址发生改变,加入其它路由,就要重新配置Nginx并重启Nginx。安全认证是放在每一个微服务中,这样就会导致服务中包含了非业务强相关的内容,看起来也是不够优雅。我接触到的限流也是放在Nginx配置的。
所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul底层利用各种filter实现如下功能:
- 动态路由:APP、web网站通过zuul来访问不同的服务提供方,且与ribbon结合,还可以负载均衡的路由到同一个应用不同的实例中。
- 安全认证:zuul作为互联网服务架构中的网关,可以用来校验非法访问、授予token、校验token等。识别每个需要认证的资源,拒绝不符合要求的请求。
- 限流:zuul通过记录每种请求的类型来达到限制访问过多导致服务down掉的目的。
- 静态响应处理:直接在zuul就处理一些请求,直接在边界返回某些响应的内容,不转发到微服务内部。
在Netflix的使用中,zuul也结合了Netflix的其他微服务组件一起使用。
- Hystrix:用来服务降级及熔断
- Ribbon:用来作为软件的负载均衡,微服务之间相互调用是通过ribbon作为软件负载均衡使用负载到微服务集群内的不同的实例
- Feign:用作微服务之间发送rest请求的组件,可以将rest调用类似spring的其他bean一样直接注入使用功能
- Eureka:服务注册中心
zuul的核心逻辑都是由一系列filter过滤器链实现的,但是filter的类型不同,执行的时机也不同,效果自然也不一样,主要特点如下:
-
filter的类型:filter的类型,决定了它在整个filter链中的执行顺序,可能在端点路由前执行,也可能在端点路由时执行,还有可能在端点路由后执行,甚至是端点路由发生异常时执行。
-
filter的执行顺序:同一种类型的filter,可以通过filterOrder()方法设置执行顺序,一般都是根据业务场景自定义filter执行顺序。
-
filter执行条件:filter运行所需的标准,或条件。
-
filter执行效果:符合某个filter执行条件,产生执行效果。
Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用。
下面有几种标准的过滤器类型:
Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
(1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
(2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
(3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
(4) ERROR:在其他阶段发生错误时执行该过滤器。
ZuulServlet 是Netflix Zuul 的全局入口,主要代码如下:
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
ZuulServlet 依次执行了 pre过滤器、route过滤器、post过滤器;当产生错误时,会执行error 过滤器。另外还有custom,自定义过滤器。
在zuul1.0时候客户端发起的请求后需要同步等待zuul网关返回,zuul网关这边对每个请求会分派一个线程来进行处理,这会导致并发请求数量有限。而zuul2.0使用netty作为异步通讯,可以大大加大并发请求量。