微服务概述

整个微服务架构是由大量的技术框架和方案构成,比如:

服务基础开发 Spring MVC、Spring、SpringBoot
服务注册与发现 Netflix的Eureka、Apache的ZooKeeper等
服务调用 RPC调用有阿里巴巴的Dubbo,Rest方式调用有当当网Dubbo基础上扩展的Dubbox、还有其他方式实现的Rest,比如Ribbon、Feign
分布式配置管理 百度的Disconf、360的QConf、淘宝的Diamond、Netflix的Archaius等
负载均衡 Ribbon
服务熔断 Hystrix
API网关 Zuul
批量任务 当当网的Elastic-Job、Linkedln的Azkaban
服务跟踪 京东的Hydra、Twitter的Zipkin等

Spring Cloud的出现,可以说是为微服务架构迎来一缕曙光,有SpringCloud社区的巨大支持和技术保障,让我们实施微服务架构变得异常简单了起来,它不像我们之前所列举的框架那样,只是解决微服务中的某一个问题,而是一个解决微服务架构实施的综合性解决框架,它整合了诸多被广泛实践和证明有效的框架作为实施的基础组件,又在该体系基础上创建了一些非常优秀的边缘组件将它们很好地整合起来。

加之Spring Cloud 有其Spring 的强大技术背景,极高的社区活跃度,也许未来Spring Cloud会成为微服务的标准技术解决方案;

SpringCloud官网:http://spring.io

版本问题

Spring Cloud的版本并不是传统的使用数字的方式标识,而是使用诸如:Angel、Brixton、Camden……等伦敦的地名来命名版本,版本的先后顺序使用字母表A-Z的先后来标识。从2020年开始以日期为版本

Spring Cloud与Spring Boot版本匹配关系

SpringCloud版本 SpringBoot版本
2021.0.0 兼容Spring Boot2.6X
2020.0.X 兼容Spring Boot2.4X, 2.5X
Hoxton 兼容Sprint Boot2.2.X,2.3X
Finchley 兼容Spring Boot 2.0.x, 不兼容Spring Boot 1.5.x
Edgware 兼容Spring Boot 1.5.x, 不兼容Spring Boot 2.0.x
Dalston 兼容Spring Boot 1.5.x, 不兼容Spring Boot 2.0.x
Camden 兼容Spring Boot 1.4.x, 也兼容Spring Boot 1.5.x
Brixton 兼容Spring Boot 1.3.x, 也兼容Spring Boot 1.4.x
Angel 兼容Spring Boot 1.2.x

spring-cloud版本:Hoxton.SR3版本
spring版本:2.2.6.RELEASE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!--2.2.6.RELEASE版本-->
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.peng</groupId>
<artifactId>003-consumer-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>003-consumer-feign</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<!--Hoxton.SR3版本-->
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

主要学习

组件名称 作用
Eureka 服务注册中心
Ribbon 客户端负载均衡
Feign 声明式服务调用
Hystrix 客户端容错保护
Zuul API服务网关

Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。
注册中心—— 负责服务的注册与发现,很好将各服务连接起来
断路器——负责监控服务之间的调用情况,连续多次失败进行熔断保护。
API网关——负责转发所有对外的请求和服务
配置中心——提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
链路追踪——可以将所有的请求数据记录下来,方便我们进行后续分析
各个组件又提供了功能完善的dashboard监控平台,可以方便的监控各组件的运行状况

RestTemplate概述

RestTemplateSpring 提供的一个调用 Restful 服务的抽象层,它简化了 Restful 服务的通信方式,隐藏了不必要的一些细节,让我们更加优雅地在应用中调用 Restful 服务 。 我们在项目中经常要使用第三方的 Rest API 服务,比如短信、快递查询、天气预报等等。这些第三方只要提供了 Rest Api ,你都可以使用 RestTemplate 来调用它们。 只要你的项目使用了 Spring MVC 就已经集成了RestTemplate 。但是通常情况下该类不会自动被注入 Spring IoC容器 ,需要手动实例化。

CAP理论

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。

由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡,在此Zookeeper保证的是CP, 而Eureka则是AP。

Zookeeper保证CP

