[关闭]
@coldxiangyu 2017-06-13T17:23:32.000000Z 字数 4329 阅读 2684

Spring Cloud(四、Zuul 服务网关)

WEB框架


前面我们已经介绍了使用Spring Cloud Netflix中的Eureka注册中心进行服务注册与发现,服务间通过Ribbon,Feign进服务消费以及负载均衡,Hystrix断路器,Spring Cloud Config进行统一配置管理。
在实际应用过程中,我们内部微服务往往是不对外暴露的,需要提供专门的对外服务。这时候难以做到已有服务的复用,也没有一个统一的访问权限控制,因此整个服务需要一个大门,也就是我们这篇文章要讲的服务网关Zuul
Zuul是整个微服务架构重要的组成部分,它负责对外提供统一的REST API,可以对路由策略进行调整,实现负载均衡,还具备权限控制等功能。将一些复杂的非业务逻辑前移,使得服务集群本身具备高可用。
看完这些,你会不会把Zuul联系到Nginx?实际上,Zuul就是Netflix版的Nginx,不过实现方式不同。而Zuul本身是基于Servlet的过滤器的集合,再加上与Spring boot整合之后,处理请求的速度可能没有nginx这种简单设计的来得快,但确实可以作为一款不错的nginx替代品。
下面我们来看一下Zuul的基本用法:
新建模块zuul-gateway,pom配置如下:

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>1.3.5.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <properties>
  8. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  9. <java.version>1.8</java.version>
  10. </properties>
  11. <dependencies>
  12. <dependency>
  13. <groupId>org.springframework.cloud</groupId>
  14. <artifactId>spring-cloud-starter-zuul</artifactId>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.cloud</groupId>
  18. <artifactId>spring-cloud-starter-eureka</artifactId>
  19. </dependency>
  20. </dependencies>
  21. <dependencyManagement>
  22. <dependencies>
  23. <dependency>
  24. <groupId>org.springframework.cloud</groupId>
  25. <artifactId>spring-cloud-dependencies</artifactId>
  26. <version>Brixton.RELEASE</version>
  27. <type>pom</type>
  28. <scope>import</scope>
  29. </dependency>
  30. </dependencies>
  31. </dependencyManagement>

应用主类使用@EnableZuulProxy注解开启Zuul:

  1. @EnableZuulProxy
  2. @SpringCloudApplication
  3. public class Application {
  4. public static void main(String[] args) {
  5. new SpringApplicationBuilder(Application.class).web(true).run(args);
  6. }
  7. }

其中,@SpringCloudApplication注解整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker,相当于三个注解的集合。

配置application.properties中配置Zuul应用的基础信息,如:应用名、服务端口等。

  1. spring.application.name=api-gateway
  2. server.port=5555

只是配置这些还远远不够,我们还要在这里配置路由策略,路由的映射有两种配置方式:
一种是通过跳转url:

  1. # routes to url
  2. zuul.routes.api-a-url.path=/api-a-url/**
  3. zuul.routes.api-a-url.url=http://localhost:2222/

这种方式是将所有的/api-a-url/**的请求跳转到http://localhost:2222/,然而这种配置方式不够友好,因为你需要知道服务的地址才能进行配置。
我们推荐通过serviceId进行映射的方式:

  1. zuul.routes.api-a.path=/api-a/**
  2. zuul.routes.api-a.serviceId=service-A
  3. zuul.routes.api-b.path=/api-b/**
  4. zuul.routes.api-b.serviceId=service-B
  5. eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

我们把zuul注册到Eureka Server上去发现服务,然后只需要配置需要跳转的serviceId,具体要跳转的url无需关心。

接下来验证一下我们Zuul配置的路由策略是否生效:
首先启动我们最早搭建的Eureka注册中心,还有compute-service服务,我们将compute-service服务分别以service-A、service-B的服务名称在2222和3333端口启动,然后启动我们的Zuul服务。
打开Eureka注册中心:
image_1biga7bui17p11mba1me014ltfcb9.png-37.1kB
可以看到service-A、service-B以及我们的Zuul均已注册成功。
我们首先验证url的配置路由,访问http://localhost:5555/api-a-url/add?a=1&b=2
image_1bigan924g5ciek2ageou17bjm.png-13.2kB
service-A后台输出:
image_1bigb0r21cdr131l1ijk1n391q9t1t.png-38.2kB
再验证通过serviceId映射配置,访问http://localhost:5555/api-b/add?a=1&b=2
image_1bigau3hmj943dhna710i6dts13.png-13.3kB
service-B后台输出:
image_1bigb011k6m91ango7bn3fb2s1g.png-50.7kB

除此之外,Zuul还有着强大的过滤功能,比如外部访问安全控制。
我们来实现一个Zuul过滤功能,外部必须通过有效的accessToken才能进行服务调用:
首先要继承ZuulFilter,并重写它的四个抽象方法。

  1. public class AccessFilter extends ZuulFilter {
  2. private static Logger log = LoggerFactory.getLogger(AccessFilter.class);
  3. @Override
  4. public String filterType() {
  5. return "pre";
  6. }
  7. @Override
  8. public int filterOrder() {
  9. return 0;
  10. }
  11. @Override
  12. public boolean shouldFilter() {
  13. return true;
  14. }
  15. @Override
  16. public Object run() {
  17. RequestContext ctx = RequestContext.getCurrentContext();
  18. HttpServletRequest request = ctx.getRequest();
  19. log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
  20. Object accessToken = request.getParameter("accessToken");
  21. if(accessToken == null) {
  22. log.warn("access token is empty");
  23. ctx.setSendZuulResponse(false);
  24. ctx.setResponseStatusCode(401);
  25. return null;
  26. }
  27. log.info("access token ok");
  28. return null;
  29. }
  30. }

filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:

filterOrder:通过int值来定义过滤器的执行顺序
shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。
run:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回body内容进行编辑等。

之后我们需要在启动类中添加@bean,对定义的过滤器进行实例化。

  1. @EnableZuulProxy
  2. @SpringCloudApplication
  3. public class Application {
  4. public static void main(String[] args) {
  5. new SpringApplicationBuilder(Application.class).web(true).run(args);
  6. }
  7. @Bean
  8. public AccessFilter accessFilter() {
  9. return new AccessFilter();
  10. }
  11. }

重启Zuul服务,再次访问:http://localhost:5555/api-a/add?a=1&b=2,无返回结果:
image_1bigbjbvg1m4d1hvsm28733n6k2a.png-11.8kB
后台服务日志:
image_1bigbkv3jkub1ph01vpq3581r6l2n.png-48.5kB

关于Zuul过滤器的功能还有很多,本文暂到此,在后续的实际应用中再单独研究。

本文源码已上传github:https://github.com/coldxiangyu/spring-cloud-demo/tree/master/zuul-gateway

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注