微服务 - 第5部分:使用Spring Cloud Zuul Proxy来构建API网关
原文:Microservices - Part 5: Spring Cloud Zuul Proxy as API Gateway
作者:Siva Prasad Reddy Katamreddy
译者:ForeverL888
API网关是为服务架构中不可或缺的一个组件,我们将会从这个教程中学习到如何使用Spring Cloud Zuul Proxy 来构建API网关。
在微服务架构中,可能包含了大量的API服务和少数和页面UI组件进行交互的API接口。截至目前,许多微服务应用依旧是采用过去那种单体的前端框架,即将整个UI都构建在一个单独的模块中。你也可以选择会微前端,也就是将整个UI切分为独立的多个微服务,然后通过使用API接口来获取仅与该模块相关的数据。为了避免让单个UI模块可以访问到我们所有的微服务的详细信息,我们可以使用一个统一的代理接口,通过URL来匹配调用不同的微服务。在这篇文章中,我们将会学习到如何使用Spring Cloud Zuul Proxy来构建一个API网关。
在这篇文章中,我们将会学到
- 为什么我们需要API 网关
- 使用Spring Cloud Zuul Proxy来实现API网关
- 使用Zuul Filters来切分关注点(类似于Spring的AOP)
API网关,也叫做边界服务(Edge Service),是为一系列的微服务提供了一个统一接口。这样子的话,客户端就不需要了解整个微服务内部的所有细节。但是在微服务架构中使用API网关,有利亦有弊。
优点:
- 向客户端提供更简单的接口
- 避免向客户端暴露过多内部微服务的细节
- 允许重构微服务, 而不需要强制重构客户端的逻辑
- 能够集中的管理类似安全,监控,限速等可以横切的操作
缺点:
- 如果不采取合适的措施来构建网关的高可用,可能会引起单点故障
- 要了解各种不同的微服务API,可能需要了解API网关
Spring Cloud提供了可以用于创建API网关的Zuul Prxoy,该组件功能类似于Nginx。
让我们创建一个基于Spring Boot框架的前端的UI模块-“shoppingcart-ui”。该模块依赖了Web,Config Client, Eureka Discovery,Zuul的starter,并在主入口点类中使用@EnableZuulProxy进行标注。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
ShoppingcartUiApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ShoppingcartUiApplication {
public static void main(String[] args) {
SpringApplication.run(ShoppingcartUiApplication.class, args);
}
}
因为我们使用Eureka作为服务发现,所以URL格式形如/service-id/**会被转发到在Eureka Server上注册的service id 为“service-id”的微服务应用上。
举个例子,如果我们将来自前端应用的请求标记为http://localhost:8080/catalog-service/products,那么Eureka Server会从注册的服务仓库中找到service-id为catalog-service服务,并将这个请求的url重新设为/products,并发送到任意一个可到达的catalog-service的微服务实例上。
为了将“shoppingcart-ui”注册到Eureka的服务注册器上,我们需要按照以下设置
bootstrap.properties
spring.application.name=shoppingcart-ui
server.port=8080
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
通过这种配置,接下来我们可以使用JQuery来从catalog-service上获取商品信息
$.ajax({
url: '/catalog-service/products'
})
.done(function(data) {
this.products = data;
}.bind(this));
从我们的前端应用,我们使用http://localhost:8080/catalog-service/products对后端进行调用。假设catalog-service服务运行在8181端口,在注册时将“catalog-service”作为service-id。那么来自前端的请求将会被转发到http://host:8181/products。这时候前端应用是完全无法感知到实际的catalog-service任何部署的信息,包括主机名,端口号等。
我们同样也可以为这些url设置一个通用的前缀,就像“/api”,为了能够让Zuul来代理这类的url,我们需要设置zuul.prefix这个属性。
zuul.prefix=/api
现在,我们可以从前端页面,创建一个请求http://localhost:8080/api/catalog-service/products来获取商品。默认的,Zuul会自动的帮我们剥离配置的前缀后,并进行转发。
你也可以按照以下配置,为一个服务定义一个路径映射关系
zuul.routes.catalogservice.path=/catalog/**
zuul.routes.catalogservice.serviceId=catalog-service
通过这种配置,你可以使用http://localhost:8080/api/catalog/products这个url,
它将会被转发到service-id为catalog-service的微服务应用上。
默认的,Eureka Server会暴露所有注册在它上面的微服务。你可以使用zuul.ignored-services 属性来禁止这种行为,且只有显式配置的服务才会被暴露。
zuul.ignored-services=*
zuul.routes.catalogservice.path=/catalog/**
zuul.routes.catalogservice.serviceId=catalog-service
zuul.routes.orderservice.path=/orders/**
zuul.routes.orderservice.serviceId=order-service
通过这种配置,可以通过Zuul Proxy暴露出去的只有catalog-service, order-service,而不是所有的后端微服务。
因为Zuul扮演的是一个能够访问到所有微服务的代理角色,所以我们可以使用Zuul服务来实现一些类似安全,流量限制的切面操作。一个常见的使用示例是,将认证信息头转发到所有的下游服务。
使用Zuul Proxy为下发到游的微服务转发授权的信息头。
通常在微服务中,我们使用OAuth进行认证和授权。一旦一个客户端通过OAuth服务的认证,就会生成一个令牌。这个令牌会被添加到请求头中,通过这种方式就可以避免在访问每一个微服务时,还要进行单独的认证。我们可以使用Zuul Filter来实现类似的功能。
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
public class AuthHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getAttribute("AUTH_HEADER") == null) {
//generate or get AUTH_TOKEN, ex from Spring Session repository
String sessionId = UUID.randomUUID().toString();
ctx.addZuulRequestHeader("AUTH_HEADER", sessionId);
}
return null;
}
}
我们通过使用RequestContext.addZuulRequestHeader()方法来添加一个名字为AUTH_HEADER请求头。之后这个请求会被转发到Zuul Proxy的下游服务上。我们需要将它将它注册为一个Spring 的bean。
@Bean
AuthHeaderFilter authHeaderFilter() {
return new AuthHeaderFilter();
}