天天看点

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

目录

前言

7.1.5 Hystrix

7.1.5.1 什么是Hystrix

7.1.5.2 雪崩问题

7.1.5.3 线程隔离,服务降级

7.1.5.4 搭建

7.1.5.4.1 引入依赖

7.1.5.4.2 开启熔断

7.1.5.4.3 编写降级逻辑

1.局部降级逻辑

2.全局降级逻辑

7.1.5.4.4 设置超时

7.1.5.5 服务熔断

7.1.6 Feign

7.1.6.1 什么是Feign

7.1.6.2 快速入门

7.1.6.2.1 引入依赖

7.1.6.2.2 开启Feign功能

7.1.6.2.3 Feign的客户端

7.1.6.2.4 测试

7.1.6.3 负载均衡

7.1.6.4 Hystrix

7.1.6.5 请求压缩

7.1.6.6 日志级别

7.1.7 Zuul网关

7.1.7.1 Zuul概述

7.1.7.1.1 问题

7.1.7.1.2 Zuul

7.1.7.1.3 Zuul加入后的架构

7.1.7.2 快速入门

7.1.7.2.1 添加依赖

7.1.7.2.2 配置

7.1.7.2.3 启动类添加注解

7.1.7.2.4 编写路由规则

7.1.7.3 面向服务的路由

7.1.7.3.1 添加Eureka客户端依赖

7.1.7.3.2 添加Eureka配置,获取服务信息

7.1.7.3.3 启动类开启Eureka客户端发现功能

7.1.7.3.4 修改映射配置,通过服务名称获取

7.1.7.4 简化的路由配置

7.1.7.5 默认的路由规则

7.1.7.6 路由前缀

7.1.7.7 过滤器

7.1.7.7.1 ZuulFilter

7.1.7.7.2 过滤器执行生命周期

7.1.7.8 自定义过滤器

7.1.7.9 负载均衡和熔断

前言

SpringCloud组件使用套路,三步走

  1. 引入组件的启动器
  2. 覆盖默认配置
  3. 在引导类上添加注解,开发相关组件 

重点理解SpringCloud这些组件的使用细节,知道每个阶段出现的注解,它们的顺序以及具体功能

7.1.5 Hystrix

7.1.5.1 什么是Hystrix

Hystrix,[hɪst'rɪks],英文意思是豪猪,是一种保护机制。

Hystrix也是Netflix公司的一款组件。

主页:GitHub - Netflix/Hystrix: Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

Hystix作用:

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

7.1.5.2 雪崩问题

微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:  

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

 如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。

如果此时,某个服务出现异常:

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

例如微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

 服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。

这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。  此时如果有很多个车型都需要这个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。

Hystix解决雪崩问题的手段有两个:

  • 线程隔离
  • 服务熔断

7.1.5.3 线程隔离,服务降级

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。

用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理,什么是服务降级?

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

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。

服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

触发Hystix服务降级的情况:

  • 线程池已满
  • 请求超时

7.1.5.4 搭建

7.1.5.4.1 引入依赖

在消费者的pom.xml中引入Hystrix依赖:

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

7.1.5.4.2 开启熔断

在消费者启动器中加入注解

@EnableCircuitBreaker

或者使用组合注解

@SpringCloudApplication  

/*
@SpringBootApplication
@EnableDiscoveryClient //springcloud提供的
@EnableCircuitBreaker//启用断路器(熔断器)
*/
@SpringCloudApplication  //组合注解,相当于上面三个注解
public class ConsumerApplication {
           

7.1.5.4.3 编写降级逻辑

1.局部降级逻辑

降级方法要和被降级的方法参数列表一致,返回值也要一致

在需要降级的方法中使用注解@HystrixCommand(fallbackMethod = "findByIdFallback") ,指定降级方法的方法名

@Controller
@RequestMapping("/consumer")
public class UserController {
    @Resource
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;


    @RequestMapping("/findbyid/{id}")
    @ResponseBody
   @HystrixCommand(fallbackMethod = "findByIdFallback") //指定降级方法的方法名
    public String findById(@PathVariable Integer id){
        // 调用
        User user = restTemplate.getForObject("http://service-provider/provider/findbyid/" + id, User.class);
        return user.toString();
    }
   //降级方法要和被降级的方法参数列表一致,返回值也要一致
    public String findByIdFallback(Integer id){
        //处理
        return "服务器正忙,请稍后重试";
    }
}
           

2.全局降级逻辑

全局降级逻辑(默认FallBack、默认降级逻辑)

局部降级逻辑把fallback写在了某个业务方法上,如果这样的方法很多,会很冗余。因此把Fallback配置加在类上),实现默认fallback:

@DefaultProperties(defaultFallback = "defaultFallBack"):在类上指明统一的失败降级方法

@HystrixCommand:在方法上直接使用该注解,使用默认的降级方法。

定义defaultFallback:默认降级方法,不用任何参数,以匹配更多方法,但是返回值一定一致

@Controller
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "defaultFasllback")//指定全局降级方法的方法名
public class UserController {
    @Resource
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    UserFeign userFeign;

