返回介绍

快速入门

发布于 2024-08-18 11:12:34 字数 9491 浏览 0 评论 0 收藏 0

介绍了这么多关于API网关服务的概念和作用,在这一节中,我们不妨用实际的示例来直观地体验一下Spring Cloud Zuul中封装的API网关是如何使用和运作,并应用到微服务架构中去的。

构建网关

首先,在实现各种API网关服务的高级功能之前,我们需要做一些准备工作,比如,构建起最基本的API网关服务,并且搭建几个用于路由和过滤使用的微服务应用等。对于微服务应用,我们可以直接使用之前章节实现的hello-service和feign-consumer。虽然之前我们一直将feign-consumer视为消费者,但是在Eureka的服务注册与发现体系中,每个服务既是提供者也是消费者,所以 feign-consumer 实质上也是一个服务提供者。之前我们访问的http://localhost:9001/feign-consumer等一系列接口就是它提供的服务。读者也可以使用自己实现的微服务应用,因为这部分不是本章的重点,任何微服务应用都可以被用来进行后续的试验。这里,我们详细介绍一下API网关服务的构建过程。

- 创建一个基础的Spring Boot工程,命名为api-gateway,并在pom.xml中引入spring-cloud-starter-zuul依赖,具体如下:

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.3.7.RELEASE</version>

<relativePath/>

</parent>

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-zuul</artifactId>

</dependency>

</dependencies>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-dependencies</artifactId>

<version>Brixton.SR5</version>

<type>pom</type>

<scope>import</scope>

</dependency>

</dependencies>

</dependencyManagement>

对于spring-cloud-starter-zuul依赖,可以通过查看它的依赖内容了解到:该模块中不仅包含了Netflix Zuul的核心依赖zuul-core,它还包含了下面这些网关服务需要的重要依赖。

- spring-cloud-starter-hystrix:该依赖用来在网关服务中实现对微服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发API网关资源无法释放,从而影响其他应用的对外服务。

- spring-cloud-starter-ribbon:该依赖用来实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试。

- spring-boot-starter-actuator:该依赖用来提供常规的微服务管理端点。另外,在Spring Cloud Zuul中还特别提供了/routes端点来返回当前的所有路由规则。

- 创建应用主类,使用@EnableZuulProxy注解开启Zuul的API网关服务功能。

@EnableZuulProxy

@SpringCloudApplication

public class Application {

public static void main(String[]args){

new SpringApplicationBuilder(Application.class).web(true).run(args);

}

}

- 在application.properties中配置Zuul应用的基础信息,如应用名、服务端口号等,具体内容如下:

spring.application.name=api-gateway

server.port=5555

完成上面的工作后,通过Zuul实现的API网关服务就构建完毕了。

请求路由

下面,我们将通过一个简单的示例来为上面构建的网关服务增加请求路由的功能。为了演示请求路由的功能,我们先将之前准备的Eureka服务注册中心和微服务应用都启动起来。此时,我们在Eureka信息面板中可以看到如下图所示的两个微服务应用已经被注册成功了。

传统路由方式

使用Spring Cloud Zuul实现路由功能非常简单,只需要对api-gateway服务增加一些关于路由规则的配置,就能实现传统的路由转发功能,比如:

