Spring cloud zuul (高级)
4.1zuul的核心
Filter是Zuul的核心,用来实现对外服务的控制。Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,
4.2 zuul的生命周期
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
(1).PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
(2).ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
(3).POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。
(4).ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们 创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
4.3 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 | 处理正常的请求响应 |
4.4 禁用指定的Filter
可以在application.yml中配置需要禁用的filter,格式:
zuul:
FormBodyWrapperFilter:
pre:
disable: false4.5自定义filter示例
4.5.1 pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!--注意:这里必须要添加,否则各种依赖有问题-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>4.5.2 application.yml
server:
port: 8003
spring:
application:
name: zuul-gataway
zuul:
routes:
api-a:
path: /producer/**
serviceId: zuul-producer
FormBodyWrapperFilter:
pre:
disable: false
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka4.5.3main方法启动
@SpringBootApplication
@EnableZuulProxy
public class GateWayRuN {
public static void main(String[] args) {
SpringApplication.run(GateWayRuN.class, args);
}
@Bean
public MyFilter getFilter(){
return new MyFilter();
}
}4.5.4自定义filter
public class MyFilter extends ZuulFilter{
public boolean shouldFilter() {
// TODO Auto-generated method stub
return true;//表示是否需要执行该filter,true表示执行,false表示不执行
}
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;//定义filter顺序,数字越小表示顺序越高,越先执行
}
@Override
public String filterType() {
// TODO Auto-generated method stub
return "pre";//定义filter类型,有pre,route,post,error四种
}
//filter执行的具体操作
public Object run() {
// TODO Auto-generated method stub
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String parameter = request.getParameter("name");
if(StringUtils.isNotBlank(parameter)){
currentContext.setSendZuulResponse(true);//对请求进行路由
currentContext.setResponseStatusCode(200);
currentContext.set("issuccess", true);
return null;
}else{
currentContext.setSendZuulResponse(false);//对请求不进行路由
currentContext.setResponseStatusCode(400);
currentContext.setResponseBody("this is empty");
currentContext.set("issuccess", true);
return null;
}
}
}4.5.5测试验证
浏览器访问:
http://192.168.18.106:8003/producer/test?
返回:this is empty
http://192.168.18.106:8003/producer/test?name=’dsfa’
返回:’dsfa’,say hello1
说明已经进行了拦截。
4.6路由熔断
4.6.1 熔断使用场景
当我们的后端服务出现异常的时候,我们不希望将异常抛出给最外层,期望服务可以自动进行一降级。
Zuul给我们提供了这样的支持
当某个服务出现异常时,直接返回我们预设的信息。我们通过自定义的fallback方法,并且将其指定给某个route来实现该route访问出问题的熔断处理。
(1).主要继承ZuulFallbackProvider接口来实现,ZuulFallbackProvider默认有两个方法,一个用来指明熔断拦截哪个服务,一个定制返回内容。
(2).实现类通过实现getRoute方法,告诉Zuul它是负责哪个route定义的熔断。而fallbackResponse方法则是告诉 Zuul 断路出现时,它会提供一个什么返回值来处理请求。
注意:新版本,Springboot2.0以后,zuulFallbackProvider类没了,但是还可以使用FallbackProvider
4.6.2 熔断超时配置说明(ZUUL超时)
注意:使用serviceId路由和url路由是不一样的超时策略
4.6.2.1 ribbon超时配置
路由方式:serviceId的方式
ribbon.ReadTimeout
ribbon.SocketTimeout当在yml写时,应该是没有提示的,给人的感觉好像是不是这么配的一样,其实不用管它,直接配上就生效了。
4.6.2.2 ribbon超时配置
路由方式:url的方式
zuul.host.connect-timeout-millis
zuul.host.socket-timeout-millis4.6.2.3 熔断fallback相关的超时配置
如果在zuul配置了熔断fallback的话,熔断超时也要配置,不然你配置的ribbon超时时间大于熔断的超时,那么会先走熔断,相当于你配的ribbon超时就不生效了。
熔断超时是这样的:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
default代表默认,4.6.3 熔断代码示例
4.6.3.1 eureka 服务注册
4.6.3.1.1 pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!--注意:这里必须要添加,否则各种依赖有问题-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>4.6.3.1.2 application.yml
server:
port: 8000
spring:
application:
name: zuul-eureka
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
register-with-eureka: false
fetchRegistry: false4.6.3.1.3 main方法启动
@SpringBootApplication
@EnableEurekaServer
public class EurekaRun {
public static void main(String[] args) {
SpringApplication.run(EurekaRun.class, args);
}
}4.6.3.2 producer生产者
4.6.3.2.1 pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!--注意:这里必须要添加,否则各种依赖有问题-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>4.6.3.2.2 application.yml
spring:
application:
name: zull-producerzull
server:
port: 8003
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka4.6.3.2.3 main方法启动
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerZullRun {
public static void main(String[] args) {
SpringApplication.run(ProducerZullRun.class, args);
}
}4.6.3.2.4 controller
@RestController
public class TestHello {
@RequestMapping("/test")
public String testHello(@RequestParam String name){
return "hello,"+name+"is first";
}
@GetMapping("/cc1")
public String get1(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "cc1";
}
@GetMapping("/cc2")
public String get2(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "cc2";
}
@GetMapping("/cc3")
public String get3(){
try {
Thread.sleep(5500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "cc3";
}
}4.6.3.3 zuul gateway 路由转发
4.6.3.3.1 pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!--注意:这里必须要添加,否则各种依赖有问题-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>4.6.3.3.2 application.yml
spring:
application:
name: gate-way-zull
server:
port: 8002
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
zuul:
routes:
api-a:
path: /hello/**
serviceId: zull-producerzull
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2500
ribbon:
ConnectTimeout: 5000
ReadTimeout: 50004.6.3.3.3 main方法启动
@SpringBootApplication
@EnableZuulProxy
public class GateWayZuulRun {
public static void main(String[] args) {
SpringApplication.run(GateWayZuulRun.class, args);
}
}4.6.3.3.4 fallback调用
@Component
public class ProducerFallBack implements ZuulFallbackProvider{
public ClientHttpResponse fallbackResponse(Throwable throwable) {
// TODO Auto-generated method stub
if(throwable!=null || throwable.getCause()!=null){
System.out.println(throwable.getMessage());
}
return fallbackResponse();
}
public String getRoute() {
// TODO Auto-generated method stub
return "zull-producerzull";
}
public ClientHttpResponse fallbackResponse() {
// TODO Auto-generated method stub
return new ClientHttpResponse() {
public HttpHeaders getHeaders() {
// TODO Auto-generated method stub
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
public InputStream getBody() throws IOException {
// TODO Auto-generated method stub
return new ByteArrayInputStream("the service is unavaiable".getBytes());
}
public String getStatusText() throws IOException {
// TODO Auto-generated method stub
return "OK";
}
public HttpStatus getStatusCode() throws IOException {
// TODO Auto-generated method stub
return HttpStatus.OK;
}
public int getRawStatusCode() throws IOException {
// TODO Auto-generated method stub
return 200;
}
public void close() {
// TODO Auto-generated method stub
}
};
}
}4.6.3.4 调用调试
4.6.3.4.1 测试fallback成功调用
启动eureka注册中心 和 zuul gateway 路由转发
浏览器输入 http://192.168.18.106:8002/hello?
返回:the service is unavaiable
此时,生产者zull-producerzull 没有打开,说明成功进入fallback熔断
4.6.3.4.2 测试负载均衡熔断处理
启动eureka注册中心 和 zuul gateway 路由转发 和 生产者 zull-producerzull,
可以启动端口8001 和 8003 的producer服务,然后任意关闭其中一个端口服务。
浏览器输入 http://192.168.18.106:8002/zull-producerzull/test?name=‘w’
交替返回:
hello,’w’is first
the service is unavaiable
说明,当8001或者 8003 端口的服务异常时,会进入熔断处理。
4.6.3.4.3 测试超时
浏览器输入 :
可根据:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2500
ribbon:
ConnectTimeout: 5000
ReadTimeout: 5000查看具体情况。
ribbon和hystrix是同时生效的,哪个值小哪个生效,另一个就看不到效果了
4.7路由重试
4.7.1 使用场景
有时候因为网络或者其它原因,服务可能会暂时的不可用,这个时候我们希望可以再次对服务进行重试,Zuul也帮我们实现了此功能,需要结合Spring Retry 一起来实现
4.7.2 路由重试实现
4.7.2.1 eureka 服务注册
使用上述 4.6.3.1
4.7.2.2 producer 生产者
使用上述 4.6.3.2
Controller 新增一个requestmapping
@RequestMapping("/testRetry")
public String testRetryDemo(){
System.out.println("reqeust two name cc");
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "testRetry";
}4.7.2.3 retry 路由重试
4.7.2.3.1 pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<!-- 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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!--注意:这里必须要添加,否则各种依赖有问题-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>4.7.2.3.2 application.yml
server:
port: 8005
spring:
application:
name: zuul-retry
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
zuul:
routes:
api-a:
path: /hello/**
serviceId: zull-producerzull
retryable: true
ribbon:
MaxAutoRetries: 2 # 对当前服务重试的次数
MaxAutoRetriesNextServer: 0 #切换相同server的次数
ConnectTimeout: 2000
ReadTimeout: 2000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 25004.7.2.3.3 main方法启动
@SpringBootApplication
@EnableZuulProxy
public class RetryRun {
public static void main(String[] args) {
SpringApplication.run(RetryRun.class, args);
}
}4.7.2.3.4 fallback调用
@Component
public class FallBack implements ZuulFallbackProvider{
public ClientHttpResponse fallbackResponse() {
// TODO Auto-generated method stub
return new ClientHttpResponse() {
public HttpHeaders getHeaders() {
// TODO Auto-generated method stub
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
public InputStream getBody() throws IOException {
// TODO Auto-generated method stub
return new ByteArrayInputStream("the service is unaviable".getBytes());
}
public String getStatusText() throws IOException {
// TODO Auto-generated method stub
return "OK";
}
public HttpStatus getStatusCode() throws IOException {
// TODO Auto-generated method stub
return HttpStatus.OK;
}
public int getRawStatusCode() throws IOException {
// TODO Auto-generated method stub
return 200;
}
public void close() {
// TODO Auto-generated method stub
}
};
}
public String getRoute() {
// TODO Auto-generated method stub
return "*";//代表任意服务
}
}####4.7.2.4 测试调试
浏览器访问:http://192.168.18.106:8005/zull-producerzull/testRetry?
查看 producer 生产者日志:
reqeust two name cc
reqeust two name cc
reqeust two name cc
则说明进行了3次请求,也就是2次重试
注意:
注意开启重试在某些情况下是有问题的,比如当压力过大,一个实例停止响应时,路由将流量转到另一个实例,很有可能导致最终所有的实例全被压垮。说到底,断路器的其中一个作用就是防止故障或者压力扩散。用了retry,断路器就只有在该服务的所有实例都无法运作的情况下才能起作用。这种时候,断路器的形式更像是提供一种友好的错误信息,或者假装服务正常运行的假象给使用者。
不用retry,仅使用负载均衡和熔断,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断。如果可以接受,就无需使用retry。
4.8 zuul高可用
不同的客户端使用不同的负载将请求分发到后端的Zuul,Zuul在通过Eureka调用后端服务,最后对外输出。因此为了保证Zuul的高可用性,前端可以同时启动多个Zuul实例进行负载,在Zuul的前端使用Nginx或者F5进行负载转发以达到高可用性。
文章标题:Spring cloud zuul (高级)
发布时间:2019-11-14, 09:48:56
最后更新:2019-11-14, 09:48:56