SpringCloud- 第十二篇 Zuul概念及原理(一)
1:Zuul是什么
Zuul是Netflix开源的API网关。
API网关,类似于面向对象设计模式中的Facade模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤,它除了要实现请求路由,负载均衡,来源合法性检测,权限校验,反爬虫等功能之外,还需要更多能力,比如与服务治理框架的结合,请求转发时的熔断机制,服务的聚合等一系列的高级功能。
官网:https://github.com/Netflix/zuul/
2:Zuul能干什么
- 身份验证和安全 - 识别每个资源的身份验证要求,并拒绝不满足的请求
- 审查和监测 - 跟踪边缘的有意义的数据和统计数据,以便我们准确地了解生产运行情况
- 动态路由 - 根据需要将请求动态路由到不同的后端集群
- 压力测试 - 逐渐增加到集群的流量,以衡量性能
- 负载分配 - 为每种类型的请求分配容量并删除超出限制的请求
- 静态响应处理 - 直接在边缘构建一些响应,而不是将它们转发到内部集群
3: 流程图
4:Zuul有什么
Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础
而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础
然而实际上,路由功能在真正运行时,它的路由映射和请求转发都是由几个不同的过滤器完成的。其中,路由映射主要通过pre类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是由route类型的过滤器来完成,对pre类型过滤器获得的路由地址进行转发
5:HelloWorder
- 加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
- 配置示例,注意serviceId需要全小写
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
server.port=8780
spring.application.name=service-zuul
zuul.routes.api-a.path=/aa/**
zuul.routes.api-a.serviceId=userservice
zuul.routes.api-b.path=/bb/**
zuul.routes.api-b.serviceId=service-feign
- 启动类加入注解
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
1:@EnableZuulProxy开启Zuul服务,将本地调用转发到相应的服务,Zuul使用Ribbon来定位一个要转发到的服务实例,并且所有请求都以 hystrix命令执行
2:注意@EnableZuulProxy不包括发现服务的客户端,因此对于基于服务ID的路由,还需要提供服务治理(例如Eureka)
3:使用@EnableZuulServer也可以运行不带代理的Zuul服务器,差别是它不会自动添加任何代理过滤器,也没有服务发现和代理
- 测试运行
1:如果配置文件里面没有配置zuul的话,zuul会把Eureka Server里面的服务都读取出来,此时运行的时候,需要用各个服务的serviceId做为ContextPath
2:如果配置了zuul,还可以用配置的前缀代替服务的serviceId,然后进行访问
3:如果不希望直接使用serviceId进行访问,可以配置ignored-services: '’,例如:zuul.ignored-services='’,当然可以不用*,而是直接把要禁用的服务的serviceId罗列出来,用逗号分开
6:传统路由配置
所谓的传统路由配置方式就是在不依赖于服务发现机制的情况下,通过在配置文件中具*定每个路由表达式实例的映射关系来实现api网关对外部请求的路由。
- 单实例配置:
通过zuul.routes.<route>.path与zuul.routes.<route>.url参数对的方式进行配置,如:
zuul.routes.cc.path=/cc/**
zuul.routes.cc.url=http://localhost:8769
- 多实例配置
通过zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数的方式进行配置,结合Ribbon来做负载均衡,且让Ribbon关闭和Eureka的结合,比如:
zuul.routes.cc.path=/cc/**
#zuul.routes.cc.url=http://localhost:8769
zuul.routes.cc.serviceId=ccservice
ribbon.eureka.enabled=false
ccservice.ribbon.listOfServers=http://localhost:8769/,http://localhost:8768/
这种简单的URL路由不会被执行为HystrixCommand,也不能使用Ribbon对多个URL进行负载平衡
7:服务路由配置
spring cloud zuul整合了spring cloud eureka,实现了对服务实例的自动化维护,那么使用服务路由配置的时候,不需要像传统路由配置方式那样为serviceId指定具体服务实例地址,只需要通过zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数对的方式进行配置即可。
还可以简化配置,直接设置<route>为具体的serviceId,例如:
zuul.routes.service-feign.path=/dd/**
请记得开启前面示例中关闭的Ribbon和Eureka的结合。
8:服务路由的默认规则
大部分的路由规则机会都会采用服务名作为外部请求的前缀,zuul已经自动的帮我们实现以服务名作为前缀的映射,不需要去配置它。
如果不希望外部能直接访问,可以使用zuul.ignored-services参数来设置一个服务名匹配表达式来定义不自动创建路由的规则。
9:自定义路由映射关系
有时候,为了兼容外部不同版本的客户端程序,我们需要在微服务接口上定义版本的标记,比如:service-v1、 service-v2,而这样在映射的时候不太好管理。
通常的做法是为这些不同版本的微服务应用,生成以版本号作为路由前缀定义规则的路由规则,比如/v1/userservice/,具体示例如下:
@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)","${version}/${name}");
}
路径匹配
在zuul中,路由匹配的路径表达式采用ant风格定义:
1:? 匹配任意单个字符
2:* 匹配任意数量的字符
3:** 匹配任意数量的字符,支持多级目录
注意:当存在多个匹配的路由规则时,匹配结果完全取决于路由规则的定义顺序
由于properties的配置内容无法保证有序,所以当出现这样的情况的时候,为了保证路由的优先顺序,需要使用yml文件来配置,以实现有序的路由规则
10:cookie与头信息
默认情况下,spring cloud zuul在请求路由时,会过滤掉http请求头信息中一些敏感信息,防止它们被传递到下游的外部服务器。
默认的敏感头信息通过zuul.sensitiveHeaders参数定义,默认包括cookie,set-Cookie,authorization三个属性。
这会引发一个常见的问题,使用了spring security,shiro等安全框架构建的web应用,通过spring cloud zuul构建的网关来进行路由时,由于cookie信息无法传递,web应用将无法实现登录和鉴权。为了解决这个问题,配置的方法可以为:
1:通过设置全局参数为空来覆盖默认值,如:
zuul.sensitiveHeaders=
不推荐这种做法,范围太大
2:将指定路由的敏感头设置为空,如:
zuul.routes.userservice.sensitiveHeaders=
11:重定向问题
使用API网关的一个重要原因就是将网关作为统一入口,从而不暴露所有内部服务细节。但我们在应用内部跳转的url却是具体web应用实例的地址,而不是通过网关的路由地址,该问题的根本原因在于spring cloud zuul在路由请求时,并没有将最初的host信息设置正确,怎么办呢?
配置zuul.add-host-header=true即可
12:路由回退
12.1:Hystrix和ribbon支持
- Spring Cloud Zuul默认集成了Hystrix和Ribbon,自然就拥有线程隔离和断路器,以及对服务调用的客户端负载均衡功能。 需要注意的是,当使用path与url的映射关系来配置路由规则的时候,对于路由转发的请求不会采用hystrixCommand来包装,所以这类请求没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。因此,我们在使用zuul的时候尽量使用path和serviceId的组合来进行配置,这样不仅可以保证api网关的健壮和稳定,也能用到ribbon的客户端负载均衡功能。
- 所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)为SEMAPHORE
12.2:Hystrix的路由回退
- 可以通过创建ZuulFallbackProvider类型的bean来提供回退响应
- zuul首先会去判断是否存在自定义的zuulFallbackProvider,如果有,那么直接回调你自定义实现类的fallbackResponse()方法。如果不存在会走hystrix的fallback逻辑(有可能直接抛出异常)
- 虽然Zuul提供了降级的回调方法fallbackResponse(),但是这个方法是无参的,也就是说此时虽然你能够给调用端返回一个消息,但是此时你并不知道发生了什么样的异常(也就是说在这里你是获取不到异常信息的)。
13:请求路由其它配置
- 路由前缀
zuul.prefix:为路由规则增加前缀,例如: zuul.prefix=/user。
zuul.strip-prefix(默认为true):这个设置是在转发请求之前,从请求中删除代理前缀 - 本地跳转
通过在serviceId中使用forward来指定需要跳转的服务器资源路径,例如:
zuul.routes.api-t.path=/tt/**
zuul.routes.api-t.serviceId=forward:/tt - 忽略表达式
zuul.ignored-patterns:设置不希望被api网关进行路由的url表达式。例如:
ignoredPatterns: //products/
注意:它的范围并不是针对某个路由,而是对所有路由,所以要小心使用。 - 路由端点
使用@EnableZuulProxy,将启用/routes端点,可返回映射路由的列表。
可以通过将endpoints.routes.enabled设置为false来禁用此端点 - 配置属性
zuul.max.host.connections已被两个新属性zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections替换,分别默认为200和20 - 性能优化
Zuul内部路由可以理解为使用一个线程池去发送路由请求,所以我们也需要扩大这个线程池的容量,如:
zuul.host.max-per-route-connections=1000
zuul.host.max-total-connections=1000