在ZooKeeper中,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举,但是问题在于,选举leader需要一定时间, 且选举期间整个ZooKeeper集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得ZooKeeper集群失去master节点是大概率事件,虽然服务最终能够恢复,但是在选举时间内导致服务注册长期不可用是难以容忍的。

Eureka保证AP

Eureka优先保证可用性,Eureka各个节点是平等的,某几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。

所以Eureka在网络故障导致部分节点失去联系的情况下,只要有一个节点可用,那么注册和查询服务就可以正常使用,而不会像zookeeper那样使整个注册服务瘫痪,Eureka优先保证了可用性。

服务注册中心

作用类似zookeeper

注册中心负载均衡

自我保护机制

两个微服务客户端实例A和B之间有调用的关系,A是消费者,B是提供者,但是由于网络故障,B未能及时向Eureka发送心跳续约,这时候Eureka 不能简单的将B从注册表中剔除,因为如果剔除了,A就无法从Eureka 服务器中获取B注册的服务,但是这时候B服务是可用的;
所以,Eureka的自我保护模式最好还是开启它。

关于自我保护常用几个配置如下:

分别修改2个Eureka服务服务端的application.properties文件增加一下配置

1
2
3
4
#关闭自我保护模式 作用是如果开启了自我保护模式以后,那么如果服务的提供者或消费者(Eureka的客户端)因为网络波动问题
#暂时失去了与服务器端的连接那么Eureka就会直接注销这个服务删除这个服务相关的数据,如果关闭了这个自我保护,Eureka只
#会先挂起这个服务,当网络恢复正常以后这个服务将自动的恢复
eureka.server.enable-self-preservation = false

分别修改服务提供者和服务消费项目的application.properties文件添加一下配置

1
2
3
4
#每间隔2s,向服务端发送一次心跳,证明自己依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=2
#告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我踢出掉
eureka.instance.lease-expiration-duration-in-seconds=10

配置

  • 导包
    Spring Cloud Discovery —>Eureka Server

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
  • 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #内嵌定时tomcat的端口
    server.port=9500

    #设置该服务注册中心的hostname
    eureka.instance.hostname=eureka9500

    #由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,
    #这个应用会向注册中心(也是它自己)注册它自己,设置为false表示禁止这种自己向自己注册的默认行为
    eureka.client.register-with-eureka=false

    #表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他服务
    eureka.client.fetch-registry=false

    #指定服务注册中心的位置(指向对方地址,多个server加逗号分割)
    eureka.client.service-url.defaultZone=http://localhost:9600/eureka
  • 主程序
    需要加入@EnableEurekaServer注释启动
    例子:

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaServer // 注册中心专用
    public class Application9500 {
    public static void main(String[] args) {
    SpringApplication.run(Application9500.class, args);
    }
    }

生产者

