接上篇《​​24.API Gateway简介​​》  Spring Cloud版本为Finchley.SR2版

上一篇我们简单介绍了API Gateway即API网关是什么,以及其出现的意义和优缺点,本篇我们介绍Spring Cloud的微服务API网关组件Zuul。
本部分官方文档:https://cloud.spring.io/spring-cloud-static/Finchley.SR4/single/spring-cloud.html#_router_and_filter_zuul
注:好像Finchley.SR2的文档已经挂了,最新的是Finchley.SR4的文档。

一、什么是zuul

“zuul”翻译过来的意思是“祖鲁”,是一种怪兽,我们可以理解成小时候看数码宝贝里面的“加鲁鲁兽”(哈哈哈),本意是和咱们的网关好像没啥联系(个人认为就是把网关比喻成了一个看门狗吧)?

在官方文档中,将Zuul加了两个修饰词“Router and Filter”,即“路由”和“过滤”,得确,这两个特点正是API网关的主要功能。

下面是官方文档对于Zuul的一些介绍:
Zuul是Netflix的基于JVM的路由器和服务器端的负载均衡器。

Netflix使用Zuul来进行以下下操作:
验证、压力测试、动态路由、服务迁移、减载、安全、静态响应处理以及主动的流量管理等。

Zuul的规则引擎允许使用任何的JVM语言来编写路由规则和过滤器,目前内置支持Java和Groovy。

Zuul中的所有请求都在Hystrix Command中执行。所以当断路器打开时,代理将不会重试连接后端服务。

Zuul中,所有路由默认的Hystrix的隔离模式为SEMAPHORE,可以使用“zuul.ribbonIsolationStrategy”参数改为其他的隔离策略(如THREAD)。
回顾:
THREAD(线程隔离):使用该方式,HystrixCommand将会在单独的线程上执行,并发请求受线程池中线程数量的限制。
SEMAPHORE(信号量隔离):使用该方式,HystrixCommand将会在调用线程上执行,开销相对较小,并发请求受到信号量个数的限制。

二、如何使用Zuul

在Spring Cloud中应用Zuul组件,在POM文件中引入spring-cloud-starter-netflix-zuul的依赖即可。

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

三、嵌入式Zuul反向代理

1、Zuul反向代理的实例

按照官方文档上的讲解,大家可能会一头雾水,所以我们先动手敲一个Zuul的实例,有一个初步的理解,就能明白。

在工作空间中新建一个名为“microserver-getaway-zuul”的Maven工程:

【Spring Cloud总结】25.Zuul简介及代码示例_spring

【Spring Cloud总结】25.Zuul简介及代码示例_zuul_02


然后在POM文件中引入Spring Cloud的父工程、“zuul”和“eureka”的依赖:

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>com.microserver.zuul</groupId>
<artifactId>microserver-getaway-zuul</artifactId>
<name>microserver-getaway-zuul</name>

<parent>
<groupId>com.microserver.cloud</groupId>
<artifactId>microserver-spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<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-server</artifactId>
</dependency>
</dependencies>

</project>

其中引入“eureka”是因为Zuul starter不包含服务发现客户端,所以想要使用服务发现功能,需要提供一个服务发现客户端(这里我们使用的是Eureka)。

注意,这里为了便于版本统一管理,该工程的parent父工程和我们User、Movie工程一样,均依赖于microserver-spring-cloud工程(此父工程统一引入了spring-cloud-dependencies的Finchley.SR2版,这个在前面的章节已经讲过)。

父工程pom.xml的modules中别忘记加入这个新工程(microserver-getaway-zuul):

<modules>
<module>microserver-provider-user</module>
<module>microserver-consumer-movie</module>
<module>microserver-discovery-eureka</module>
<module>microserver-discovery-eureka-high-availability</module>
<module>microserver-hystrix-dashboard</module>
<module>microserver-hystrix-dashboard-turbine</module>
<module>microserver-getaway-zuul</module>
</modules>

