现在我们来研究下SpringCloud中Zuul网关的使用,当然之前得说明下为什么要用Zuul网关,毕竟都已经有注册中心中有每个服务的别名了,通过Feign已经完美的调用了,根本不需要Zuul,下面我们就来分析下。
一、为什么要Zuul网关
在微服务中,若是服务暴露出去给外部访问调用,那么因为外部是不可能跟我们的注册中心有关系的,此时用Feign调用就将失效,那么我们能做的就是告诉外部ip、端口、服务请求路径。在外部调用的时候,我们可能需要进行身份认证,此时我们能做的就是在每个微服务中进行权限认证什么的,每个微服务都一套,这样的话不科学。并且因为每个都是不同的ip,所以存在跨域请求的问题。
上面随便列举了三个问题,SpringCloud微服务框架中的Zuul技术完美的解决了上面的问题,我们只需要调用同一个ip即可。
当然这里需要说明的是,内部服务与服务之间的调用,当然还是通过Feign,虽然说网关也是注册到注册中心的,但是没有必要再加一层,浪费性能。Zuul一般都是用于外部调用我们的微服务。
二、Zuul网关
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
三、实战
下面我们实现一个例子,两个服务:127.0.0.1:8001/a 和 127.0.0.1:8002/a
我们通过网关直接统一访问127.0.0.1/api-a/a 和127.0.0.1/api-b/b 来访问,并且在网关中做登录验证。
1、环境准备
建立三个项目:注册中心、服务a、服务b,然后启动。相信这部通过前面的博文三、服务治理SpringCloud Eureka入门实战
下面简单的列一下服务a和b的配置:
服务a
###服务提供者启动端口server:port: 8001###服务名称(服务注册到eureka名称)spring:application:name: app-suibibk-zuul-a###服务注册到eureka地址eureka:client:service-url:defaultZone: http://localhost:8000/eurekaregister-with-eureka: truefetch-registry: true
@RestControllerpublic class AController {@Value("${server.port}")private String serverPort;@RequestMapping("/a")public String a() {return "我是a服务"+serverPort;}}
服务b
###服务提供者启动端口server:port: 8002###服务名称(服务注册到eureka名称)spring:application:name: app-suibibk-zuul-b###服务注册到eureka地址eureka:client:service-url:defaultZone: http://localhost:8000/eurekaregister-with-eureka: truefetch-registry: true
@RestControllerpublic class BController {@RequestMapping("/b")public String a() {return "我是b服务";}}
浏览器下面两个连接保证服务正常
http://127.0.0.1:8001/a 返回:我是a服务8001
http://127.0.0.1:8002/b 返回:我是b服务
2、建立Zuul网关项目
上面服务已经正常启动了,加入我作为一个外部系统,需要访问调用的话,我应该会用http发起http://127.0.0.1:8001/a 和 http://127.0.0.1:8002/b 请求,但是这样的话,需要知道具体的ip,并且比较难做本地的负载均衡(提醒一下:服务之间的调用用Feign默认开启本地负载均衡的),当然Zuul 默认开启了 Ribbon本地负载均衡功能。如果要进行token验证,也就是参数上是否传递token,那么我们可能需要在服务a和服务b中都进行校验。下面用Zuul解决这些问题,项目结构:
其实SpringCloud中建立项目无外乎就是引入依赖,修改配置文件,启动类开启需要的功能什么的。
a、pom.xml
<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>cn.myforever</groupId><artifactId>zuul-gateway</artifactId><version>0.0.1-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.RELEASE</version></parent><!-- 管理依赖 --><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Finchley.M7</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency><!-- SpringBoot整合eureka客户端 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency></dependencies><!-- 注意: 这里必须要添加, 否者各种依赖有问题 --><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/libs-milestone</url><snapshots><enabled>false</enabled></snapshots></repository></repositories></project>
主要是引入了如下依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency>
b、application.yml
###注册 中心eureka:client:serviceUrl:defaultZone: http://localhost:8000/eureka/server:port: 80###网关名称spring:application:name: app-suibibk-zuul-gatewayzuul:routes:#这个名字可以顺便取api-a:### 以 /api-a/访问转发到a服务path: /api-a/**serviceId: app-suibibk-zuul-a#这个名字可以顺便取api-b2:### 以 /b/访问转发到b服务path: /api-b/**serviceId: app-suibibk-zuul-b
这里用80端口,主要是看zuul的配置路由。
上面这段配置表示:/api-ar/开头的url请求,将转发到app-suibibk-zuul-a这个微服务上,/api-b/开头的url请求,将转发到app-suibibk-zuul-b这个微服务上。
c、过滤器MyFilter.java
/*** 拦截用户是否有token,没有token就直接报错* @author forever**/@Componentpublic class MyFilter extends ZuulFilter{@Overridepublic Object run() throws ZuulException {//获取上下文RequestContext context = RequestContext.getCurrentContext();HttpServletRequest reqeust = context.getRequest();//获取tokenString token =reqeust.getParameter("token");if(token==null) {context.setSendZuulResponse(false);context.setResponseStatusCode(403);context.setResponseBody("token is null");//return null;}System.out.println("执行正常逻辑");return null;}@Overridepublic boolean shouldFilter() {// TODO Auto-generated method stubreturn true;}@Overridepublic int filterOrder() {// TODO Auto-generated method stubreturn 0;}@Overridepublic String filterType() {// TODO Auto-generated method stubreturn "pre";}}
主要关注的是如下几个方法
run()
这里做权限验证逻辑,只要能执行完就表示通过。若是不通过则用如下代码来提醒用户
context.setSendZuulResponse(false);context.setResponseStatusCode(403);context.setResponseBody("token is null");
shouldFilter()
返回结果表这个过滤器是否生效,true代表生效,false代表不生效。那么什么情况下使用不生效呢,不生效干嘛还要写这个filter类呢?其实是有用的,有时我们会动态的决定让不让一个filter生效,譬如我们可能根据Request里是否携带某个参数来判断是否需要生效,或者我们需要从上一个filter里接收某个数据来决定,再或者我们希望能手工控制是否生效(使用如Appolo之类的配置中心,来动态设置该字段)。
filterOrder()
过滤器的执行顺序。当请求在一个阶段的时候存在多个多个过滤器时,需要根据该方法的返回值依次执行。
filterType()
| 返回值 | 作用 |
|---|---|
| pre | 该类型的filters在Request routing到源web-service之前执行。用来实现Authentication、选择源服务地址等,简言之,就是请求之前操作 |
| routing | 该类型的filters用于把Request routing到源web-service,源web-service是实现业务逻辑的服务。这里使用HttpClient请求web-service。(不是很理解,以后再研究) |
| post | 该类型的filters在路由返回Response后执行。用来实现对Response结果进行修改,收集统计数据以及把Response传输会客户端。 |
| error | 上面三个过程中任何一个出现错误都交由error类型的filters进行处理。 |
主要关注 pre、post和error。分别代表前置过滤,后置过滤和异常过滤。
如果你的filter是pre,就是指请求先进入pre的filter类,你可以进行一些权限认证,日志记录,或者额外给Request增加一些属性供后续的filter使用。pre会优先按照order从小到大执行,然后再去执行请求转发到业务服务。如果你的filter是post,那么就会执行完被路由的业务服务后,再进入post的filter,在post的filter里,一般做一些日志记录,或者额外增加response属性什么的。如果你的filter是error,如果在上面的任何一个地方出现了异常,就会进入到type为error的filter中
d、启动类AppZuulGateway.java
@SpringBootApplication@EnableEurekaClient@EnableZuulProxypublic class AppZuulGateway {//默認開啓ribbon负载均衡public static void main(String[] args) {SpringApplication.run(AppZuulGateway.class, args);}}
@EnableZuulProxy表示开启Zuul网关代理功能
3、启动Zuul网关,测试
当注册中心Eureka,服务a,服务b,Zuul网关都启动后,此时我们可以访问如下链接测试
1、http://127.0.0.1/api-a/a 和http://127.0.0.1/api-b/b 返回token is null ,表示Zuul网关拦截请求成功。
2、http://127.0.0.1/api-a/a?token=123 返回我是a服务8001
3、http://127.0.0.1/api-b/b?token=123 返回我是b服务
到此,搭建zull网关成功。
四、Nginx与Zuul的区别
1、相同点
Zuul和Nginx都可以实现负载均衡、反向代理(隐藏真实ip地址),过滤请求,实现网关的效果。
2、不同点
Nginx--c语言开发Zuul--java语言开发Zuul负载均衡实现:采用ribbon+eureka实现本地负载均衡Nginx负载均衡实现:采用服务器实现负载均衡Nginx相比zuul功能会更加强大,因为Nginx整合一些脚本语言(Nginx+lua)Nginx适合于服务器端负载均衡Zuul适合微服务中实现网关
结语
到此,我们就基本上学会了Zuul网关的使用,当然这里只是入门使用,真正想了解深入的话,还需要继续深入研究。暂时来说会用即可,有时间再去深入研究做笔记。
