一、Gateway简介
Gateway是Spring Cloud官方推出的第二代微服务网关,它旨在提供统一的路由方式以及为微服务应用提供强大的负载均衡能力。与第一代Spring Cloud Netflix Zuul相比,Spring Cloud Gateway在性能、可扩展性、易用性等方面都有了显著的提升。其主要特性有以下几点:
- 基于Spring Boot 和Spring Cloud 开发,支持RESTful和WebSocket;
- 支持通过Feign或RestTemplate进行服务调用;
- 支持负载均衡、熔断、限流等操作;
- 支持动态路由、灵活的路由策略;
- 支持多种协议,如HTTP、WebSocket等。
二、Gateway工作流程
Spring Cloud Gateway的工作流程主要包括以下几个步骤:
- 客户端请求到达Gateway。
- 再由Gateway Handler Mapping根据请求路径匹配路由表,找到对应的路由规则。路由规则中包含断言,再根据断言规则对请求进行匹配,只有当请求符合断言规则时才会继续转发到Gateway Web Handler进行处理。
- Gateway Web Handler会根据路由规则将该请求转发到目标服务实例。
- 在将请求转发到目标服务实例前会经过过滤器的处理。
- 通过过滤器对该请求的处理后,最后到达目标服务进行该请求的处理。
- 目标服务处理完成后,将响应结果再根据路由规则并应用过滤器对响应进行处理,最后将响应返回给客户端。
三、Gateway使用
- 在项目中创建一个Gateway模块,在其中pom文件中引入gataway、nacos、以及loadbalancer的依赖。
bash
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- SpringCloud 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
- 路由配置,可以在YML中配置,也可以通过配置类@Configuration,这两种方式的功能一样,建议使用在YML中配置。
(1)配置类方式:通过@Bean注入一个RouteLocator配置类型。
bash
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.build();
}
(2)YML文件配置方式:
bash
# 服务配置
server:
port: 9003
spring:
application:
name: njh-gateway-app
cloud:
loadbalancer:
ribbon:
enabled: false
cache:
enabled: true
caffeine:
spec: initialCapacity=500,expireAfterWrite=5s
# nacos配置
nacos:
discovery:
server-addr: ${spring.cloud.nacos.config.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
group: ${spring.cloud.nacos.config.group}
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
# gateway配置
gateway:
routes:
- id: njh-web-app #路由id,保证唯一,这里配置成服务名
uri: lb://njh-web-app #匹配成功后路由的服务,lb表示启用负载均衡
predicates:
- Path=/api/webapp/** #断言,根据路径进行匹配
filters:
- name: Retry
args:
retries: 1 #重试次数
- id: njh-biz-app
uri: lb://njh-biz-app
predicates:
- Path=/api/bizapp/**
filters:
- name: Retry
args:
retries: 1 #重试次数
这样配置完后,当请求到达gateway后就会根据请求的路径进行匹配,然后将其转发到指定的服务去进行处理。例如:上面yml文件中配置的,请求中所有包含/api/webapp/
路径的请求都会转发到njh-web-app服务上进行处理,所有包含/api/bizapp/
路径的请求都会转发到njh-biz-app服务上进行处理。
四、Gateway三大核心组件
Gateway的三大组件通常指的是路由(Routing)、断言(Assertions)和过滤器(Filters)。通过这三大组件共同协作,使得Spring Cloud Gateway可以灵活地处理各种网络请求,并提供强大的API网关功能。
- 路由(Routing):路由是Spring Cloud Gateway中的基本组成,它由ID、目标URL、断言和过滤器组成。路由定义了如何将请求路由到特定的微服务。下面是routes属性的一些关键组成部分:
bash
spring:
cloud:
gateway:
routes:
- id: user_service_route
uri: lb://USER-SERVICE
predicates:
- Path=/users/**
filters:
- RewritePath=/users/(?<remaining>.*), /$\{remaining}
id:路由的唯一标识符。上面设置为user_service_route。
uri:请求被转发到的目标服务的URI。上面使用服务发现(lb://USER-SERVICE)来找到并转发请求到用户服务。
predicates:一组断言(条件),用于匹配请求。如果断言条件满足,请求将被路由到相应的URI。上面配置中的Path=/users/**断言匹配所有以/users/开头的路径。
filters:一组过滤器,用于在请求到达目标服务之前或响应返回客户端之后对请求和响应进行操作。上面配置中的RewritePath过滤器用于重写路径,移除/users/前缀。
- 断言(Assertions):断言主要用于匹配请求,只有当断言为true时,请求才会被路由到相应的URI。Spring Cloud Gateway提供了多种方式来定义断言,例如路径匹配、参数匹配等。Spring Cloud Gateway提供了多种内置的断言工厂,例如:
- Path:根据请求路径匹配断言。例如,Path=/api/**会匹配任何以/api/开头的路径。
- Query:根据请求查询参数匹配断言。例如,Query=name,aaa会匹配查询参数中包含name=aaa的请求。
- Header:根据请求头匹配断言。例如,Header=X-Requested-By,myapp会匹配请求头中包含X-Requested-By: myapp的请求。
- Method:根据HTTP请求方法匹配断言。例如,Method=GET仅匹配GET请求。
- Cookie:根据请求中的Cookie值匹配断言。例如,Cookie=session,mySessionId会匹配cookie中包含session=mySessionId的请求。
- Host=:根据请求的主机头匹配。例如,Host=**.example.com会匹配任何以example.com结尾的主机头。
- 过滤器(Filters):过滤器是Spring Cloud Gateway的核心组件之一,它可以在请求转发到相应微服务之前或之后执行一些操作,例如请求转发、响应修改、限流、认证等。Spring Cloud Gateway提供了许多默认的过滤器,同时也允许开发者自定义过滤器以满足特定的需求。过滤器可以链式执行,即一个请求可以经过多个过滤器的处理。每个过滤器都能够访问到请求和响应对象,并对它们进行修改。以下是一些常用的内置GatewayFilter类型:
- AddRequestHeader:向请求添加一个新的头部信息。
- AddResponseHeader:向响应添加一个新的头部信息。
- RewritePath:重写请求的路径。
- RemoveRequestHeader:从请求中移除一个头部信息。
- Hystrix:为网关提供断路器功能,防止服务故障导致网关崩溃。
- StripPrefix=2:去掉Path中前两个节点。
五、实现自定义过滤器
在Spring Cloud Gateway中实现自定义Filter主要分为以下几个步骤:
- 创建一个类实现GlobalFilter接口或者GatewayFilter接口。GlobalFilter允许你创建全局过滤器,而GatewayFilter通常用于创建特定路由的过滤器。
- 在该类中,实现filter方法,该方法接收ServerWebExchange对象和GatewayFilterChain对象作为参数。
- 通过ServerWebExchange对象,你可以访问和修改请求和响应的信息。
- 调用GatewayFilterChain对象的filter方法来继续执行后续的过滤器链。
- 如果需要,可以通过实现Ordered接口来指定过滤器的执行顺序。
- 要使Spring Cloud Gateway识别并使用这个自定义过滤器,需要在启动类上添加@EnableGatewayWebFlux注解,并确保自定义过滤器组件被Spring容器扫描到。
bash
@Component
public class CustomRequestHeaderFilter implements GatewayFilter, Ordered {
private static final int DEFAULT_ORDER = 0;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 添加自定义请求头
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-Custom-Header", "MyCustomValue")
.build();
// 将修改后的请求传递给下一个过滤器
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
// 返回过滤器的执行顺序,值越小,优先级越高。
return DEFAULT_ORDER;
}
}
六、实现负载均衡
在Spring Cloud Gateway中实现负载均衡主要依赖于内置的负载均衡器或者集成的服务发现组件。以下是实现负载均衡的几种方式:
(1)使用Ribbon实现负载均衡:
默认使用Netflix Ribbon作为负载均衡器。要启用Ribbon负载均衡,需要在application.yml配置文件中添加相关配置。
- 添加依赖,在pom.xml文件中添加以下依赖:
bash
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud Ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!-- Eureka (如果使用服务发现) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- 在application.yml中配置Ribbon,启用服务发现功能:
bash
spring:
cloud:
nacos:
discovery:
enabled: true
gateway:
routes:
- id: service_route
uri: lb://SERVICE_ID
filters:
- AddRequestHeader=X-Request-Redirected, By-Gateway
- 自定义Ribbon规则:通过实现IRule接口来创建自定义的Ribbon规则,然后在配置文件中指定这个规则。例如,创建一个自定义的轮询规则:
bash
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new RoundRobinRule();
}
}
# 在application.yml中引用这个规则:
spring:
ribbon:
NFLoadBalancerRuleClassName: com.yourpackage.RibbonConfiguration$YourCustomRule
(2)基于Spring Cloud LoadBalancer的负载均衡:
- 添加依赖
bash
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
- 在application.yml文件中配置路由
bash
# 禁用ribbon
spring:
cloud:
loadbalancer:
ribbon:
enabled: false
gateway:
routes:
- id: service_route
uri: lb://SERVICE_ID
filters:
- AddRequestHeader=X-Request-Redirected, By-Gateway
(3)自定义负载均衡器:
在Spring Cloud Gateway中,自定义负载均衡器可以通过实现LoadBalancerClientFactory接口来完成。
- 实现LoadBalancerClientFactory接口
bash
public class CustomLoadBalancerClientFactory implements LoadBalancerClientFactory {
@Override
public LoadBalancerClient create(String serviceId) {
// 实现创建自定义LoadBalancerClient的逻辑
// 返回自定义的LoadBalancerClient实例
return new CustomLoadBalancerClient();
}
}
- 创建自定义LoadBalancerClient
bash
public class CustomLoadBalancerClient implements LoadBalancerClient {
@Override
public Mono<ServerWebExchange> execute(Request request, ServerWebExchange exchange) {
// 实现自定义的负载均衡逻辑
// 返回处理后的ServerWebExchange
return Mono.just(exchange);
}
}
- 配置Spring Cloud Gateway使用自定义的负载均衡器
bash
spring:
cloud:
loadbalancer:
client:
factory: com.yourpackage.CustomLoadBalancerClientFactory
- 使用自定义负载均衡器
bash
@Configuration
public class GatewayConfig {
@Bean
public RouteLocators customRouteLocations(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/customPath/**")
.uri("lb://CUSTOM_SERVICE"))
.build();
}
}