springcloud 组件_springcloud之一代常用组件详解

2dee13049a3e4b62358e25c5048ad29c.png

SpringCloud简介

官网地址

SpringCloud是基于SpringBoot的一整套实现微服务的框架。它提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。最重要的是,基于SpringBoot,会让开发微服务架构非常方便。常用的组件如下:

  • Eureka:注册中心
  • Zuul:服务网关
  • Ribbon:负载均衡
  • Feign:服务调用
  • Hystix:熔断器

Eureka

代码示例

搭建Eureka Server

引入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  
  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

配置

server:
  port: 10001
spring:
  application:
  name: eureka-server
eureka:
  client:
  # 是否注册自己的信息到EurekaServer,默认是true
    register-with-eureka: false
    # 是否拉取其它服务的信息,默认是true
    fetch-registry: false
    # EurekaServer的地址,如果是集群,需要加上其它Server的地址。
    service-url:
      defaultZone: http://127.0.0.1:${server.port}/eureka

添加@EnableEurekaServer注解

在启动类添加@EnableEurekaServer注解

查看euraka面板

启动服务并访问,http://127.0.0.1:10001/,如下:

694b209c18f16ef407d1742ed9c25bce.png

注册Eureka客户端

引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

配置

server:
  port: 10002
spring:
  application:
    name: consumer_test1
eureka:
  client:
    service-url:
    # EurekaServer地址
      defaultZone: http://127.0.0.1:10001/eureka
  instance:
  # 当其它服务获取地址时提供ip而不是hostname
    prefer-ip-address: true
   # 指定自己的ip信息,不指定的话会自己寻找
    ip-address: 127.0.0.1

添加@EnableDiscoveryClient注解

在启动类添加@EnableDiscoveryClient注解

查看euraka面板

953665daac3e4a483d9646ed7ebcc2dc.png

在status一列中,显示以下信息:

  • UP(1):代表现在是启动了1个示例,没有集群
  • DESKTOP-KJUJ87C:consumer_test1:10002:是示例的名称(instance-id),
    • 默认格式是:${hostname} + ${spring.application.name} + ${server.port}
    • instance-id是区分同一服务的不同实例的唯一标准,因此不能重复。

通过instance-id属性来修改它的构成:

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}

重启服务再查看:

2e9b24f3e0464661f743417410b019af.png

Eureka 详解

上面用代码演示了一下,接下来就详细解释一下euraka

基础架构

Eureka架构中的三个核心角色:

  • 服务注册中心
    Eureka的服务端应用,提供服务注册和发现功能
  • 服务提供者
    提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。
  • 服务消费者
    消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。

搭建Eureka Server 集群

Eureka Server配置

修改上文中的Eureka Server配置,区别在于defaultZone地址不同,删掉register-with-eureka: falsefetch-registry: false 。因这两个参数默认为true,可以不用配置,这样eureka server就可以互相注册并拉取到每个服务的信息了

Eureka Server服务1

server:
  port: 10001
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10003/eureka

Eureka Server服务2

server:
  port: 10003
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10001/eureka

查看面板

f460130e363c9621cff86591618ea338.png

Eureka客户端修改

defaultZone添加服务端地址,用逗号隔开即可

server:
  port: 10002
spring:
  application:
    name: consumer_test1
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10001/eureka,http://127.0.0.1:10003/eureka
  instance:
    instance-id: ${spring.application.name}:${server.port}

Eureka特性

服务注册

无论是服务的提供者还是服务的消费者都需要向EurekaServer注册服务

  • eureka.client.register-with-erueka=true属性
是否注册自己的信息到EurekaServer,默认为true

当服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。第一层Map的Key就是服务名称,第二层Map的key是服务的实例id。

  • eureka.client.fetch-registry=true属性
是否拉取其它服务的信息,默认是true

当服务消费者启动是,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。可以通过registry-fetch-interval-seconds来修改拉取服务的间隔时间

eureka:
  client:
    registry-fetch-interval-seconds: 5

服务续约

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告知自己是否还存活着,这个就是所谓的服务续约。当然,这个间隔时间可以通过以下参数修改:

  • lease-renewal-interval-in-seconds属性
服务续约的间隔,默认为30秒
  • lease-expiration-duration-in-seconds属性
服务失效时间,默认值90秒

默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。但是在开发时,这个值有点太长了,比如开发时经常会关掉一个服务,然而Eureka依然认为服务在活着。所以开发阶段可以时可以适当修改一下值。

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90
    lease-renewal-interval-in-seconds: 30

失效剔除和自我保护

  • 失效剔除

当一个服务出现故障而不能提供服务时,Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,开发时可以适当配置一下值,因为重启一个服务时,Eureka Server 60秒才反应过来,对开发造成不便。

  • 自我保护