如果消费者需要轮询,服务者要相同服务名,用不同端口区分,并且注册中心的地址需要一致

  • 导包
    Spring Cloud Discovery —> Eureka Discovery Clients

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  • 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    #指定服务名字 这个名称将在服务消费者时被调用
    spring.application.name=003-provider

    #指定eureka的访问地址
    eureka.client.service-url.defaultZone=http://localhost:9500/eureka,http://localhost:9600/eureka

    #设置端口
    server.port=8081
  • Controller

    1
    2
    3
    4
    5
    6
    7
    @RestController
    public class ProviderController {
    @GetMapping("/hello")
    public String hello(){
    return "hello,集群模式使用,服务提供者向注册中心注册服务-8081";
    }
    }
  • 主程序

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableEurekaClient // 启动生产者模式
    public class Application8081 {

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

消费者 - Ribbon

主要作用Ribbon实现客户端负载均衡

Ribbon是一个基于HTTP 和 TCP 的客户端负载均衡器,当使用Ribbon对服务进行访问的时候,它会扩展Eureka客户端的服务发现功能,实现从Eureka注册中心中获取服务端列表,并通过Eureka客户端来确定服务端是否己经启动。Ribbon在Eureka客户端服务发现的基础上,实现了对服务实例的选择策略,从而实现对服务的负载均衡消费。

由于Spring Cloud Ribbon的封装, 我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步:

1、启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群。

2、服务消费者通过被@LoadBalanced注解修饰过的RestTemplate来调用服务提供者。

这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用。

策略

方法 解释
RandomRule 随机
RoundRobinRule 轮询
AvailabilityFilteringRule 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问;
WeightedResponseTimeRule 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略;
RetryRule 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务;
BestAvailableRule 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务;
ZoneAvoidanceRule 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务;

配置

导包依旧是 eureka-client

  • 配置文件

    1
    2
    3
    4
    5
    6
    #指定服务的名称 即使是服务的消费者也会将自己注册到注册中心
    spring.application.name=003-consumer
    #指定Eureka注册中的访问地址
    eureka.client.service-url.defaultZone=http://localhost:9500/eureka,http://localhost:9600/eureka

    server.port=8080
  • 需要一个RestConfig
    作用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Configuration
    public class RestConfig {
    @Bean
    @LoadBalanced //负载均衡
    public RestTemplate restTemplate(){
    return new RestTemplate();
    }

    //不设置的话默认轮训

    @Bean //随机策略实现负载均衡
    public IRule rule(){
    return new RandomRule();
    }
    }
  • Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RestController
    public class IndexController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/queryProduct")
    public String test(){
    String serviceUrl="http://003-provider/hello"; // 这里用到的是生产者名
    String result=restTemplate.getForObject(serviceUrl,String.class);
    return "集群模式,使用eureka调用结果:"+result;
    }
    }
  • 主程序

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableEurekaClient
    public class Application8080 {
    public static void main(String[] args) {
    SpringApplication.run(Application8080.class, args);
    }

    }

消费者 - Hystrix

服务熔断Hystrix 主要解决雪崩问题
还有个仪表盘能查看服务之间的情况

知识点

  • 引起雪崩效应常见场景
    硬件故障:如服务器宕机,机房断电,光纤被挖断等

    流量激增:如异常流量,重试加大流量等

    缓存击穿:一般发生在应用重启,所有缓存失效时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端服务,造成服务提供者超负荷运行,引起服务不可用

    程序 BUG:如程序逻辑导致内存泄漏,JVM 长时间 FullGC 等

    同步等待:服务间采用同步调用模式,同步等待造成的资源耗尽

  • 服务熔断
    熔断机制是应对雪崩效应的一种微服务链路保护机制,一般来说,每个服务都需要熔断机制的。如高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。在微服务架构中,熔断机制也是起着类似的作用。

  • 服务降级
    降级是指自己的待遇下降了,从 RPC 调用环节来讲,就是说整体资源快不够了,忍痛将某些服务单元先关掉,关闭后还要返回一些可处理的备选方法,待渡过难关,再开启回来

  • 熔断器的状态
    熔断器有三个状态 CLOSED 、 OPEN 、 HALF_OPEN 熔断器默认关闭状态。
    当触发熔断后状态变更为OPEN ,在等待到指定的时间,Hystrix会放请求检测服务是否开启,这期间熔断器会变为 HALF_OPEN 半开启状态,熔断探测服务可用则继续变更为 CLOSED 关闭熔断器。

