在微服务架构中,可能存在许多API服务和很少与UI通信的UI组件。截至目前,许多基于微服务的应用程序仍然使用单片前端,其中整个UI构建为单个模块。您可以选择使用micro-frontends,其中UI也被分解为多个微服务,与API通信以获取相关数据。我们可以提供统一的代理接口,而不是让UI知道我们所有的微服务细节,而是根据URL模式将调用委托给各种微服务。在这篇文章中,我们将学习如何使用Spring Cloud Zuul Proxy创建API网关。
使用Spring Boot和Spring Cloud的MicroServices
- 第1部分:MicroServices:Spring Boot和Spring Cloud概述
- 第2部分:MicroServices:使用Spring Cloud Config和Vault进行配置管理
- 第3部分:MicroServices:Spring Cloud Service Registry and Discovery
- 第4部分:MicroServices:使用Netflix Hystrix的Spring Cloud断路器
- 第5部分:MicroServices:Spring Cloud Zuul Proxy作为API网关
- 第6部分:MicroServices:使用Spring Cloud Sleuth和Zipkin进行分布式跟踪
在这篇文章中,我们将学习:
- 为什么我们需要API网关?
- 使用Spring Cloud Zuul Proxy实现API网关
- 使用Zuul过滤器来解决交叉问题
为什么我们需要API网关?
API网关,即边缘服务,为一组微服务提供统一的接口,以便客户无需了解微服务内部的所有细节。但是,在微服务架构中使用API网关模式有一些优缺点。
优点:
- 为客户提供更简单的界面
- 可用于防止将内部微服务结构暴露给客户端
- 允许重构微服务而不强制客户端重构消耗逻辑
- 可以集中安全,监控,速率限制等交叉问题
缺点:
- 如果不采取适当措施使其具有高可用性,它可能会成为单点故障
- 各种微服务API的知识可能会渗透到API网关中
使用Spring Cloud Zuul Proxy实现API网关
Spring Cloud提供类似于Nginx的 Zuul代理,可用于创建API网关。
让我们创建一个前端UI模块“shoppingcart-ui”作为SpringBoot应用程序,它也充当Zuul代理。使用Web,Config Client,Eureka Discovery,Zuul启动器创建一个SpringBoot项目,并使用@EnableZuulProxy注释主要入口点类。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
|
<
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
1
2
3
4
五
6
7
8
9
10
11
|
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 Discovery,因此来自具有URL模式/ service-id / **的代理的请求将被路由到服务ID为“service-id”的 Eureka Server中注册的服务。
For,ex:从UI应用程序中,如果我们向http:// localhost:8080 / catalog-service / products 发出请求,那么它将在Service Registry中查找ServiceID “catalog-service”并将请求与URL / products一起发送到一个可用的目录服务实例。
为了实现这一目标,我们需要向Eureka Service Registry注册“shoppingcart-ui”。
bootstrap.properties
1
2
3
|
spring.application.name=shoppingcart-ui
server.port=8080
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
|
现在使用此配置,我们可以使用jQuery从catalog-service获取产品信息,如下所示:
1
2
3
4
五
6
|
$.ajax({
url:
'/catalog-service/products'
})
.done(
function
(data) {
this
.products = data;
}.bind(
this
));
|
在我们的UI应用程序中,我们正在调用http:// localhost:8080 / catalog-service / products。假设catalog-service已在ServiceID“catalog-service”中注册并在端口8181上运行,则此请求将转发到http:// host:8181 / products。但UI完全不知道运行的实际目录服务在哪里,它的主机名端口号等。
我们还可以使用URL的公共前缀,例如“/ api”,我们希望Zuul通过设置zuul.prefix属性来代理。
1
|
zuul.prefix=/api
|
现在,我们可以从UI请求获取http:// localhost:8080 / api / catalog-service / products中的产品。默认情况下,Zuul将剥离前缀并转发请求。
您还可以自定义服务的路径映射,如下所示:
1
2
|
zuul.routes.catalogservice.path=/catalog/**
zuul.routes.catalogservice.serviceId=catalog-service
|
使用此配置,您可以使用URL http:// localhost:8080 / api / catalog / products,这些产品将通过serviceId catalog-service转发到服务。
默认情况下,将公开在Eureka Server中注册的所有服务。您可以使用zuul.ignored-services属性来禁用此行为,并仅公开显式配置的服务。
1
2
3
4
五
6
7
|
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代理公开,但不通过库存服务公开。
使用Zuul过滤器来解决交叉问题
由于Zuul充当我们所有微服务的代理,我们可以使用Zuul服务来实现一些跨领域的问题,如安全性,速率限制等。一个常见的用例是将Authentication头转发给所有下游服务。
通常在微服务中,我们将使用OAuth服务进行身份验证和授权。一旦客户端通过身份验证,OAuth服务将生成一个令牌,该令牌应包含在发送给其他微服务的请求中,以便客户端无需单独为每个服务进行身份验证。我们可以使用Zuul过滤器来实现这样的功能。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
|
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添加为请求标头,该请求标头将转发到下游服务。我们需要将它注册为Spring bean。
1
2
3
4
|
@Bean
AuthHeaderFilter authHeaderFilter() {
return
new
AuthHeaderFilter();
}
|
您可以在https://github.com/sivaprasadreddy/spring-boot-microservices-series找到本文的源代码。