当关停一个服务,就会在Eureka面板看到一条警告。

3ce4c7df5e3208bc99fff9af2a0c8579.png

这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

eureka:
  server:
   # 关闭自我保护模式(缺省为打开)
    enable-self-preservation: false 
    # 扫描失效服务的间隔时间(缺省为60*1000ms)
    eviction-interval-timer-in-ms: 1000

Ribbon

什么是Ribbon

Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。

负载均衡

Eureka中已经集成了负载均衡组件,所以不用引用ribbon的依赖

代码示例

服务提供方

引入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

添加@EnableDiscoveryClient注解

在启动类添加@EnableDiscoveryClient注解

配置

server:
  port: 10005
spring:
  application:
    name: Product-01
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10001/eureka
  instance:
    # 当其它服务获取地址时提供ip而不是hostname
    prefer-ip-address: true
    # 指定自己的ip信息,不指定的话会自己寻找
    ip-address: 127.0.0.1

添加方法

@RestController
public class TestController {
    @GetMapping("/tt")
    public String tt(){
        return "访问服务提供者成功!";
    }

}

服务消费方

还是以上文的consumer_test1为例子

开启负载均衡

在启动类里面注入RestTemplate并添加@LoadBalanced注解

@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerTest1Application {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerTest1Application.class, args);
    }

}

新增service

这里注意一下url,这里直接写服务名称。服务名称不能用下划线_,要换成中划线-。如果用了下划线restTemplate会报错:Request URI does not contain a valid hostname。

@Service
public class TestService {
    @Autowired
    private RestTemplate restTemplate;

    public String tt(){
        String url = "http://PRODUCT-01/tt";
        String s = restTemplate.getForObject(url, String.class);
        System.out.println(s);
        return s;
    }

}

新增controller

@RestController
public class TConsumerController {

    @Autowired
    private TestService testService;

    @GetMapping("/getTest")
    public String getTest(){
        return testService.tt();
    }
}

测试

e6624c0b69d74f9f34a75b9776339685.png

原理分析

为什么restTemplate.getForObject直接写服务名称就会调用成功呢?这是因为LoadBalancerInterceptor根据service名称,获取到了服务实例的ip和端口,如下源码:

restTemplate.getForObject发起请求,被LoadBalancerInterceptor拦截,接着进入intercept方法获取到uri和服务名称。

f83354d1230a8bfa81e7e90e1f124cff.png
RibbonLoadBalanceClient类中接着执行execute方法, getLoadBalancer获取一个负载均衡器,然后 getServer根据 负载均衡器的算法在服务列表中选择server。

b9cf053aee145b3b1808e72c1c6c0e87.png

负载均衡策略

先增加一个服务提供者,端口号为10006,以便后面测试。

8d1e3b9c7d57abeaad5d970a11337982.png

在刚才的源码中看到拦截是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,这个方法的作用:就是从LoadBalancer中为指定的服务选择服务示例

public interface ServiceInstanceChooser {

 /**
  * Chooses a ServiceInstance from the LoadBalancer for the specified service.
  * @param serviceId The service ID to look up the LoadBalancer.
  * @return A ServiceInstance that matches the serviceId.
  */
 ServiceInstance choose(String serviceId);

}

测试一下

@SpringBootTest
class ConsumerTest1ApplicationTests {

    @Autowired
    private RibbonLoadBalancerClient client;

