Spring Cloud Zuul
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。
Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
在Spring Cloud体系中, Spring Cloud Zuul 封装了Zuul组件,作为一个API网关,负责提供负载均衡、反向代理和权限认证。
Zuul工作机制
过滤器机制
Zuul的核心是一系列的filters, 其作用类似Servlet框架的Filter,Zuul把客户端请求路由到业务处理逻辑的过程中,这些filter在路由的特定时期参与了一些过滤处理,比如实现鉴权、流量转发、请求统计等功能。Zuul的整个运行机制,可以用下图来描述。
过滤器的生命周期
Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示。
基于Zuul的这些过滤器,可以实现各种丰富的功能,而这些过滤器类型则对应于请求的典型生命周期。
PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR:在其他阶段发生错误时执行该过滤器。
除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
Zuul中默认实现的Filter
Zuul默认实现了很多Filter,这些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
处理正常的请求响应
我们先看看Zuul的工作原理,Zuul是主要起路由转发和路由拦截作用的,这里直接说Zuul和Feign集成的工作流程如下:
新建一个Consumer服务消费模块,他的pom.xml添加如下依赖:
看看他的主程序:
再看看application.yml里的配置文件:
1eureka: 2 client: 3 serviceUrl: 4 defaultZone: http://localhost:8761/eureka/ 5 instance: 6 prefer-ip-address: true 7server: 8 port: 8768 9spring: 10 application: 11 name: service-ribbon 12zuul: 13 routes: 14 #标识你服务的名字,这里可以自己定义,一般方便和规范来讲还是跟自己服务的名字一样 15 service-feign: 16 #服务映射的路径,通过这路径就可以从外部访问你的服务了,目的是为了不爆露你机器的IP,面向服务的路由了,给你选一个可用的出来, 17 path: /service-feign/** 18 serviceId: service-feign
启动我们的注册中心服务、至少一个服务提供者、启动Feign服务消费者、启动我们的Zuul服务消费者,启动正常:
打开浏览器器,输入http://localhost:8768/service-feign/hello?name=111这是我们基于Feign的请求转发
打开浏览器器,输入http://localhost:8768/service-feign/hi?name=111这是我们基于RestTemplate的请求转发
可以看到,我们成功将我们的路由请求转发到Feign服务消费者模块上了,接下来我们要实现以下Zuul对请求的拦截和熔断处理,
拦截就是在转发之前或者之后当请求到达Feign之前做拦截处理,这个是很重要的功能,熔断是当我们路由转发请求的服务不可用该怎么处理响应呢?
一、路由请求拦截
1package com.xu.serviceconsumer.component; 2 3import com.netflix.zuul.ZuulFilter; 4import com.netflix.zuul.context.RequestContext; 5import org.slf4j.Logger; 6import org.slf4j.LoggerFactory; 7import org.springframework.stereotype.Component; 8 9import javax.servlet.http.HttpServletRequest; 10 11@Component 12public class MyTokenFilter extends ZuulFilter { 13 14 private Logger logger = LoggerFactory.getLogger(MyTokenFilter.class); 15 16 //四种类型:pre,routing,error,post 17 //pre:主要用在路由映射的阶段是寻找路由映射表的 18 //routing:具体的路由转发过滤器是在routing路由器,具体的请求转发的时候会调用 19 //error:一旦前面的过滤器出错了,会调用error过滤器。 20 //post:当routing,error运行完后才会调用该过滤器,是在最后阶段的 21 @Override 22 public String filterType() { 23 return "pre"; 24 } 25 26 //自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行 27 @Override 28 public int filterOrder() { 29 return 1; 30 } 31 32 //控制过滤器生效不生效,可以在里面写一串逻辑来控制 33 @Override 34 public boolean shouldFilter() { 35 return true; 36 } 37 38 //执行过滤逻辑 39 @Override 40 public Object run() { 41 42 RequestContext context = RequestContext.getCurrentContext(); 43 HttpServletRequest request = context.getRequest(); 44 String url = request.getRequestURL().toString(); 45 logger.info("请求URL:"+url); 46 String token = request.getParameter("name"); 47 if (token == null){ 48 context.setSendZuulResponse(false); 49 context.setResponseStatusCode(401); 50 context.setResponseBody("there is no request parameter name"); 51 52 53 return null; 54 } 55 return null; 56 } 57}
简单看一下代码知道这个过滤器继承了ZuulFilter,在run()方法里日志输出了请求的URL路径和对参数是否有name做了简单的拦截过滤,请求http://localhost:8768/service-feign/hello?
可以看到这个Zuul的路由过滤器成功的对这个请求拦截过滤了,最后看一下控制台信息也可以看到成功输出打印了请求的URL
二、路由熔断
1package com.xu.serviceconsumer.component; 2 3import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; 4import org.springframework.http.HttpHeaders; 5import org.springframework.http.HttpStatus; 6import org.springframework.http.MediaType; 7import org.springframework.http.client.ClientHttpResponse; 8import org.springframework.stereotype.Component; 9 10import java.io.ByteArrayInputStream; 11import java.io.IOException; 12import java.io.InputStream; 13 14@Component 15public class MyFallbackProvider implements FallbackProvider { 16 17 @Override 18 public String getRoute() { 19 return "service-feign"; 20 } 21 22 @Override 23 public ClientHttpResponse fallbackResponse(String route, Throwable cause) { 24 System.out.println("route:"+route); 25 System.out.println("exception:"+cause.getMessage()); 26 return new ClientHttpResponse() { 27 @Override 28 public HttpStatus getStatusCode() throws IOException { 29 return HttpStatus.OK; 30 } 31 32 @Override 33 public int getRawStatusCode() throws IOException { 34 return 200; 35 } 36 37 @Override 38 public String getStatusText() throws IOException { 39 return "ok"; 40 } 41 42 @Override 43 public void close() { 44 45 } 46 47 @Override 48 public InputStream getBody() throws IOException { 49 return new ByteArrayInputStream(("Sorry, the service "+getRoute()+" is unavailable now.").getBytes()); 50 } 51 52 @Override 53 public HttpHeaders getHeaders() { 54 HttpHeaders headers = new HttpHeaders(); 55 headers.setContentType(MediaType.APPLICATION_JSON); 56 return headers; 57 } 58 }; 59 } 60}
注意这里的一段代码
我们的熔断路由器配置的是service-feign(就是我们zuul请求路由的这个这个服务),我们关闭这个服务,保留其他之前开启的服务,请求http://localhost:8768/service-feign/hi?name=111
自此,我们可以
自此,说明我们自定义的熔断器已经起作用了,这课的内容到此为止,下回再见!
===============================================================================
如果您觉得此文有帮助,可以打赏点钱给我支付宝或扫描二维码

