Spring Cloud Greenwich.RC1现已发布。(https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now#spring-cloud-netflix-projects-entering-maintenance-mode)
还有一些其他的API网关实现,如Kong、Tyk、Apigee等,它们并不基于Spring Cloud。但是本讨论完全基于Spring团队创建的开源Spring Cloud Gateway。
一、简介
Spring Cloud Gateway是非阻塞式的,即它的设计、编写方式绝不会阻塞主线程。相反,这些线程始终可以为请求提供服务,并在后台异步处理请求,一旦处理完成就返回响应。
Spring Cloud Gateway提供以下几个功能:
- 将应用程序中所有服务的路由映射到单个URL。
- 构建过滤器,可以检查并处理通过网关发出的请求和响应。
- 构建谓词,这些对象允许我们在执行或处理请求之前检查请求是否满足一组给定的条件。
Spring Cloud Gateway是一个反向代理。反向代理是位于试图访问资源的客户端和资源本身之间的中间服务器。客户端甚至不知道自己正在与服务器通信。反向代理负责捕获客户端的请求,然后代表客户端调用远程资源。简而言之,反向代理就像其他API网关一样,充当所有进入系统的请求的单一入口点,而系统则分为一个或多个微服务。
可以对网关进行配置,以基于与DiscoveryClient兼容的服务注册表中注册的服务创建路由。要启用此功能,我们需要在属性文件中设置以下属性,并确保DiscoveryClient实现位于类路径上并已启用(例如Netflix Eureka、Consul或Zookeeper)。
spring.cloud.gateway.discovery.locator.enabled=true
Spring Cloud Gateway现在将自动使用被调用服务的Eureka服务ID,并将其映射到下游服务实例。
二、路由
路由可以通过Java配置或通过在属性/YAML文件中配置来定义。在这里,为了简单起见,我们将使用第二种方法,因为它可以根据需求进行外部化。
spring.cloud.gateway.routes[0].id=product-service
spring.cloud.gateway.routes[0].uri=lb://product-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/product/**
spring.cloud.gateway.routes[1].id=inventory-service
spring.cloud.gateway.routes[1].uri=lb://inventory-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/inventory/**
三、负载均衡
当存在多个可用实例时,Spring Cloud Gateway将智能地在Discovery客户端中的可用实例之间平衡传入请求的负载。
它内部使用spring-cloud-loadbalancer来分发请求流量。它使用其中一种算法来完成相同的操作,但是负载均衡算法的内部实现超出了本讨论的范围,我们将在演示结束后的几分钟内更详细地讨论它是如何实现的。
注意:这不能与使用Spring-cloud-loadbalancer的客户端负载均衡混淆,后者需要在服务通过基于Spring的不同同步/异步Rest客户端(如RestTemplate、WebClient等)相互通信时使用,与Spring Cloud的Open Feign不同,这些客户端默认情况下不进行负载均衡。此外,当请求到达单个服务之一时,需要客户端负载均衡,而在Spring Cloud Gateway中,请求仍在API Gateway层,不需要客户端负载均衡。
好了,现在让我们通过一个快速演示来说明这一点吧。
四、演示
在这个演示中,我们将创建两个服务,一个是产品服务(product service),一个是库存服务(inventory service),并将它们的多个实例注册到Netflix Eureka Discovery Server上。完成后,我们将创建API Gateway服务器,并将其注册到Eureka Client中。
所有的服务都是使用Spring Boot 3.2.4和Spring Cloud 2023.0.0创建的。
所有服务启动并正常运行后,我们可以在下面的Eureka仪表板上看到注册的所有服务,它们运行在8761端口上,每个服务有多个(2个)实例。
图片
Eureka仪表板显示所有已注册服务,运行在8761端口上。
现在让我们深入了解各个服务。为了展示Spring Cloud Gateway的路由和负载均衡功能,我们将尽量保持业务逻辑的最小化。
我们在每个服务中创建了一个/greet端点,它是一个HTTP GET请求。为了展示负载均衡功能,我们在响应体中发送以下字段:
- greeting:一个简单的硬编码问候消息。
- instanceid:实际注册到Eureka Server的实例ID。
- port:实例的实际端口号(由Spring Boot Embedded Tomcat动态创建)。
- url:端点的完整URL。
但是其中最重要的是端口,因为它是用来识别负载均衡功能的。为了获取该值,我们可以在控制器层添加以下代码片段,如下所示:
@Value("${spring.application.name}")
private String appName;
// 只有当发现客户端是Eureka时才有效
private EurekaClient eurekaClient;
// 连接Eureka客户端
public ProductController(EurekaClient eurekaClient) {
this.eurekaClient = eurekaClient;
}
@GetMapping("/greet")
public ResponseEntity getProduct(HttpServletRequest request) {
InstanceInfo service = eurekaClient.getApplication(appName).getInstances().get(0);
response.setPort(service.getPort());
return new ResponseEntity<>(response, HttpStatus.OK);
}
接下来,我们需要在API Gateway中配置路由。路由配置已在本文的路由部分中提到过。
一切就绪后,让我们测试一下应用程序。
五、测试
我们将需要一些REST API测试工具,如Postman或Insomnia。在这里使用了Insomnia。
Spring Cloud Gateway运行在Spring Boot的默认端口8080上。我们将访问API Gateway,而不是直接调用产品/库存服务,这也是本演示的目的所在,看看我们是否能够获得响应。
图片
图片
由于我们能够获得正确的响应,说明路由功能正常工作。
现在,让我们来看看负载均衡方面的内容。我们只选取库存服务实例进行演示。
在上面的截图中,我们看到库存服务的端口是58464,这意味着请求是从58464端口提供的。让我们再次发出请求,看看我们是否会一直获得相同的端口。
图片
在多次访问端点后,我们发现一些不同的端口号58436和一些不同的实例ID,这意味着请求是从另一个实例提供的,而不是同一个实例。这意味着它成功地分发了请求。
六、深入了解负载均衡
完成上述工作后,让我们试着深入研究一下。我们从API Gateway服务器的日志开始,通过将日志级别设置为TRACE来查看发生了什么。
日志有很多,但最重要的是下面的内容,它可以帮助我们窥探框架层的幕后运行状况。
图片
因此,基本上,负载均衡的实际URL是在Spring Cloud Gateway jar文件的以下类中解析的:
https://github.com/spring-cloud/spring-cloud-gateway/blob/main/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilter.java
日志分别来自第108行和第143行。现在,在所有的方法中,choose()方法是最重要的,它创建了负载均衡器的实例ReactLoadBalancer。ReactLoadBalancer是一个接口,来自于spring cloud loadbalancer,它为实际的负载均衡实现算法提供了一个抽象。目前有两种可用的实现方式,即RandomLoadBalancer和RoundRobinLoadBalancer。关于它的工作原理和算法实现的更多内部细节对于开发者来说并不重要,因为Spring Cloud Gateway框架已经在开箱即用时处理了这些细节。
但需要理解的一点是,如果没有lb方案,负载均衡将无法工作。在路由部分,我们在属性文件的API Gateway服务中定义了以下配置。
spring.cloud.gateway.routes[1].uri=lb://inventory-service
如果URL具有lb方案(即lb://myservice),它将使用Spring Cloud ReactorLoadBalancer将名称解析为实际的主机和端口。因此,如果不添加lb方案,负载均衡将无法工作。
七、结语
这就是关于Spring Cloud Gateway中路由和负载均衡的讨论。完整的代码可以在以下的GitHub链接中找到。
https://github.com/purbarunc/Spring-Cloud-Microservice/tree/gateway-loadbalancing