Zuul的工作原理
Zuul的核心时一系列的过滤器Filter来实现的,zuul的所有功能都是基于过滤器去实现的。
Zuul Filter 的主要特点
- Filter的类型:Filter的类型决定了此Filter在Filter链中的执行顺序。可能是路由发生前,可能路由动作发生时,可能时路由动作发生后,也可能是在路由过程发生在异常时。
- Filter的执行顺序:统一类型的Filter可以通过filterOrder()方法来设定字定义的Filter的执行顺序。
- Filter的执行条件:Filter运行所需要的标准或条件
- Filter的执行效果:符合某个Filter的中条件,尝试的执行效果。
Zuul生命周期
Zuul一共有四种不同的生命周期的Filter,分别是:
-
pre:在Zuul按照规则路由到下级服务之前执行。如果需要对请求进行预处理,比如鉴权,限流等,都应该考虑在此类Filter实现。
-
route:这个类Filter是Zuul路由动作的执行者,是Apache HttpClient或Netflix Ribbon构建和发送原始HTTP请求的地方法,目前支持OkHttp。
-
post:这类Filter是在源服务返回结果或者异常信息发生后执行,如果需要对返回信息做一些处理,则在此类Filter进行处理。
-
error:在整个生命周期内如果发生异常,则会进入error Filter,可做全局异常处理。
在实际项目中,往往需要自实现以上类型的Filter来对请求链路进行处理,根据业务的需求,选取相应的生命周期的Filter来达成目的。在Filter之间,通过RequestContext类老进行通信,内部采用ThreadLocal保存你没给请求的一些信息,包括请求路由,错误信息,HttpServletRequest,HttpServletResponse,这使得一些操作时十分可靠的,它还扩张了ConcurrentHashMap,目的时为了在处理工程中保存各种形式的信息。
Zuul原生Filter
如果使用@EnableZuulProxy注解搭配Spring Boot Actuator,会看到管控的端点的信息,具体的配置如下:
添加依赖pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
复制代码
添加配置:
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字
复制代码
访问如下的地址可以看到:
名称 | 类型 | 次序 | 描述 |
---|---|---|---|
ServletDetectionFilter | pre | -3 | 通过Spring Dispatcher检查请求是否通过 |
Servlet30WrapperFilter | pre | -2 | 适配HttpServletRequest为Servlet30Wrapper对象 |
FormBodyWrapperFilter | pre | -1 | 解析表单数据为下游请求重新编译 |
DebugFilter | pre | 1 | Debug路由标识 |
PreDecorationFilter | pre | 5 | 处理请求上下文供后续使用,设置下游相关头信息 |
RibbonRoutingFilter | route | 10 | 使用Ribbon,Hystrix或者嵌入式HTTP客户端发送请求 |
SimpleHostRoutingFilter | route | 100 | 使用Apache的HttpClient发送请求 |
SendForwardFilter | route | 500 | 使用Servlet发送请求 |
SendResponseFilter | post | 1000 | 将代理请求的响应写入当前响应 |
SendErrorFilter | error | 0 | 如果RquestContext.getThrowable()不为空,则转发到error,path配置的路径 |
由于之前的例子的启动类使用了@EnbaleZuulProxy注解后安装的Filter,如果使用了@EnaleZuulServer将缺少PreDecorationFilter,RibbonRoutingFilter,SimpleHostRoutingFilter等过滤器
以上的过滤器类有可能并不一定能够满足我们的需要,所以我们可以采取替代的方式,将其源码覆盖,也可以采取禁用策略: zuul...disable=true,比如要禁用DebugFilter,只需要在配置文件添加如下的配置: zuul.DebugFilter.pre.disable=true.
实现自定义的Filter
在Zuul中实现自定义的Filter,我们只需要集成ZuulFilter类即可,ZuulFilter是一个抽象类,我们需要实现以下的几个方法:
filterOrder():使用返回值设定Filter执行次序。
filterType():使用返回值摄动Filter类型,可以是pre,route,post,error类型。
shouldFilter():使用返回值设定该Filter是否执行,可以作为开关使用。
run():Filter里面的核心执行逻辑,业务处理在此编写。
除了是上面的方法需要重写之外,还需要把创建好的过滤器加入配置中,我们可以像下面把配置的加入内存中
@Configuration
public class ZuulFilterConfiguration {
@Bean
public FirstPreFilter firstPreFilter() {
return new FirstPreFilter();
}
@Bean
public SencodPreFilter sencodPreFilter() {
return new SencodPreFilter();
}
@Bean
public ThirdPreFilter thirdPreFilter(){
return new ThirdPreFilter();
}
@Bean
public PostFilter postFilter(){
return new PostFilter();
}
@Bean
public ErrorFilter errorFilter(){
return new ErrorFilter();
}
}
复制代码
后台打印的日志如下:
Zuul限流
限流的算法主要有两种算法,分别如下:
-
漏桶算法:
漏桶算法(Leaky Bucket)是网络世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。
-
令牌算法:
令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。
显示限流中我们最常用的方式是定义Filter,然后加上上面的主要限流算法即可。在实际开发中我们可以使用别人现成的工具。spring-cloud-zuul-ratelimit,该框架主要用于提供Zuul的限流,该框架提供了很多细粒度的策略:
- user:认证用户名或者匿名,针对某个用户粒度进行限流
- origin:客户机ip,针对请求客户机ip粒度进行限流
- url:特定的url,针对某个请求url粒度进行限流
- serviceId:特定服务,针对某个服务id粒度进行限流
多种粒度临时变量存储方式
- IN_MEMEORY:基于本地内存,底层是ConcurrentHashMap
- REDIS:Redis的K/V存储
- CONSUL:Consul的K/V存储
- JPA:Spring Data JPA,基于数据库的存储
- BUKE4J:一个使用JAVA编写的基于令牌算法的限流库,主要有四种模式:JCache,Hazelcast,Apache Ignite,Inifinispan。
上面主要是一些关于spring-cloud-zuul-ratelimit的一些基本的只是,现实中需要怎么使用?
pom的依赖
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
复制代码
application.yml配置文件的配置
zuul:
ratelimit:
#按力度拆分的临时变量key的前缀
key-prefix: springcloud-book
#启动限流开关
enabled: true
#key的存储类型默认是IN_MEMORY本地存储,此外还有多种形式
repository: in_memory
#标识代理之后,可以单独细化到服务粒度
behind-proxy: true
#犬奴限流策略,可单独细化到服务粒度
default-policy:
#在一个单位时间窗口内的请求数量
limit: 2
#在一个单位时间窗口内的请求时间限制
quota: 1
#单位时间窗口
refresh-interval: 3
type:
- user
- origin
- url
复制代码
连续多次请求可以看到如下的结果:
Zuul的文件上传
Zuul的文件上传直接沿用了Springboot的那一套,所以配置的时候也可以想配置SpringBoot文件上传那样。
spring:
application:
name: zuul-server
servlet: #spring boot2.0之前是http
multipart:
enabled: true # 使用http multipart上传处理
max-file-size: 100MB # 设置单个文件的最大长度,默认1M,如不限制配置为-1
max-request-size: 100MB # 设置最大的请求文件的大小,默认10M,如不限制配置为-1
file-size-threshold: 1MB # 当上传文件达到1MB的时候进行磁盘写入
location: / # 上传的临时目录
##### Hystrix默认超时时间为1秒,如果要上传大文件,为避免超时,稍微设大一点
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 30000
复制代码
Zuul的饥饿加载
Zuul内部默认使用了Ribbon来调用远程服务,所以由于Ribbon的原因,在我们部署好所有的组华南之后,第一次经历zuul的调用往往会注册中心读取服务注册表,初始化Ribbon负载信息,这是一种懒加载的策略,但是这个过程是很耗时的,尤其是服务过多的时候,为了避免这个问题,我们可以在启动的Zuul的时候就姐加载应用程序上下文信息,开启饥饿加载我们只需要配置配置文件。
zuul:
ribbon:
eager-load:
enabled: true #开启饥饿加载
复制代码
重试机制
在Spring Cloud中有多种和发送Http请求的返回格式可以与Zuul结合,Ribbon,Feign或者RestTemplate,但是无论选择那种,都可以出现请求失败的情况,在复杂的网络中是无可避免的。Zuul作为uige网关中间件,在出现偶然请求失败的时候进行适当的调整是很有必要的,重试是可以有效避免一些特殊情况的引起的请求丢失的。
pom依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
复制代码
配置如下:
ribbon:
#重试机制配置
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 1 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
复制代码