    @Test
    public void test(){
        for (int i = 0; i < 20; i++) {
            ServiceInstance instance = this.client.choose("Product-01");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }

}

查看控制台

可以看到默认就是轮询

84d670d8c112f4a60474d42943da53cf.png

修改负载均衡策略

继续查看源代码会看到有一个这样的方法,然后这个rule就是用来进行服务选择的。

0c30f27411f211b6f86beeab1098063f.png

可以看到这个rule默认就是轮询

11e348ffae957a16b47a5cc1fc18c82b.png

9ee6998591fedc2d4366980364caca45.png

那么有多少种策略呢?可以查看它的实现类,如下

56737b765eca5d9dfd8cb129e3bcb28e.png

那怎么去修改负载均衡策略呢?可以修改如下配置:

格式是: {服务名称}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的实现类。
Product-01:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

再测试

可以发现已经是随机了

9b3837d0411f9ec98d8a7dc89fef21a0.png

重试机制

当把10006端口的服务提供者停掉后,再次访问http://localhost:10002/getTest,如下结果。这是因为服务剔除的延迟,consumer并不会立即得到最新的服务列表,此时再次访问你会得到错误提示。

fdf91777689cf4679f5595d0111cc5f7.png

但是,不是还有一个服务10005是正常的吗,此时,可以用重试机制,当一次服务调用失败后,不会立即抛出异常,而是再次重试另一个服务。

配置

根据如下配置,当访问到某个服务超时后,它会再次尝试访问下一个服务实例,如果不行就再换一个实例,如果不行,则返回失败。切换次数取决于MaxAutoRetriesNextServer参数的值

spring:
  application:
    name: consumer_test1
  cloud:
    loadbalancer:
      retry:
        enabled: true # 开启Spring Cloud的重试功能
Product-01:
  ribbon:
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

测试

31539e141bca2bbc675637ceeccc9425.png

Hystix

简介

Hystix,即熔断器。它是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

服务雪崩

服务雪崩是一种因“服务提供者的不可用”导致“服务调用者不可用”,并将不可用逐渐放大的现象。

例如A为服务提供者, B为A的服务调用者, C和D是B的服务调用者. 当A的不可用,引起B的不可用,并将不可用逐渐放大C和D时, 服务雪崩就形成了。

服务降级

服务降级就是优先保证核心服务,而非核心服务不可用或弱可用。

服务降级目的就是先保证主要服务的畅通,一切资源优先让给主要服务来使用。比如访问一个商品详情时,获取评论时10s都还没有响应,但是商品服务是正常的,这时候就可以做服务降级,保证商品服务正常,商品的评论就给出温馨的提示即可。

代码示例

服务消费方

引入依赖

在服务消费方(consumer_test1)引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

添加@EnableCircuitBreaker注解

亦可以直接写 @SpringCloudApplication,该注解包含下面三个注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ConsumerTest1Application {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerTest1Application.class, args);
    }

}

编写降级方法

@HystrixCommand(fallbackMethod = "ttFallback"):用来声明一个降级逻辑的方法。这里要注意,熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明

@RestController
public class TConsumerController {

    @Autowired
    private TestService testService;

    @HystrixCommand(fallbackMethod = "ttFallback")
    @GetMapping("/getTest")
    public String getTest(){
        return testService.tt();
    }

    public String ttFallback(){
        return "系统繁忙,请稍后再试!";
    }
}

服务提供方

模拟服务网络延迟

Hystix的默认超时时长为1s,所以这里设置2s,消费方就会进行服务降级。这里对端口为10006的服务方改造。

@RestController
public class TestController {

    @GetMapping("/tt")
    public String tt() throws InterruptedException {
        Thread.sleep(2000);
        return "访问服务提供者成功!";
    }

}

测试

访问http://localhost:10002/getTest,结果如下:

0fc74edc07d7ba6128574d44324011e0.png

优化

不知道看到测试结果后有没有伙伴想到上文的重试机制呢?为什么没有进行重试呢?那是因为ribbon的读取时间设置成了一秒。因此重试机制没有被触发,而是先触发了熔断。所以,Ribbon的超时时间 一定要小于Hystix的超时时间。

ce092ea81f38ae4136bff4324235e6b0.png

那么可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。在消费方增加如下配置:

###配置请求超时时间
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 7000
    ###配置具体方法超时时间
    tt:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

在消费方指定commandkey

@HystrixCommand(commandKey = "tt",fallbackMethod = "ttFallback")

再测试

632099e87ee01f7f52fb45740613b764.png

Feign

简介

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。Spring Cloud 给 Feign 添加了支持Spring MVC注解,并整合Ribbon及Eureka进行支持负载均衡。

代码示例

引入依赖

在服务消费端引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

Feign的客户端

配置需要调用的微服务接口,然后添加@FeignClient注解,value指定服务名称。那么Feign会通过动态代理,生成实现类,并且Feign会根据注解生成URL,并访问获取结果。

@FeignClient("Product-01")
public interface TTFeignClient {
    @GetMapping("/tt")
    String tt();
}

开启Feign功能

在启动类添加@EnableFeignClients注解

服务消费方

把RestTemplate相关的代码都注释掉,改造如下:

@Service
public class TestService {
//    @Autowired
//    private RestTemplate restTemplate;
    @Autowired
    private TTFeignClient ttFeignClient;

    public String tt(){
        return ttFeignClient.tt();
    }
}

服务提供方

不变

测试

46e884fd6a45322b09d98c7af3310a7c.png

负载均衡

feign已经集成了ribbon依赖和自动配置,所以不需要像上文ribbon那样,引入依赖和配置 restTemplate。直接配置即可,如下:

Product-01:
  ribbon:
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

Hystix

Feign默认也有对Hystix的集成,所以启动Hystix也只需一行配置:

feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能

请求压缩

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
    response:
      enabled: true # 开启响应压缩

同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
      min-request-size: 2048 # 设置触发压缩的大小下限

