API 网关服务:Spring Cloud Zuul
API网关是一个更为智能的应用服务器。它的定义类似于面向对象设计模式的Facade模式,它的存在就像是对整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、负载均衡、娇艳过滤等功能之外,还需要更多能力,比如与服务治理框架结合、请求转发的熔断机制、服务的聚合等一系列功能。
快速入门
介绍了API网关服务的概念和作用,我们用实际的示例直观的体验一下Spring Cloud Zuul中封装的API网关是如何使用和运作的,并应用到微服务架构中去的。
构建网关
在使用API网关服务的高级功能之前,我们需要做一些准备工作,比如,构建起最基本的API网关服务,并搭建几个用于路由和过滤的微服务应用等。
创建一个spring boot工程,并加入spring-cloud-starter-netflix-zuul 依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
创建应用主类,使用@EnableZuulProxy注解开启Zuul的API网关服务功能
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringCloudApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
在application.properties中配置Zuul应用的基础信息
spring.application.name=api-gateway
server.port=5555
请求路由
下面,我们将通过一个简单的示例来为上面构建的网关服务增加路由请求的功能。为了演示,我们将之前准备的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/地址上。其中,配置属性的zuul.routes.api-a-url.path中的api-a-url部分为路由的名字,可以任意定义,但是一组path和url的映射关系路由名要相同,下面要介绍的面向服务的映射方式也是如此。
面向服务的路由
传统路由的方式对于我们来说并不友好,他同样需要运维人员花费大量的时间来维护各个路由的path和url的关系。为了解决这个问题,Spring cloud zuul实现了与Spring Cloud Eureka的无缝整合,我们可以让路由的path不是映射具体的url,而是让它映射到某个具体的服务,而具体的url则交给Eureka的服务发现机制去自动维护。
为了与Eureka整合,我们需要在api-gateway中加入eureka的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在api-gateway的application.properties配置文件中指定Eureka注册中心的位置,并配置服务路由
eureka.client.service-url.defaultZone=http://localhost:1111/eureka
zuul.routes.feign-consumer.path=/feign-consumer/**
zuul.routes.feign-consumer.service-id=feign-consumer
zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.service-id=hello-service
使用http://localhost:5555/feign-consumer/consumeSayHello类似url即可访问到feign-consumer服务的接口
请求过滤
为了在API网关中实现对客户端请求的校验,我们将继续介绍Spring Cloud Zuul的另一个核心功能:请求过滤。Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤,我们只需继承ZuulFilter抽象类并实现它定义的4歌抽象函数就可以完成对请求的拦截和过滤了。
package com.example.apigateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.http.HttpServletRequest;
public class AccessFilter extends ZuulFilter {
private static final Log logger = LogFactory.getLog(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 requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
logger.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURI()));
String token = request.getParameter("token");
if (token == null) {
logger.warn("token is empty");
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(401);
return null;
}
return null;
}
}
在过滤器代码中,我们重写了4个方法:
filterType:过滤器的类型,决定过滤器在请求的哪个生命周期执行。这里定义为pre,代表会在请求被路由之前执行
filterOrder:过滤器的执行顺序。当一个阶段存在多个过滤器时,需要根据该方法的返回值来依次执行
shouldFilter:判断过滤器是否需要被执行
run:过滤器的具体逻辑。
在实现了自定义过滤器去之后,它并不会直接生效,我们还需要为其创建具体的bean才能启动该过滤器,比如在启动主类中增加如下内容:
@Bean
AccessFilter accessFilter() {
return new AccessFilter();
}