    @RequestMapping("/findbyid/{id}")
    @ResponseBody
    @HystrixCommand //方法上的注解不能省略,因为不是所有的方法都需要降级方法
    public String findById(@PathVariable Integer id){
        User user = userFeign.findById(id);
        return user.toString();
    }

    //全局降级方法
    //降级方法要和被降级的方法参数列表一致,那跟谁保持一致呢?那就不要参数了,返回值也一致,那就用string
    public String defaultFasllback(){
        return "网络拥挤,请稍后再试~~~";
    }
}
           

7.1.5.4.4 设置超时

在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystix的默认超时时长为1,我们可以通过配置修改这个值:

我们可以通过

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

来设置Hystrix超时时间。该配置没有提示。

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms
           

改造服务提供者

改造服务提供者的UserController接口,随机休眠一段时间,以触发熔断:

@GetMapping("{id}")
public User queryUserById(@PathVariable("id") Long id) {
    try {
        Thread.sleep(6000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return this.userService.queryUserById(id);
}
           

7.1.5.5 服务熔断

熔断器,也叫断路器,其英文单词为:Circuit Breaker

熔断机制,在分布式系统中,服务调用方可以自己进行判断某些服务反应慢或者存在大量超时的情况时,能够主动熔断,防止整个系统被拖垮。

Hystrix可以实现弹性容错,当情况好转后,可以自动连接。

通过断路的方式,可以将后续请求直接拒绝,一段时间后允许部分请求通过,如果调用成功则回到闭合状态,否则继续断开

熔断状态机3个状态:

  • Closed:关闭状态,所有请求都正常访问。
  • Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
  • Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时

不过,默认的熔断触发要求较高,休眠时间窗较短,为了测试方便,我们可以通过配置修改熔断策略:

circuitBreaker.requestVolumeThreshold=10
circuitBreaker.sleepWindowInMilliseconds=10000
circuitBreaker.errorThresholdPercentage=50
           
  • requestVolumeThreshold:触发熔断的最小请求次数,默认20
  • errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%
  • sleepWindowInMilliseconds:休眠时长,默认是5000毫秒

7.1.6 Feign

7.1.6.1 什么是Feign

Fein ,[feɪn],假装,伪装

在前面的学习中,我们使用了Ribbon的负载均衡功能,大大简化了远程调用时的代码:

String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);      

如果就学到这里,你可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?

这就是我们接下来要学的Feign的功能了。

什么是伪装?

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

Feign是 Netflix开发的声明式、模板化的HTTP客户端,其灵感来自 Retrofit,JAXRS-2.0以及WebSocket。Feign 可帮助我们更加便捷、优雅地调用 HTTP API。

在Spring Cloud中,使用Feign 非常简单一一创建一个接口,并在接口上添加一些注解代码就完成了。Feign支持多种注解,例如 Feign自带的注解或者JAX-RS注解等。

Spring Cloud对 Feign进行了增强,使 Feign 支持了Spring MVC注解,并整合了 Ribbon和 Eureka,从而让 Feign的使用更加方便。

7.1.6.2 快速入门

7.1.6.2.1 引入依赖

在消费者模块中导入依赖

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

7.1.6.2.2 开启Feign功能

我们在启动类上,添加@EnableFeignClients注解,开启Feign功能

/*
@SpringBootApplication
@EnableDiscoveryClient //springcloud提供的
@EnableCircuitBreaker//启用断路器(熔断器)
*/

@SpringCloudApplication  //组合注解,相当于上面三个注解
@EnableFeignClients // 开启feign客户端
public class ConsumerApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConsumerApplication.class,args);
        }
/*
删除
    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
*/

}

           
删除RestTemplate:feign已经自动集成了Ribbon负载均衡的RestTemplate。所以,此处不需要再注册RestTemplate。  

7.1.6.2.3 Feign的客户端

创建UserFeign 接口

@FeignClient(value = "service-provider",fallback = UserFeignImpl.class)   //feign客户端,用于调用远程服务
public interface UserFeign {
    //可以使用springmvc注解
    //注意完整路径
    @RequestMapping("/provider/findbyid/{id}")
    User findById(@PathVariable("id") int id);
}
           

创建UserFeign 实现类UserFeignImpl 

@Component //放入容器
public class UserFeignImpl implements UserFeign {
    @Override
    public User findById(int id) {
        User user = new User();
        user.setName("服务器繁忙,请稍后再试");
        return user;
    }
}
           
  • 这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
  • @FeignClient

    ,声明这是一个Feign客户端,类似

    @Mapper

    注解。同时通过

    value

    属性指定服务名称
  • 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果