注:上面的数据类型、压缩大小下限均为默认值。

Zuul网关

简介

网关即是一个网络整体系统中的前置门户入口。请求首先通过网关,进行路径的路由,定位到具体的服务节点上。Spring Cloud Zuul是整合Netflflix公司的Zuul开源项目实现的微服务网关,它实现了请求路由、负载均衡、校验过虑等功能。

代码示例

引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

配置

/consumer_test1/**开头的请求,代理到http://127.0.0.1:10002,也就是请求到consumer_test1服务上

server:
  port: 10008
spring:
  application:
    name: gateway

zuul:
  routes:
#    这里是路由id,可随意写
    consumer_test1:
#    这里是映射路径
      path: /consumer_test1/**
#      映射路径对应的实际url地址
      url: http://127.0.0.1:10002

添加@EnableZuulProxy注解

在启动类添加@EnableZuulProxy注解

测试

e74519c8ed2f08588ec8218d3115008f.png

优化

可以看到测试是通过了,但是地址却被写死了,那如果这个服务是多个实例的话,是不是要手动添加每一个地址。显然,这不应该。那么就应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对。

引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

添加@EnableDiscoveryClient注解

在启动类添加@EnableDiscoveryClient注解

添加配置

eureka配置

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10001/eureka,http://127.0.0.1:10003/eureka
  instance:
    # 当其它服务获取地址时提供ip而不是hostname
    prefer-ip-address: true
    # 指定自己的ip信息,不指定的话会自己寻找
    ip-address: 127.0.0.1

zuul路由配置

因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。

zuul:
  routes:
#    这里是路由id,随意写
    consumer_test1:
#    这里是映射路径
      path: /consumer_test1/**
#      映射路径对应的实际url地址
      serviceId: consumer_test1

测试

e74519c8ed2f08588ec8218d3115008f.png

路由规则配置

默认路由规则

默认情况下,一切服务的映射路径就是服务名本身。

  • 例如服务名为:consumer_test1,则默认的映射路径就是:/consumer_test1/**

简化版路由规则

在上文的配置中,配置的规则是这样的:

  • zuul.routes.<route>.path=/xxx/**: 来指定映射路径。<route>是自定义的路由名
  • zuul.routes.<route>.serviceId=/consumer_test1:来指定服务名。

而大多数情况下,配置的<route>路由名称往往和 服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:zuul.routes.<serviceId>=<path>

上文的配置可以简化为一条:

zuul:
  routes:
    consumer_test1: /consumer_test1/**

路由前缀

通过zuul.prefix=/api来指定了路由的前缀,这样在发起请求时,路径就要以/api开头。路径/api/consumer_test1/getTest将会被代理到/consumer_test1/getTest

zuul:
  prefix: /api # 添加路由前缀
  routes:
      consumer_test1: # 这里是路由id,随意写
        path: /consumer_test1/** # 这里是映射路径
        service-id: consumer_test1 # 指定服务名称

负载均衡和熔断

Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:

zuul:
  retryable: true
ribbon:
  ConnectTimeout: 250 # 连接超时时间(ms)
  ReadTimeout: 2000 # 通信超时时间(ms)
  OkToRetryOnAllOperations: true # 是否对所有操作重试
  MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
  MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
  command:
   default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 熔断超时时长:6000ms

过滤器

Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个功能往往是通过Zuul提供的过滤器来实现的。

ZuulFilter

查看源码,可以看到ZuulFilter中有四个重要的方法,如下:

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {

    abstract public String filterType();

    abstract public int filterOrder();
    // 来自IZuulFilter
    boolean shouldFilter();
 // IZuulFilter
    Object run() throws ZuulException;
}
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

过滤器执行生命周期

  • 正常流程:
    • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

2ce1717e27ebb6129939dae297961d09.png

场景使用

  • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
  • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
  • 服务调用时长统计:pre和post结合使用。

自定义过滤器

代码示例

package com.ao.gateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class LoginFilter extends ZuulFilter {

    /*登录校验,前置拦截*/
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    /*返回true,代表过滤器生效。*/
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /*登录校验逻辑*/
    @Override
    public Object run() {
        // 获取Zuul提供的请求上下文对象
        RequestContext ctx = RequestContext.getCurrentContext();
        // 从上下文中获取request对象
        HttpServletRequest req = ctx.getRequest();
        //从请求中获取token
        String token = req.getParameter("my-token");
        // 校验
        if(token == null || "".equals(token.trim())){
            // 没有token,登录校验失败,拦截
            ctx.setSendZuulResponse(false);
            // 返回401状态码
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
}

测试

没有token示例

ef79f7e382c9fc5e41346a07a725473b.png

存在token示例

8a73f5de19343136ce1494330db47132.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值