个人随笔
目录
六、微服务网关-Zuul
2020-09-11 22:47:07

现在我们来研究下SpringCloud中Zuul网关的使用,当然之前得说明下为什么要用Zuul网关,毕竟都已经有注册中心中有每个服务的别名了,通过Feign已经完美的调用了,根本不需要Zuul,下面我们就来分析下。

一、为什么要Zuul网关

  1. 在微服务中,若是服务暴露出去给外部访问调用,那么因为外部是不可能跟我们的注册中心有关系的,此时用Feign调用就将失效,那么我们能做的就是告诉外部ip、端口、服务请求路径。
  2. 在外部调用的时候,我们可能需要进行身份认证,此时我们能做的就是在每个微服务中进行权限认证什么的,每个微服务都一套,这样的话不科学。
  3. 并且因为每个都是不同的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
  1. ###服务提供者启动端口
  2. server:
  3. port: 8001
  4. ###服务名称(服务注册到eureka名称)
  5. spring:
  6. application:
  7. name: app-suibibk-zuul-a
  8. ###服务注册到eureka地址
  9. eureka:
  10. client:
  11. service-url:
  12. defaultZone: http://localhost:8000/eureka
  13. register-with-eureka: true
  14. fetch-registry: true
  1. @RestController
  2. public class AController {
  3. @Value("${server.port}")
  4. private String serverPort;
  5. @RequestMapping("/a")
  6. public String a() {
  7. return "我是a服务"+serverPort;
  8. }
  9. }
服务b
  1. ###服务提供者启动端口
  2. server:
  3. port: 8002
  4. ###服务名称(服务注册到eureka名称)
  5. spring:
  6. application:
  7. name: app-suibibk-zuul-b
  8. ###服务注册到eureka地址
  9. eureka:
  10. client:
  11. service-url:
  12. defaultZone: http://localhost:8000/eureka
  13. register-with-eureka: true
  14. fetch-registry: true
  1. @RestController
  2. public class BController {
  3. @RequestMapping("/b")
  4. public String a() {
  5. return "我是b服务";
  6. }
  7. }

浏览器下面两个连接保证服务正常
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/ahttp://127.0.0.1:8002/b 请求,但是这样的话,需要知道具体的ip,并且比较难做本地的负载均衡(提醒一下:服务之间的调用用Feign默认开启本地负载均衡的),当然Zuul 默认开启了 Ribbon本地负载均衡功能。如果要进行token验证,也就是参数上是否传递token,那么我们可能需要在服务a和服务b中都进行校验。下面用Zuul解决这些问题,项目结构:

其实SpringCloud中建立项目无外乎就是引入依赖,修改配置文件,启动类开启需要的功能什么的。

a、pom.xml
  1. <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">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>cn.myforever</groupId>
  4. <artifactId>zuul-gateway</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-parent</artifactId>
  9. <version>2.0.0.RELEASE</version>
  10. </parent>
  11. <!-- 管理依赖 -->
  12. <dependencyManagement>
  13. <dependencies>
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-dependencies</artifactId>
  17. <version>Finchley.M7</version>
  18. <type>pom</type>
  19. <scope>import</scope>
  20. </dependency>
  21. </dependencies>
  22. </dependencyManagement>
  23. <dependencies>
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  27. </dependency>
  28. <!-- SpringBoot整合eureka客户端 -->
  29. <dependency>
  30. <groupId>org.springframework.cloud</groupId>
  31. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  32. </dependency>
  33. </dependencies>
  34. <!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
  35. <repositories>
  36. <repository>
  37. <id>spring-milestones</id>
  38. <name>Spring Milestones</name>
  39. <url>https://repo.spring.io/libs-milestone</url>
  40. <snapshots>
  41. <enabled>false</enabled>
  42. </snapshots>
  43. </repository>
  44. </repositories>
  45. </project>

