接上篇《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的依赖即可。
三、嵌入式Zuul反向代理
1、Zuul反向代理的实例
按照官方文档上的讲解,大家可能会一头雾水,所以我们先动手敲一个Zuul的实例,有一个初步的理解,就能明白。
在工作空间中新建一个名为“microserver-getaway-zuul”的Maven工程:
然后在POM文件中引入Spring Cloud的父工程、“zuul”和“eureka”的依赖:
其中引入“eureka”是因为Zuul starter不包含服务发现客户端,所以想要使用服务发现功能,需要提供一个服务发现客户端(这里我们使用的是Eureka)。
注意,这里为了便于版本统一管理,该工程的parent父工程和我们User、Movie工程一样,均依赖于microserver-spring-cloud工程(此父工程统一引入了spring-cloud-dependencies的Finchley.SR2版,这个在前面的章节已经讲过)。
父工程pom.xml的modules中别忘记加入这个新工程(microserver-getaway-zuul):
然后我们新建启动类,在启动类中,添加“@EnableZuulProxy”注解,以开启Zuul的代理功能:
这里注意:Finchley.RELEASE版本的SpringCloud不需要@EnableEurekaClient注解就可以启用注册功能。在SpringBoot的自动配置类里启用了注册功能,只要引了eureka-client的依赖就会进行注册。
然后@EnableZuulProxy是一个组合注解,打开其源码:
可以看到是有Hystrix的启动注解“@EnableCircuitBreaker”的,这就进一步说明了Zuul中的所有请求都在Hystrix Command中执行。
然后在resource文件夹下创建application.yml,配置一下应用名和端口,然后将该服务配置到Eureka上:
创建完之后的完整工程结构如下:
在我们给Zuul不配置反向代理参数的情况下,启动Eureka、User以及Zuul工程:
然后我们在Eureka Server主页看到Zuul和User工程都已经注册上来了:
我们直接访问User的findById服务:
此时可以访问成功。然后我们这时候使用Zuul来通过User的ServiceId来直接访问其findById服务(http://localhost:8040/microserver-provider-user/findById/1):
发现也可以访问成功!这是为啥呢?此时我们去编译器的控制台查看,生成了一段日志:
这一段的详情为:
可以发现此请求触发了Ribbon的“DynamicServerListLoadBalancer”,即客户端的静态服务负载均衡服务,这里根据ServiceId获取到其ip和端口,然后去访问该服务,此时Zuul就实现了一个反向代理的功能,通过Zuul服务可以访问注册在Eureka上的任意一个服务。
上面同时也说明了Zuul的代理是使用Ribbon通过服务发现来定位后端服务实例的。
Zuul是默认代理所有注册在Eureka的服务的,如果我们不想代理所有服务,需要在配置文件中使用zuul.ignored-services参数来避免自动添加服务,然后通过zuul.routes来添加指定代理的微服务,例如在application.yml中添加:
这里的配置实现了两点:
(1)阻止Zuul自动代理所有注册在Eureka上的服务。
(2)设置所有在ServiceId为“microserver-provider-user”的微服务的代理地址变为“/user/**”,即原本的访问地址为“http://localhost:8040/microserver-provider-user/findById/1”,设置之后可以访问简写地址“http://localhost:8040/user/findById/1”。
我们配置上述参数后重启Zuul服务,发现是可以的:
我们也可以单独指定路径和service ID来设置反向代理:
这里的ServiceId是代理的微服务的实例ID,path是代理的ServiceId微服务对应的访问路径,它是一个ant风格的表达式,所以/user/*仅仅匹配一层目录,而/user/**可以匹配任意多层级目录。
如果不指定ServiceId,也可以指定为具体服务的url:
我们重启Zuul试一下:
代理访问成功。
但是这种配置情况下,url不会在HystrixCommand执行,也不会使用Ribbon负载均衡。如果要使用的话,可以指定一个服务器列表的serviceId,如下:
另一个方法是指定一个服务路由并且为serviceId配置Ribbon客户端(这么做需要在Ribbon中禁用Eureka),如下:
最后,我们可以使用正则表达式来配置路由规则。如下:
在上面的例子中如果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高级视频》