译:微服务 - 第5部分:使用Spring Cloud Zuul Proxy来构建API网关

497 阅读5分钟
原文链接: www.spring4all.com

微服务 - 第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-serviceorder-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();
}

点击此处获取该文章使用的源码。