然后我们新建启动类,在启动类中,添加“@EnableZuulProxy”注解,以开启Zuul的代理功能:

package com.microserver.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;


@SpringBootApplication
@EnableZuulProxy
public class MircoserverGateWayZuulApplication {
public static void main(String[] args) {
SpringApplication.run(MircoserverGateWayZuulApplication.class, args);
}
}

这里注意:Finchley.RELEASE版本的SpringCloud不需要@EnableEurekaClient注解就可以启用注册功能。在SpringBoot的自动配置类里启用了注册功能,只要引了eureka-client的依赖就会进行注册。

然后@EnableZuulProxy是一个组合注解,打开其源码:

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

可以看到是有Hystrix的启动注解“@EnableCircuitBreaker”的,这就进一步说明了Zuul中的所有请求都在Hystrix Command中执行。

然后在resource文件夹下创建application.yml,配置一下应用名和端口,然后将该服务配置到Eureka上:

spring:
application:
name: microserver-getaway-zuul
server:
port: 8040
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@eureka1:8761/eureka
instance:
prefer-ip-address: true

创建完之后的完整工程结构如下:

【Spring Cloud总结】25.Zuul简介及代码示例_springcloud_03

在我们给Zuul不配置反向代理参数的情况下,启动Eureka、User以及Zuul工程:

【Spring Cloud总结】25.Zuul简介及代码示例_spring_04


然后我们在Eureka Server主页看到Zuul和User工程都已经注册上来了:

【Spring Cloud总结】25.Zuul简介及代码示例_zuul_05

我们直接访问User的findById服务:

【Spring Cloud总结】25.Zuul简介及代码示例_springcloud_06


此时可以访问成功。然后我们这时候使用Zuul来通过User的ServiceId来直接访问其findById服务(http://localhost:8040/microserver-provider-user/findById/1):

【Spring Cloud总结】25.Zuul简介及代码示例_@EnableZuulProxy_07


发现也可以访问成功!这是为啥呢?此时我们去编译器的控制台查看,生成了一段日志:

【Spring Cloud总结】25.Zuul简介及代码示例_zuul_08


这一段的详情为:

c.n.l.DynamicServerListLoadBalancer:
DynamicServerListLoadBalancer for client microserver-provider-user initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=microserver-provider-user,current list of Servers=[192.168.3.1:7900],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:192.168.3.1:7900; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]

 

可以发现此请求触发了Ribbon的“DynamicServerListLoadBalancer”,即客户端的静态服务负载均衡服务,这里根据ServiceId获取到其ip和端口,然后去访问该服务,此时Zuul就实现了一个反向代理的功能,通过Zuul服务可以访问注册在Eureka上的任意一个服务。
上面同时也说明了Zuul的代理是使用Ribbon通过服务发现来定位后端服务实例的。

Zuul是默认代理所有注册在Eureka的服务的,如果我们不想代理所有服务,需要在配置文件中使用zuul.ignored-services参数来避免自动添加服务,然后通过zuul.routes来添加指定代理的微服务,例如在application.yml中添加:

zuul:
ignored-services: '*'
routes:
microserver-provider-user: /user/**

这里的配置实现了两点:
(1)阻止Zuul自动代理所有注册在Eureka上的服务。
(2)设置所有在ServiceId为“microserver-provider-user”的微服务的代理地址变为“/user/**”,即原本的访问地址为“http://localhost:8040/microserver-provider-user/findById/1”,设置之后可以访问简写地址“http://localhost:8040/user/findById/1”。

我们配置上述参数后重启Zuul服务,发现是可以的:

【Spring Cloud总结】25.Zuul简介及代码示例_springcloud_09

我们也可以单独指定路径和service ID来设置反向代理:

zuul:
routes:
user:
path: /user/**
serviceId: microserver-provider-user

这里的ServiceId是代理的微服务的实例ID,path是代理的ServiceId微服务对应的访问路径,它是一个ant风格的表达式,所以/user/*仅仅匹配一层目录,而/user/**可以匹配任意多层级目录。

如果不指定ServiceId,也可以指定为具体服务的url:

zuul:
routes:
user:
path: /user-url/**
url: http://192.168.3.1:7900

我们重启Zuul试一下:

【Spring Cloud总结】25.Zuul简介及代码示例_springcloud_10


代理访问成功。

但是这种配置情况下,url不会在HystrixCommand执行,也不会使用Ribbon负载均衡。如果要使用的话,可以指定一个服务器列表的serviceId,如下:

zuul:
routes:
echo:
path: /user-url/**
serviceId: microserver-provider-user
stripPrefix: true

hystrix:
command:
myusers-service:
execution:
isolation:
thread:
timeoutInMilliseconds: ...

myusers-service:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
listOfServers: http://192.168.3.1:7900,http://192.168.3.1:7901
ConnectTimeout: 1000
ReadTimeout: 3000
MaxTotalHttpConnections: 500
MaxConnectionsPerHost: 100

另一个方法是指定一个服务路由并且为serviceId配置Ribbon客户端(这么做需要在Ribbon中禁用Eureka),如下:

zuul:
routes:
users:
path: /user-url/**
serviceId: microserver-provider-user

ribbon:
eureka:
enabled: false

users:
ribbon:
listOfServers: http://192.168.3.1:7900,http://192.168.3.1:7901

最后,我们可以使用正则表达式来配置路由规则。如下:

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}

在上面的例子中如果serviceId为myusers-v1那么它将被映射到/v1/myusers/**。如果有一个serviceId不匹配,那么将会使用默认规则。例如,在上面的例子中一个serviceId为microserver-provider-user的服务将会映射到"/microserver-provider-user/**"。

2、Zuul反向代理的总结

经过刚才的实例编写,大家应该对Zuul的反向理解有一个基本的概念,这里我们跟着官方文档顺应着梳理一遍Zuul反向代理的知识点(下面的知识点在上面的实例中全部都有体现)。

(1)Spring Cloud创建了一个内置Zuul代理来简化开发,可以令一个应用使用代理调用后端的一个或者多个服务。

(2)在Spring Boot的入口类上使用@EnableZuulProxy注解来开启代理。

(3)代理使用Ribbon通过服务发现来定位后端服务实例。

(4)Zuul的所有请求在Hystrix Command中执行。所以当断路器打开时,代理将不会重试连接后端服务。

(5)Zuul starter不包含服务发现客户端,所以想要使用服务发现功能,需要提供一个服务发现客户端(比如Eureka)。

(6)配置方面:
为了防止自动添加服务,可以设置zuul.ignored-services参数来避免。如果一个服务匹配到一个忽略表达式,并且又在路由映射中明确指定了,那么它就不会被忽略。例如(application.yml):
zuul:
  ignored-services: '*'
  routes:
    microserver-provider-user: /user/**
也可以单独指定路径和service ID,例如(application.yml):
zuul:
  routes:
    user:
      path: /user/**
      serviceId: microserver-provider-user
其中path是一个ant风格的表达式,所以/user/*仅仅匹配一层目录,而/user/**可以匹配任意多层级目录。

其中后端服务的位置既可以使用serviceId也可以使用url(物理位置)指定。如下(application.yml):
zuul:
  routes:
    user:
      path: /user-url/**
      url: http://192.168.3.1:7900
这些简单的url路由不会作为HystrixCommand执行,也不会使用Ribbon负载均衡。如果要使用的话,可以指定一个服务器列表的serviceId,或是指定一个服务路由并且为serviceId配置Ribbon客户端(这么做需要在Ribbon中禁用Eureka)。

最后,可以使用正则表达式来配置路由规则。
    

参考:《51CTO学院Spring Cloud高级视频》