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-3ServletDetectionFilter标记处理Servlet的类型
pre-2Servlet30WrapperFilter包装HttpServletRequest请求
pre-1FormBodyWrapperFilter包装请求体
route1DebugFilter标记调试标志
route5PreDecorationFilter处理请求上下文供后续使用
route10RibbonRoutingFilterserviceId请求转发
route100SimpleHostRoutingFilterurl请求转发
route500SendForwardFilterforward请求转发
post0SendErrorFilter处理有错误的请求响应
post1000SendResponseFilter处理正常的请求响应

4.4 禁用指定的Filter

可以在application.yml中配置需要禁用的filter,格式:

zuul: 
 FormBodyWrapperFilter: 
    pre: 
      disable: false

4.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/eureka

4.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-millis

4.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: false

4.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/eureka

4.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: 5000

4.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 测试超时

浏览器输入 :

http://192.168.18.106:8002/zull-producerzull/cc1,

http://192.168.18.106:8002/zull-producerzull/cc2,

http://192.168.18.106:8002/zull-producerzull/cc3,

可根据:

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: 2500

4.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