修改controller中的调用逻辑

通过userFeign来获取

@Controller
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "defaultFasllback")//指定全局降级方法的方法名
public class UserController {
    @Resource
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    UserFeign userFeign;

    @RequestMapping("/findbyid/{id}")
    @ResponseBody
    @HystrixCommand //方法上的注解不能省略,因为不是所有的方法都需要降级方法
    public String findById(@PathVariable Integer id){
        // 调用
        User user = userFeign.findById(id);
        return user.toString();
    }
}
           

7.1.6.2.4 测试

启动模块

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

 输入  localhost:消费者端口号/以及路径/二级路径/id

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

7.1.6.3 负载均衡

Feign中本身已经集成了Ribbon依赖和自动配置:

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

因此我们不需要额外引入依赖,也不需要再注册

RestTemplate

对象。

7.1.6.4 Hystrix

Feign默认也有对Hystrix的集成

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

 只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:(在消费者模块添加配置内容)

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

但是,Feign中的Fallback配置不像hystrix中那样简单了。

1)首先,我们要定义一个类UserClientFallback,实现刚才编写的UserClient,作为fallback的处理类

@Component
public class UserClientFallback implements UserClient {
​
    @Override
    public User queryById(Long id) {
        User user = new User();
        user.setUserName("服务器繁忙,请稍后再试!");
        return user;
    }
}
           

2)然后在UserFeignClient中,指定刚才编写的实现类

@FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 标注该类是一个feign接口
public interface UserClient {
​
    @GetMapping("user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
           

3)重启测试:

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

7.1.6.5 请求压缩

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 # 设置触发压缩的大小下限
           

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

7.1.6.6 日志级别

前面讲过,通过

logging.level.xx=debug

来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为

@FeignClient

注解修饰的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。

1)设置cn.bl包下的日志级别都为debug

logging:
  level:
    cn.bl: debug
           

2)编写配置类,定义日志级别

@Configuration
public class FeignLogConfiguration {
​
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
           

这里指定的Level级别是FULL,Feign支持4种级别:

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关
  • NONE:不记录任何日志信息,这是默认值
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据

3)在FeignClient中指定配置类:

@FeignClient(value = "service-privider", fallback = UserFeignClientFallback.class, configuration = FeignConfig.class)
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
           

4)重启项目,即可看到每次访问的日志:

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

7.1.7 Zuul网关

7.1.7.1 Zuul概述

7.1.7.1.1 问题

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

我们使用SpringCloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。

在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过均衡负载公开至服务调用方。我们把焦点聚集在对外服务这块,直接暴露我们的服务地址,这样的实现是否合理,或者是否有更好的实现方式呢?

先来说说这样架构需要做的一些事儿以及存在的不足:

  • 破坏了服务无状态特点。

    为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。 从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外考虑对接口访问的控制处理。

  • 无法直接复用既有接口。

    当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

面对类似上面的问题,我们要如何解决呢?答案是:服务网关!

为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器的 服务网关。

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备

服务路由

均衡负载

功能之外,它还具备了

权限控制

等功能。SpringCloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

zuul主要是请求的鉴权,部署在“门口”,如下图

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

7.1.7.1.2 Zuul

Zuul是Netflix开源的微服务网关,它可以和 Eureka、Ribbon、Hystrix等组件配合使用。Zuul 的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
  • 动态路由:动态地将请求路由到不同的后端集群。
  • 压力测试:逐渐增加指向集群的流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群.
  • 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB (Elastic Load Balancing )使用的多样化,以及让系统的边缘更贴近系统的使用者。

SpringCloud对Zuul进行了整合与增强。目前,Zuul使用的默认HTTP客户端是 Apache HTTP Client,也可以使用RestClient或者okhttp3.OkHttpClient。如果想要使用RestClient,可以设置ribbon.restclient.enabled=true;

7.1.7.1.3 Zuul加入后的架构

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。

7.1.7.2 快速入门

7.1.7.2.1 添加依赖

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

7.1.7.2.2 配置

server:
  port: 10010 #服务端口
spring:
  application:
    name: api-gateway #指定服务名
           

7.1.7.2.3 启动类添加注解

添加@EnableZuulProxy 

@SpringBootApplication
@EnableZuulProxy // 开启网关功能
public class YhZuulApplication {

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

7.1.7.2.4 编写路由规则

我们需要用Zuul来代理service-provider服务,先看一下控制面板中的服务状态:

  • ip为:127.0.0.1
  • 端口为:8081

映射规则:

server:
  port: 10010 #服务端口
spring:
  application:
    name: api-gateway #指定服务名
zuul:
  routes:
    service-provider: # 这里是路由id,随意写
      path: /service-provider/** # 这里是映射路径
      url: http://127.0.0.1:8081 # 映射路径对应的实际url地址
           

我们将符合

path

规则的一切请求,都代理到

url

参数指定的地址

本例中,我们将

/service-provider/**

开头的请求,代理到http://127.0.0.1:8081

7.1.7.3 面向服务的路由

在刚才的路由规则中,我们把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!

7.1.7.3.1 添加Eureka客户端依赖

在zuul模块中引入依赖

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

7.1.7.3.2 添加Eureka配置,获取服务信息

eureka:
  client:
    registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
           

7.1.7.3.3 启动类开启Eureka客户端发现功能

@SpringBootApplication
@EnableZuulProxy // 开启Zuul的网关功能
@EnableDiscoveryClient
public class ZuulDemoApplication {

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

7.1.7.3.4 修改映射配置,通过服务名称获取

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

zuul:
  routes:
    service-provider: # 这里是路由id,随意写
      path: /service-provider/** # 这里是映射路径
      serviceId: service-provider # 指定服务名称
           

7.1.7.4 简化的路由配置

在刚才的配置中,我们的规则是这样的:

  • zuul.routes.<route>.path=/xxx/**

    : 来指定映射路径。

    <route>

    是自定义的路由名
  • zuul.routes.<route>.serviceId=service-provider

    :来指定服务名。

而大多数情况下,我们的

<route>

路由名称往往和服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:

zuul.routes.<serviceId>=<path>

比方说上面我们关于service-provider的配置可以简化为一条:

zuul:
  routes:
    service-provider: /service-provider/** # 这里是映射路径
           

省去了对服务名称的配置。

7.1.7.5 默认的路由规则

在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:

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

    service-provider

    ,则默认的映射路径就是:

    /service-provider/**

也就是说,刚才的映射规则我们可以不配置

7.1.7.6 路由前缀

配置示例:

zuul:
  routes:
    service-provider: /service-provider/**
    service-consumer: /service-consumer/**
  prefix: /api # 添加路由前缀
           

我们通过

zuul.prefix=/api

来指定了路由的前缀,这样在发起请求时,路径就要以/api开头。

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

场景: 可以判断,哪些请求经过网关,哪些没有经过网关

7.1.7.7 过滤器

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

7.1.7.7.1 ZuulFilter

在IDEA中连续按两次SHIFT,搜索ZuulFilter

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关
7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

 ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();

    abstract public int filterOrder();
    
    boolean shouldFilter();// 来自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}
           
  • shouldFilter

    :返回一个

    Boolean

    值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run

    :过滤器的具体业务逻辑。
  • filterType

    :返回字符串,代表过滤器的类型。包含以下4种:
    • pre

      :请求在被路由之前执行
    • route

      :在路由请求时调用
    • post

      :在route和errror过滤器之后调用
    • error

      :处理请求时发生错误调用
  • filterOrder

    :通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

进入IZuulFilter

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

里面定义了两个最基本的方法

boolean shouldFilter();
 Object run() throws ZuulException;      

所有的过滤器,包括自定义的过滤器都要实现这两个最基本的方法

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

7.1.7.7.2 过滤器执行生命周期

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

正常流程:

  • 请求到达首先会经过pre类型过滤器,而后到达route类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。

异常流程:

  • 整个过程中,pre或者route过滤器出现异常,都会直接进入error过滤器,在error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
  • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,将最终结果返回给请求客户端。
  • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和route不同的是,请求不会再到达POST过滤器了。

所有内置过滤器列表:

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

使用场景

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

7.1.7.8 自定义过滤器

描述:自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行

创建gateway包,里面创建一个类LoginFilter 

@Component
public class LoginFilter extends ZuulFilter {
    /**
     * 过滤器类型,前置过滤器
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤器的执行顺序
     * @return
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 该过滤器是否生效
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 登陆校验逻辑
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        //所有的请求对象都在RequestContext中
        //所有的响应对象都在ResponseContext中
        // 获取zuul提供的上下文对象
        RequestContext context = RequestContext.getCurrentContext();
        // 从上下文对象中获取请求对象
        HttpServletRequest request = context.getRequest();
        // 获取token信息
        String token = request.getParameter("access-token");
        // 判断
        if (StringUtils.isBlank(token)) {
            // 过滤该请求,不对其进行路由
            context.setSendZuulResponse(false);
            // 设置响应状态码,401
            context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
            // 设置响应信息
            context.setResponseBody("{\"status\":\"401\", \"text\":\"request error!\"}");
        }
        // 校验通过,把登陆信息放入上下文信息,继续向后执行
        context.set("token", token);
        return null;
    }
}
           

测试 

7.1 微服务-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul网关

7.1.7.9 负载均衡和熔断

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

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000 # 设置hystrix的超时时间为6000ms