zuul.routes.api-a-url.path=/api-a-url/**

zuul.routes.api-a-url.url=http://localhost:8080/

该配置定义了发往 API 网关服务的请求中,所有符合/api-a-url/**规则的访问都将被路由转发到http://localhost:8080/地址上,也就是说,当我们访问http://localhost:5555/api-a-url/hello的时候,API网关服务会将该请求路由到http://localhost:8080/hello 提供的微服务接口上。其中,配置属性zuul.routes.api-a-url.path中的api-a-url部分为路由的名字,可以任意定义,但是一组path和url映射关系的路由名要相同,下面将要介绍的面向服务的映射方式也是如此。

面向服务的路由

很显然,传统路由的配置方式对于我们来说并不友好,它同样需要运维人员花费大量的时间来维护各个路由path与url的关系。为了解决这个问题,Spring Cloud Zuul实现了与Spring Cloud Eureka的无缝整合,我们可以让路由的path不是映射具体的url,而是让它映射到某个具体的服务,而具体的url则交给Eureka的服务发现机制去自动维护,我们称这类路由为面向服务的路由。在Zuul中使用服务路由也同样简单,只需做下面这些配置。

- 为了与 Eureka 整合,我们需要在 api-gateway 的 pom.xml 中引入 springcloud-starter-eureka依赖,具体如下:

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-eureka</artifactId>

</dependency>

- 在api-gateway的application.properties配置文件中指定Eureka注册中心的位置,并且配置服务路由。具体如下:

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.serviceId=hello-service

zuul.routes.api-b.path=/api-b/**

zuul.routes.api-b.serviceId=feign-consumer

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

针对我们之前准备的两个微服务应用hello-service和feign-consumer,在上面的配置中分别定义了两个名为api-a 和 api-b 的路由来映射它们。另外,通过指定Eureka Server服务注册中心的位置,除了将自己注册成服务之外,同时也让Zuul能够获取hello-service和feign-consumer服务的实例清单,以实现path映射服务,再从服务中挑选实例来进行请求转发的完整路由机制。

在完成了上面的服务路由配置之后,我们可以将eureka-server、hello-service、feign-consumer以及这里用Spring Cloud Zuul构建的api-gateway都启动起来。启动完毕,在 eureka-server 的信息面板中,我们可以看到,除了 hello-service 和feign-consumer之外,多了一个网关服务API-GATEWAY。

通过上面的搭建工作,我们已经可以通过服务网关来访问 hello-service 和feign-consumer这两个服务了。根据配置的映射关系,分别向网关发起下面这些请求。

- http://localhost:5555/api-a/hello:该 url 符合/api-a/**规则,由api-a路由负责转发,该路由映射的serviceId为hello-service,所以最终/hello请求会被发送到hello-service服务的某个实例上去。

- http://localhost:5555/api-b/feign-consumer:该url符合/api-b/**规则,由api-b路由负责转发,该路由映射的serviceId为feign-consumer,所以最终/feign-consumer请求会被发送到feign-consumer服务的某个实例上去。

通过面向服务的路由配置方式,我们不需要再为各个路由维护微服务应用的具体实例的位置,而是通过简单的path与serviceId的映射组合,使得维护工作变得非常简单。这完全归功于Spring Cloud Eureka的服务发现机制,它使得API网关服务可以自动化完成服务实例清单的维护,完美地解决了对路由映射实例的维护问题。

请求过滤

在实现了请求路由功能之后,我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。但是,每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对它们开放。然而,目前的服务路由并没有限制权限这样的功能,所有请求都会被毫无保留地转发到具体的应用并返回结果,为了实现对客户端请求的安全校验和权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。不过,这样的做法并不可取,它会增加日后系统的维护难度,因为同一个系统中的各种校验逻辑很多情况下都是大致相同或类似的,这样的实现方式会使得相似的校验逻辑代码被分散到了各个微服务中去,冗余代码的出现是我们不希望看到的。所以,比较好的做法是将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。在完成了剥离之后,有不少开发者会直接在微服务应用中通过调用鉴权服务来实现校验,但是这样的做法仅仅只是解决了鉴权逻辑的分离,并没有在本质上将这部分不属于冗余的逻辑从原有的微服务应用中拆分出,冗余的拦截器或过滤器依然会存在。

对于这样的问题,更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问我们的系统已经有了统一入口,既然这些校验与具体业务无关,那何不在请求到达的时候就完成校验和过滤,而不是转发后再过滤而导致更长的请求延迟。同时,通过在网关中完成校验和过滤,微服务应用端就可以去除各种复杂的过滤器和拦截器了,这使得微服务应用接口的开发和测试复杂度也得到了相应降低。

为了在API网关中实现对客户端请求的校验,我们将继续介绍Spring Cloud Zuul的另外一个核心功能:请求过滤。Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤,实现的方法非常简单,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了。

下面的代码定义了一个简单的 Zuul 过滤器,它实现了在请求被路由之前检查HttpServletRequest中是否有accessToken参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。

public class AccessFilter extends ZuulFilter {

private static Logger log=LoggerFactory.getLogger(AccessFilter.class);

@Override

public String filterType(){

return "pre";

}

@Override

public int filterOrder(){

return 0;

}

@Override

public boolean shouldFilter(){

return true;

}

@Override

public Object run(){

RequestContext ctx=RequestContext.getCurrentContext();

HttpServletRequest request=ctx.getRequest();

log.info("send {} request to {}",request.getMethod(),

request.getRequestURL().toString());

Object accessToken=request.getParameter("accessToken");

if(accessToken==null){

log.warn("access token is empty");

ctx.setSendZuulResponse(false);

ctx.setResponseStatusCode(401);

return null;

log.info("access token ok");

return null;

}

}

}

在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写下面4个方法来实现自定义的过滤器。这4个方法分别定义了如下内容。

- filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。

- filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。

- shouldFilter:判断该过滤器是否需要被执行。这里我们直接返回了 true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。

- run:过滤器的具体逻辑。这里我们通过ctx.setSendZuulResponse(false)令 zuul 过滤该请求,不对其进行路由,然后通过 ctx.setResponseStatusCode(401)设置了其返回的错误码,当然也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回的body内容进行编辑等。

在实现了自定义过滤器之后,它并不会直接生效,我们还需要为其创建具体的 Bean才能启动该过滤器,比如,在应用主类中增加如下内容:

@EnableZuulProxy

@SpringCloudApplication

public class Application {

public static void main(String[]args){

new SpringApplicationBuilder(Application.class).web(true).run(args);

}

@Bean

public AccessFilter accessFilter(){

return new AccessFilter();

}

}

在对api-gateway服务完成了上面的改造之后,我们可以重新启动它,并发起下面的请求,对上面定义的过滤器做一个验证。

- http://localhost:5555/api-a/hello:返回401错误。

- http://localhost:5555/api-a/hello&accessToken=token:正确路由到hello-service的/hello接口,并返回Hello World。

到这里,对于API网关服务的快速入门示例就完成了。通过对Spring Cloud Zuul两个核心功能的介绍,相信读者已经能够体会到API网关服务对微服务架构的重要性了,就目前掌握的API网关知识,我们可以将具体原因总结如下:

- 它作为系统的统一入口,屏蔽了系统内部各个微服务的细节。

- 它可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。

- 它可以实现接口权限校验与微服务业务逻辑的解耦。

- 通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。

实际上,基于Spring Cloud Zuul实现的API网关服务除了上面所示的优点之外,它还有一些更加强大的功能,我们将在后续的章节对其进行更深入的介绍。通过本节的内容,我们只是希望以一个简单的例子带领读者先来简单认识一下API网关服务提供的基础功能以及它在微服务架构中的重要地位。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文