配置

  • 导包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!-- 客户端 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <!--熔断器-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

    <!--仪表盘-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  • 配置文件

    1
    2
    3
    4
    5
    6
    #指定服务的名称 即使是服务的消费者也会将自己注册到注册中心
    spring.application.name=003-consumer
    #指定Eureka注册中的访问地址
    eureka.client.service-url.defaultZone=http://localhost:9500/eureka,http://localhost:9600/eureka

    server.port=8080
  • Controller
    注意:
    熔断的方法名可以不需要和主方法名保持一致
    但是错误的方法返回值类型必须和主方法返回值类型保持一致
    错误错误的方法参数类型必须和主方法的参数类型保持一致
    否则会报fallbackMethod找不到的异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @RestController
    public class IndexController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/queryProduct")
    //调用熔断方法,fallbackMethod方法名,commandProperties自定义熔断时间属性等(如果没有自定义,默认一秒)
    @HystrixCommand(fallbackMethod = "orderback",
    commandProperties={@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1500")})
    public String test(){
    String serviceUrl="http://003-provider/hello"; // 这里用到的是生产者名
    String result=restTemplate.getForObject(serviceUrl,String.class);
    return "集群模式,使用eureka调用结果:"+result;
    }


    //熔断方法,需要和调用主体方法的返回值和入参一致
    public String orderback(){
    return "抢购的人太多了,请稍后....";
    }
    }
  • 主程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @SpringBootApplication
    @EnableEurekaClient
    @EnableCircuitBreaker //启用熔断
    @EnableHystrixDashboard //启动熔断监控仪表盘
    public class Application8080 {

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

    }

    @Bean//创建仪表盘
    public ServletRegistrationBean getServlet(){
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/actuator/hystrix.stream"); // 这里指定的urlMapping 就是咱们在仪表盘中monitor Stream 的地址后缀
    registrationBean.setName("HystrixMetricsStreamServlet");
    return registrationBean;
    }

打开仪表盘的方法,例如:http://localhost:8080/hystrix

仪表盘中的输入框,输入本机地址+端口号+UrlMappings
例如:http://localhost:8080//actuator/hystrix.stream

右边框输入别名,然后回车,即可进入仪表盘

消费者 - Feign组件

整合了Ribbon 和 Hystrix,只需导一个包即可

主要好处,用方法调用而不是字符串拼接

  • 导包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--Feign组件-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  • 配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 指定服务的名称 即使是服务的消费者也会将自己注册到注册中心
    spring.application.name=003-consumer-feign

    # 指定Eureka注册中的访问地址
    eureka.client.service-url.defaultZone=http://localhost:9500/eureka,http://localhost:9600/eureka

    # 端口号
    server.port=9090

    # 开启熔断机制
    feign.hystrix.enabled=true

创建feign接口

1
2
3
4
5
6
7
8
// name:远程服务名/生产者服务名
// fallback:熔断器的类
@FeignClient(name = "003-provider",fallback = ProductFeignClientCallback.class)
public interface ProductFeignClient {

@GetMapping("/hello")//远程服务名的地址
String hello();
}

Controller

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class IndexController {
// 导入接口,接口调用方法
@Autowired
private ProductFeignClient feignClient;

@GetMapping("/queryProduct")
public String feign(){
return feignClient.hello()+":feign";
}
}

访问/queryProduct就可以调用远程的服务名

实现服务熔断

配置文件需要开启熔断机制
feign.hystrix.enabled=true

自定义一个FeignClient接口的实现类,这个类就是熔断降级触发的逻辑代码

1
2
3
4
5
6
7
@Component
public class ProductFeignClientCallback implements ProductFeignClient {
@Override
public String hello() {
return "产品服务正在升级中,暂时不可用,请耐心等待....";
}
}

在接口上添加熔断类型的注解
@FeignClient(fallback = ProductFeignClientCallback.class)

主程序

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //通过@EnableFeignClients注解开启Spring Cloud Feign的支持功能
public class Application9090 {

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

服务网关Zuul

Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的
核心是一系列的过滤器,这些过滤器可以完成以下功能:
动态路由:动态将请求路由到不同后端集群
压力测试:逐渐增加指向集群的流量,以了解性能
负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
静态响应处理:边缘位置进行响应,避免转发到内部集群
身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。Spring Cloud对Zuul进行了整合和增强。

导包

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>

主程序

开启Eureka客户端发现功能

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy//启用网关代理
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#eureka服务器的地址
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
#允许ip地址
eureka.instance.prefer-ip-address=true
#服务端口
server.port=8080
#当前网关服务的名字
spring.application.name=musicstore-zuul-server


#musicstore-albumservice的路由规则,所有以/api-album/打头的服务都转发到musicstore-albumservice
zuul.routes.api-album.path=/api-album/**
zuul.routes.api-album.service-id=musicstore-albumservice
#musicstore-orderservice的路由规则,所有以/api-album/打头的服务都转发到musicstore-albumservice
zuul.routes.api-order.path=/api-order/**
zuul.routes.api-order.service-id=musicstore-orderservice


#简化路由规则的映射(根据服务名直接映射)
#zuul.routes.musicstore-albumservice.path=/api-album/**
#zuul.routes.musicstore-orderservice.path=/api-order/**

例子: http://127.0.0.1:8080/api-album/album/2 等于 http://127.0.0.1:9001/album/2

配置过滤器

Zuul 中的过滤器跟我们之前使用的 javax.servlet.Filter 不一样,javax.servlet.Filter 只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。而 Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。

  1. PRE:
    这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

  2. ROUTING:
    这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

  3. POST:
    这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。

  4. ERROR:
    在其他阶段发生错误时执行该过滤器。Zuul提供了自定义过滤器的功能实现起来也十分简单,只需要编写一个类去实现zuul提供的接口

日志过滤器

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

//自定义日志过滤器
@Component
public class LogFilter extends ZuulFilter {
// 继承 ZuulFilter
@Override
public String filterType() {
// 选择pre属性
return "pre";
}

@Override
public int filterOrder() {
// 优先级0,最优先的意思
return 0;
}

@Override
public boolean shouldFilter() {
//设置true,否则过滤器不执行
return true;
}

@Override
public Object run() throws ZuulException {
//要做的事情
RequestContext context= RequestContext.getCurrentContext();
HttpServletRequest request=context.getRequest();
System.out.println("***请求地址="+request.getRequestURI());
System.out.println("***客户端ip="+request.getRemoteAddr());
System.out.println("***请求时间="+new Date());
return null;
}
}

身份验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Component
public class SecurityFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}

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

@Override
public boolean shouldFilter() {
//如果请求的路径中包含order,则执行身份验证,否则不执行
RequestContext context=RequestContext.getCurrentContext();
String url=context.getRequest().getRequestURI();
if(url.contains("order"))
return true;
else
return false;
}

@Override
public Object run() throws ZuulException {
RequestContext context= RequestContext.getCurrentContext();
//获取请求
HttpServletRequest request=context.getRequest();
HttpServletResponse response=context.getResponse();
//获取请求头

String header=request.getHeader("token");
System.out.println("用户身份="+header);
if(header!=null && !header.isEmpty()){
//用户传递了token,则继续执行
context.addZuulRequestHeader("token",header);
}
else{
//没有身份信息,则不能调用服务
context.setSendZuulResponse(false);
context.setResponseStatusCode(401); //未授权
response.setContentType("text/html;charset=utf-8");
context.setResponseBody("您没有登录,不能调用订单服务");
}
return null;
}
}

请求生命周期

  • 正常情况下所有的请求都是按照pre、routing、post的顺序来执行,然后由post返回response
  • 在pre阶段,如果有自定义的过滤器则执行自定义的过滤器
  • pre、routing、post的任意一个阶段如果抛异常了,则执行error过滤器

异常过滤器

禁用zuul默认的异常处理SendErrorFilter过滤器,然后自定义我们自己的Errorfilter过滤器

1
zuul.SendErrorFilter.error.disable=true

当zuul服务中某个过滤器出现异常则会执行自定义异常过滤器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

//自定义异常过滤器
@Component
public class ErrorFilter extends SendErrorFilter {

@Override
public Object run() {
String msg="请求失败!";
try{
RequestContext ctx = RequestContext.getCurrentContext();
ExceptionHolder exception = findZuulException(ctx.getThrowable());
System.out.println("错误信息:"+exception.getErrorCause());
msg+="error:"+exception.getErrorCause();
HttpServletResponse response = ctx.getResponse();
response.setContentType("text/html;charset=utf-8");
response.getWriter().println(msg);
}catch (Exception ex) {
ex.printStackTrace();
ReflectionUtils.rethrowRuntimeException(ex);
}
return msg;
}
}

设置统一的网关异常处理页面
定义控制器,实现ErrorController接口,返回错误页面,并编写视图内容,但是要设置zuul.SendErrorFilter.error.disable=false才能生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
//设置统一的网关异常处理页面
@RestController
public class GloballErrorController implements ErrorController {
@Override
public String getErrorPath() {
return "error";
}

@RequestMapping("/error")
public String error(){
return "服务出错了,请联系管理员";
}
}