主要是引入了如下依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  4. </dependency>
b、application.yml
  1. ###注册 中心
  2. eureka:
  3. client:
  4. serviceUrl:
  5. defaultZone: http://localhost:8000/eureka/
  6. server:
  7. port: 80
  8. ###网关名称
  9. spring:
  10. application:
  11. name: app-suibibk-zuul-gateway
  12. zuul:
  13. routes:
  14. #这个名字可以顺便取
  15. api-a:
  16. ### 以 /api-a/访问转发到a服务
  17. path: /api-a/**
  18. serviceId: app-suibibk-zuul-a
  19. #这个名字可以顺便取
  20. api-b2:
  21. ### 以 /b/访问转发到b服务
  22. path: /api-b/**
  23. serviceId: app-suibibk-zuul-b

这里用80端口,主要是看zuul的配置路由。

  1. 上面这段配置表示:/api-ar/开头的url请求,将转发到app-suibibk-zuul-a这个微服务上,/api-b/开头的url请求,将转发到app-suibibk-zuul-b这个微服务上。
c、过滤器MyFilter.java
  1. /**
  2. * 拦截用户是否有token,没有token就直接报错
  3. * @author forever
  4. *
  5. */
  6. @Component
  7. public class MyFilter extends ZuulFilter{
  8. @Override
  9. public Object run() throws ZuulException {
  10. //获取上下文
  11. RequestContext context = RequestContext.getCurrentContext();
  12. HttpServletRequest reqeust = context.getRequest();
  13. //获取token
  14. String token =reqeust.getParameter("token");
  15. if(token==null) {
  16. context.setSendZuulResponse(false);
  17. context.setResponseStatusCode(403);
  18. context.setResponseBody("token is null");
  19. //return null;
  20. }
  21. System.out.println("执行正常逻辑");
  22. return null;
  23. }
  24. @Override
  25. public boolean shouldFilter() {
  26. // TODO Auto-generated method stub
  27. return true;
  28. }
  29. @Override
  30. public int filterOrder() {
  31. // TODO Auto-generated method stub
  32. return 0;
  33. }
  34. @Override
  35. public String filterType() {
  36. // TODO Auto-generated method stub
  37. return "pre";
  38. }
  39. }

主要关注的是如下几个方法

run()

这里做权限验证逻辑,只要能执行完就表示通过。若是不通过则用如下代码来提醒用户

  1. context.setSendZuulResponse(false);
  2. context.setResponseStatusCode(403);
  3. 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。分别代表前置过滤,后置过滤和异常过滤。

  1. 如果你的filterpre,就是指请求先进入prefilter类,你可以进行一些权限认证,日志记录,或者额外给Request增加一些属性供后续的filter使用。pre会优先按照order从小到大执行,然后再去执行请求转发到业务服务。
  2. 如果你的filterpost,那么就会执行完被路由的业务服务后,再进入postfilter,在postfilter里,一般做一些日志记录,或者额外增加response属性什么的。
  3. 如果你的filtererror,如果在上面的任何一个地方出现了异常,就会进入到typeerrorfilter
d、启动类AppZuulGateway.java
  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. @EnableZuulProxy
  4. public class AppZuulGateway {
  5. //默認開啓ribbon负载均衡
  6. public static void main(String[] args) {
  7. SpringApplication.run(AppZuulGateway.class, args);
  8. }
  9. }

@EnableZuulProxy表示开启Zuul网关代理功能

3、启动Zuul网关,测试

当注册中心Eureka,服务a,服务b,Zuul网关都启动后,此时我们可以访问如下链接测试
1、http://127.0.0.1/api-a/ahttp://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、不同点

  1. Nginx--c语言开发
  2. Zuul--java语言开发
  3. Zuul负载均衡实现:采用ribbon+eureka实现本地负载均衡
  4. Nginx负载均衡实现:采用服务器实现负载均衡
  5. Nginx相比zuul功能会更加强大,因为Nginx整合一些脚本语言(Nginx+lua
  6. Nginx适合于服务器端负载均衡
  7. Zuul适合微服务中实现网关

结语

到此,我们就基本上学会了Zuul网关的使用,当然这里只是入门使用,真正想了解深入的话,还需要继续深入研究。暂时来说会用即可,有时间再去深入研究做笔记。

 383

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2